[Boost::ext].DI | |
---|---|
Your C++14 header only Dependency Injection library with no dependencies (Try it online!) | GitHub |
"Don't call us, we'll call you", Hollywood principle
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. It is like the Strategy Pattern, except the strategy is set once, at construction. DI enables loosely coupled designs, which are easier to maintain and test.
"Let's make some coffee!"
No Dependency injection | Dependency Injection
----------------------------------------|-------------------------------------------
class coffee_maker { | class coffee_maker {
public: | public:
void brew() { | coffee_maker(const shared_ptr<iheater>& heater
heater->on(); | , unique_ptr<ipump> pump)
pump->pump(); | : heater(heater), pump(move(pump))
clog << "coffee"! << endl; | { }
heater->off(); |
} | void brew() {
| heater->on();
private: | pump->pump();
shared_ptr<iheater> heater = | clog << "coffee!" << endl;
make_shared<electric_heater>(); | heater->off();
| }
unique_ptr<ipump> pump = |
make_unique<heat_pump>(heater); | private:
}; | shared_ptr<iheater> heater;
| unique_ptr<ipump> pump;
| };
class Button {
public:
Button(const std::string& name, Position position); // Dependency Injection!
};
Common mistakes when using Dependency Injection are:
Passing a dependency to create another dependency inside your object
It's a bad practice to pass dependencies to an object just in order to create another one with those dependencies. It's much cleaner to create the latter object beforehand and pass it to the former.
class Model {
public:
Model(int width, int height)
: board(std::make_unique<Board>(width, height)) // Bad
{ }
explicit Model(std::unique_ptr<IBoard> board) // Better
: board(std::move(board))
{ }
...
private:
std::unique_ptr<IBoard> board;
};
Carrying dependencies
It's also important NOT to pass depenencies through layers of constructors (carrying them). It's much better to always pass only dependecies which are required ONLY by the given constructor.
class Model : public Service { // Bad
public:
explicit Model(std::unique_ptr<IBoard> board) // Bad
: Service(std::move(board))
{ }
void update() {
Service::do_something_with_board(); // Bad
}
};
class Model { // Better
public:
explicit Model(std::unique_ptr<Service> service) // Better
: service(std::move(service))
{ }
void update() {
service->do_something_with_board(); // Better
}
private:
std::unique_ptr<Service> service;
};
Carrying injector (Service Locator pattern)
Service locator is consider to be an anti-pattern because its instance is required to be passed as the ONLY constructor parameter into all constructors. Such approach makes the code highly coupled to the Service Locator framework. It's better to pass required dependencies direclty instead and use a DI framework to inject them.
class Model {
public:
explicit Model(service_locator& sl) // Bad (ask)
: service(sl.resolve<unique_ptr<Service>>())
{ }
explicit Model(std::unique_ptr<Service> service) // Better (tell)
: service(std::move(service))
{ }
...
private:
std::unique_ptr<Service> service;
};
Not using strong typedefs for constructor parameters
Being explicit and declarative is always better than being impilicit.
Using common types (ex. numbers) in order to define any common-like type may cause
missusage of the constructor interface. Using strong typedefs
is easier to follow and
protects against missusage of the constructor interface.
class Board {
public:
Board(int /*width*/, int /*height*/) // Bad; Board{2, 3} vs Board{3, 2}?
Board(width, height) // Better, explicit; Board{width{2}, height{3}};
...
};
S | Singleton |
T | Tight Coupling |
U | Untestability |
P | Premature Optimization |
I | Indescriptive Naming |
D | Duplication |
vs
S | Single Responsibility |
O | Open-close |
L | Liskov substitution |
I | Interface segregation |
D | Dependency inversion |
Depending on a project and its scale you may put up with or without a DI library, however, in any project a DI framework may free you from maintaining a following (boilerplate) code...
logger logger_;
renderer renderer_;
view view_{renderer_, logger_};
model model_{logger_};
controller controller_{model_, view_, logger_};
user user_{logger_};
app app_{controller_, user_};
Notice that ORDER in which above dependencies are created is IMPORTANT as well as that ANY change in ANY of the objects constructor will REQUIRE a change in this code!
* Single Responsibility Principle
=>
* A lot of classes
=>
* Wiring Mess
=>
* Hard to maintain + Lazy programmers (99%)
=>
* Hacks/Workarounds (~~Single Responsibility~~)
Right now, imagine a project with hundreds or thousands of those dependencies and a critical issue which has to be fixed ASAP. Unfortunately, in order to fix the bug properly a new non-trivial dependency has to be introduced.
Now, imagine that a 'smart' dev figured out that it will be much easier to extend the functionally of already passed object and sneak a workaround/'solution' this way. Such approach will possibly break the single responsibility principle of the changed object but no worries though, it might be refactored later on (meaning: most likely, the workaround will stay unchanged forever and that there are no tests).
If that sounds familiar, take a look into DI library as it helps to solve developer dilemma by taking care of creating all required dependencies whereas dev may focus on fixing and testing the issue.
DI library, not only let you forget about maintaining dependencies creation (See Create Objects Tree), but also can help you with...
[Boost::ext].DI
[Boost::ext].DI
[Boost::ext].DI