Introduction
[Boost::ext].DI | |
---|---|
Your C++14 header only Dependency Injection library with no dependencies (Try it online!) | GitHub |
What is Dependency Injection?
"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;
| };
Do I use a Dependency Injection already?
- If you are using constructors in your code then you are probably using some form of Dependency Injection too!
class Button {
public:
Button(const std::string& name, Position position); // Dependency Injection!
};
Do I use Dependency Injection correctly?
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}};
...
};
Do I need a Dependency Injection?
- 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)
STUPID vs SOLID - "Clean Code" Uncle Bob
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 |
Do I need a DI Framework/Library?
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!
Manual DI - Wiring Mess (Avoid it by using [Boost].DI)
* 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...
- Testing (See Mocks Provider)
- Serializing (See Serialize)
- Understand code dependencies (See UML Dumper)
- Restrict what types and how they should be created (See Constructible Policy)
Real Life examples?
- Match-3 Game
- Simple web game in C++14 using SDL2 / Model View Controller / Meta State Machine / Dependency Injection / Range-V3 / Emscripten
- Automatic Mocks Injector
- Automatically create and inject required mocks to tested classes via constructors
- Experimental Boost.SML
- C++14 header only Meta State Machine library with no dependencies
Why [Boost].DI?
- [Boost].DI has none run-time overhead (See Performance)
- [Boost].DI compiles fast / Faster than Java-Dagger2! (See Benchmarks)
- [Boost].DI gives short diagnostic messages (See Error messages)
- [Boost].DI is non-intrusive (See Injections)
- [Boost].DI reduces boilerplate code (See Create Objects Tree)
- [Boost].DI reduces testing effort (See Mocks Provider)
- [Boost].DI gives better control of what and how is created (See Constructible Policy)
- [Boost].DI gives better understanding about objects hierarchy (See UML Dumper)
[Boost].DI design goals
- Be as fast as possible (See Performance)
- Compile as fast as possible (See Benchmarks)
- Give short and intuitive error messages (See Error messages)
- Guarantee object creation at compile-time (See Create Objects Tree)
- Be as non-intrusive as possible (See Injections)
- Be easy to extend (See Extensions)
Articles
Videos
[Boost::ext].DI
- C++Now 2019: Dependency Injection - a 25-dollar term for a 5-cent concept | Slides
- CppCon 2018: [Boost].DI - Inject all the things! | Slides
- C++Now 2017: Concepts driven design with Dependency Injection | Slides
- C++Now 2016: C++14 Dependency Injection Library | Slides
- Meeting C++ 2016: TDD/BDD and Dependency Injection | Slides
- Boost your design with C++14 dependency injection | Slides
Dependency Injection In General
- Dependency Injection
- The Clean Code Talks - Don't Look For Things!
- A New Type of dependency injection
- The Future of Dependency Injection with Dagger 2
- Design Patterns in C++: Creational
Acknowledgements
- Thanks to Bartosz Kalinczuk for code review and tips how to improve
[Boost::ext].DI
- Thanks to Kanstantsin Chernik for all his contributions to
[Boost::ext].DI
- Thanks to Olof Edlund for very useful feedback and for all the improvements to the documentation
- Thanks to Rob Stewart and Robert Ramey for documentation feedback and tips how to improve it
- Thanks to Sohail Somani for support and tips how to improve
[Boost::ext].DI