Boost Licence Version Build Status Build Status Coveralls Github Issues


Introduction

[Boost].DI
Your C++14 header only Dependency Injection library with no dependencies (Try it online!) GitHub

  Latest Release: v1.0.1 (May 4, 2016)

  Download        Changelog        Tutorial        Examples

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.

In short, DI is all about construction!

"Let's make some coffee!"

Coffee Maker

                      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 consturctor 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

SSingleton
TTight Coupling
UUntestability
PPremature Optimization
IIndescriptive Naming
DDuplication

vs

SSingle Responsibility
OOpen-close
LLiskov substitution
IInterface segregation
DDependency 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~~)

CPP(BTN)

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...

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

Why [Boost].DI?

Try it online!

CPP(BTN) CPP(BTN) CPP(BTN)



[Boost].DI design goals

Articles

Videos

Acknowledgements

  • Thanks to Bartosz Kalinczuk for code review and tips how to improve [Boost].DI
  • Thanks to Sohail Somani for support and tips how to improve [Boost].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