Boost::DI
- C++14 header only Dependency Injection library with no dependencies
Dependency Injection (DI)
- involves passing (injecting) one or more dependencies (or services) to a dependent object (or client) which become part of the client’s state.
Example
No dependency injection
class coffee_maker {
public:
// create dependencies in the constructor
coffee_maker()
: heater(std::make_shared<electric_heater>())
, pump(std::make_unique<heat_pump>(heater))
{ }
void brew() {
heater->on();
pump->pump();
}
private:
std::shared_ptr<iheater> heater;
std::unique_ptr<ipump> pump;
};
Dependency injection
class coffee_maker {
public:
// inject dependencies via constructor
coffee_maker(std::shared_ptr<iheater> heater, std::unique_ptr<ipump> pump)
: heater(heater), pump(std::move(pump))
{ }
void brew() {
heater->on();
pump->pump();
}
private:
std::shared_ptr<iheater> heater;
std::unique_ptr<ipump> pump;
};
Motivation
- DI provides loosely coupled code (separation of business logic and object creation)
- DI provides easier to maintain code (different objects might be easily injected)
- DI provides easier to test code (fakes objects might be injected)
Examples
Example 1
app
struct renderer { int device; }; class iview { public: virtual ~iview() = default; virtual void update() = 0; }; class model {}; class controller { public: controller(model&, view&) {} }; class user {}; class app { public: app(controller&, user&) {} };
usual approach to create app
renderer renderer_; view view_{"title", renderer_}; model model_; controller controller_{model_, view_}; user user_; app app_{controller_, user_};
create app with boost::di
auto app = di::make_injector().create<app>();
Example 2
class controller;
struct renderer {
int device;
};
class view {
public:
view(std::string /*title*/, const renderer&) {}
};
class model {};
class user {};
class app {
public:
app(controller&, user&) {}
};
/// CREATE OBJECTS TREE
class controller {
public:
controller(model&, view&) {}
};
int main() {
/// NO DEPENDENCY INJECTION
{
renderer renderer_;
view view_{"", renderer_};
model model_;
controller controller_{model_, view_};
user user_;
app app_{controller_, user_};
(void)app_;
}
/// DEPENDENCY INJECTION
{
auto injector = di::make_injector();
auto app_ = injector.create<app>();
(void)app_;
}
}
/// CHANGE CONTROLLER CONSTRUCTOR ORDER
class controller {
public:
controller(view&, model&) {}
};
int main() {
/// NO DEPENDENCY INJECTION
{
renderer renderer_;
view view_{"", renderer_};
model model_;
//controller controller_{model_, view_};
controller controller_{view_, model_}; /// CHANGE
user user_;
app app_{controller_, user_};
(void)app_;
}
/// DEPENDENCY INJECTION
{
auto injector = di::make_injector();
auto app_ = injector.create<app>();
(void)app_;
}
}
/// ADD A NEW DEPENDENCY TO CONTROLLER
struct configuration {};
class controller {
public:
controller(view&, model&, configuration) {}
};
int main() {
/// NO DEPENDENCY INJECTION
{
renderer renderer_;
view view_{"", renderer_};
model model_;
//controller controller_{model_, view_};
controller controller_{view_, model_, configuration{}}; /// CHANGE
user user_;
app app_{controller_, user_};
(void)app_;
}
/// DEPENDENCY INJECTION
{
auto injector = di::make_injector();
auto app_ = injector.create<app>();
(void)app_;
}
}
Bindings
- interfaces
class iview {
public:
virtual ~iview() noexcept = default;
virtual void update() =0;
};
class gui_view: public iview {
public:
gui_view(std::string title, const renderer&) {}
void update() override {}
};
class text_view: public iview {
public:
void update() override {}
};
auto injector = di::make_injector(
di::bind<iview>.to<gui_view>() // bind interface to implementation
);
struct T { // create using uniform initialization
int& a; // might be used to serialize
double b;
};
auto i = 42;
auto injector = di::make_injector(
di::bind<int>.to(i),
di::bind<double>.to(87.0)
);
injector.create<T>(); // will create T{i, 87.0};
auto use_gui_view = true/false;
auto injector = di::make_injector(
di::bind<iview>.to([&](const auto& injector) -> iview& {
return use_gui_view ?
injector.template create<gui_view&>() :
injector.template create<text_view&>();
})
);
use_gui_view = true;
assert(dynamic_cast<gui_view*>(
injector.create<std::unique_ptr<iview>().get())
);
use_gui_view = false;
assert(dynamic_cast<text_view*>(
injector.create<std::unique_ptr<iview>().get())
);