Introduction
In Part 1 I have started with a list of C++ dependency injection libraries, and sketched an example problem, solved and tested with the help of Wallaroo. For the second part, Hypodermic should have been in focus, but unfortunately, no version compiled out of the box on MacOS with Clang [1. To do in 2014]. Hence, the article is about sauce, an inspiration from google-guice.
Dependency Injection using sauce
A little repetition, the example problem contains of 3 interfaces with a linear dependency graph of the implementations. Not to repeat the implementations, and not change the structure of the existing code, simple factories for the default implementations have been exposed, but not the concrete types in the headers, i.e. for the default renderer implementation:
std::shared_ptr< IRender > NewKeyRenderer(std::shared_ptr< IGetKeyValue > model) { return std::make_shared<KeyRenderer>(model); };
Implementations
Simplified, the sauce solution looks as follows. Unlike in Wallaroo, in sauce, the implementations do not need to derive from a common “dependency” class. Any type can be bound to any derived class. The implementation of a renderer for the sauce example just adds a delegation to the original implementation [2. Efficiency and overhead are not considered in the first place.]:
class SauceKeyRenderer : public IRender { public: SauceKeyRenderer(std::shared_ptr< IGetKeyValue > m) : pimpl ( NewKeyRenderer(m) ) { } public: virtual std::string Render() { //... return pimpl->Render(); } private: std::shared_ptr< IRender > pimpl; };
Modules
Modules are containers of bindings of interfaces to their implementations. The easiest way to define a module is to provide a function with the signature void (* module)(sauce::Binder &)
.
Here’s the self-explaining module:
void render_module(sauce::Binder& b) { b.bind<IRender>().to<SauceKeyRenderer(IGetKeyValue&)>(); }
Once again, obviously, the type doesn’t have to leak outside the module due to inversion of control.
Note that the IGetKeyValue
dependency of SauceKeyRenderer
is injected via the constructor parameter, enabling automatic dependency resolution at later time.
Injectors
Instances of the bound interfaces are obtainable from injectors, which can be constructed from a collection of modules:
sauce::shared_ptr<Injector> injector = Modules() .add(render_module) .add(decoder_module) .add(model_module) .createInjector() ;
Resolving the dependencies
The hello-world example of resolving the dependencies is one simple line:
sauce::shared_ptr<IRender> renderer = injector->get<IRender>();
In this case, SauceKeyRenderer
will get resolved and two other dependencies will be automatically instantiated and injected (see test_sauce.cpp).
Mocking and singletons
Sauce allows for different lifetime models of the interface instances. Singletons are possible with the concept of scoped injectors, in which, the instances are shared.
For googlemock getting the resolved instance is crucial, since the expectations are bound to an instance of the mocked class. With automatic dependency resolution and no hacks, the interface IModel
would resolve to a new instance in the test. Hence, unless a way to use the shared instance lifetime inside the test is found, the mock is implemented trivially without googlemock.
Summary
This article is once again a short intro into the paradigm of another Dependency Injection library sauce. A more elaborate intro can be read in sauce’s tutorial test suite.
Source: https://github.com/d-led/test-ioc-cpp
More to come…