Boost logo

Boost :

From: Krzysztof Jusiak (krzysztof_at_[hidden])
Date: 2021-02-20 23:14:45


Let me try to explain DI (manual and automatic) via example.

DI vs NO-DI is all about less coupling by injecting dependencies instead of.
It's often referred as a Hollywood prince - Don't call us we will call you!

Example

struct coupled_no_di {
  void api() {
    d.call();
  }
  depedency d{singleton::get()};
};

- Coupled design (hard to test)

struct not_coupled_di {
  not_coupled_di(dependency& d) : d_{d} {}

  void api() {
    d.call();
  }
  depedency& d;
};

- Much better - separation of business logic and object creation.

But we can do even better by applying
- Dependency Inversion (relay on abstractions and not concrete
implementations)
- Abstractions can be done in many different ways (type erasure, templates,
abstract classes, etc.)

template<DependencyConcept TDependency>
struct not_coupled_di {
  not_coupled_di(TDependency& d) : d_{d} {}

  void api() {
    d.call();
  }
  TDependency& d;
};

The above is much better because
- It's not coupled to any specific implementation
- Can be changed for testing (mocks/fakes/stubs)

Okay, now let's take at the main. A good design will apply a Composition
Root (unique place in the app when dependencies are being created)

int main() {
  my_depedency dependency{...};
  not_coupled_di di{dependency};

  di.api();
}

- The above is an example of manual DI which is cool already but may lead
to what is called a Wiring Mess.

Let's imagine that we have a big dependency tree because we are following
SOLID principle and we especially apply the Single Responsibility Principle
(we will have a lot of dependencies).

int main() {
  my_dependency_1 d1{};
  my_dependency_2 d2{};
  my_dependency_3 d2{d1, d2};
  my_dependency_4 d3{d1, 42, d3};
  app a{d3, ...};
  ...

  // Order of the above is important and with bigger projects might be
easily 10k LOC+
}

- Well, we can maintain the above if we want, but Automatic DI will let us
actually focus on the business logic instead!
- Any change in the constructors (a reference to shared_pointer, the order
of parameters) will require us to change the above :(

Boost.DI library can help with removing the Wiring Mess for us, though!

int main() {
  auto injector = make_injector();
  app = create<app>(injector);
  return app.api();
}

Right now the benefits of using the framework instead of manual DI
- If we change any constructor parameter or order of any dependency we
DON't have to change anything with DI framework

- Before
  not_coupled_di(TDependency& d);

- Refactor
  not_coupled_di(std::shared_ptr<TDependency>);

With manual DI the wiring has to be changed to pass the shared_ptr with
Boost.DI we don't have to change the wiring code at all.

Okay, but what about the polymorphism behaviour.
Boost.DI allows a different type of polymorphism. One can inject templates,
abstract classes, variant, type erasure etc.
More can be found here -
https://github.com/boost-ext/di/tree/cpp14/example/polymorphism.

Why that's important? Because it's a better design and makes testing easier
too.
How to do it with Boost.DI?

// configuration
auto module = make_injector(
  bind<interface>.to<implementation> // I'm not going to go into details,
but Boost.DI allows to inject TEMPLATES, abstract classes, etc...
);

// production, release
int main() {
  app = create<app>(module);
  return app.api();
}

// end 2 end testing
int main() {
  auto injector = di::make_injector(
    module(), // production wiring
    di::bind<interface>.to<mock> [override]
  );
  app = create<app>(module);
  return app.api();
}

- Great, we can test end 2 end with overriding some dependencies such as
time, database, networking, logging etc...
- We have a loosely coupled design!
- We don't have to change ANYTHING in the wiring code when we change the
dependency tree and/or any constructor parameters/order

I hope that helps a bit?

I also really encourage to take a quick look at which summaries concepts
behind DI.
- https://www.youtube.com/watch?v=yVogS4NbL6U

Thanks, Kris

On Sat, Feb 20, 2021 at 4:10 PM Peter Dimov via Boost <boost_at_[hidden]>
wrote:

> Andrzej Krzemienski wrote:
> ...
> > So, I want to apply the Dependency Injection philosophy. I get:
> >
> > class State
> > {
> > Rect area;
> > Point location;
> > // invariant: location is inside area;
> > public:
> > State(Rect a, Point loc) : area{a}, location{loc} {}
> > };
> >
> > int main()
> > {
> > State s{Rect{{0, 0}, {2, 2}}, Point{1, 1}};
> > }
> > ```
> >
> > Now, I choose to use the DI-library (this is where I have troubles with
> > understanding: why would I want to do that?).
>
> You are thinking in C++ (value semantics) but you need to be thinking in
> Java (reference semantics, OOP, object DAGs.) Suppose you need to have a
> logger object
>
> shared_ptr<ILogger> pl; // new FileLogger( log_file_name fn );
>
> and a database connector object (which uses a logger to log things)
>
> shared_ptr<IDbConn> pdb; // new MysqlDb( mysql_db_info mdi,
> shared_ptr<ILogger> pl )
>
> and an abstract configuration query interface, that can use an .ini file,
> or
> a JSON file, or a database
>
> shared_ptr<IConfig> pc; // new DbConfig( shared_ptr<IDbConn> pdb,
> shared_ptr<ILogger> pl );
>
> Now when you need to create the config query object, you need to give it a
> database connector and a logger, and you also need to give the same logger
> to the database connector. So with [NotYetBoost].DI you'll write something
> like
>
> auto injector = make_injector(
> bind<log_file_name>.to( "something.log" ),
> bind<ILogger>.to<FileLogger>(),
> bind<>.to( mysql_db_info{ ... } ),
> bind<IDbConn>.to<MysqlDb>(),
> bind<IConfig>.to<DbConfig>()
> );
>
> auto cfg = injector.create<IConfig>();
>
> although I don't really know if this will compile or work. :-)
>
> Now imagine that same thing but with 10 objects, or 20 objects. Also
> imagine
> that your day to day vocabulary includes "business logic", "enterprise",
> "UML" and "XML".
>
>
> _______________________________________________
> Unsubscribe & other changes:
> http://lists.boost.org/mailman/listinfo.cgi/boost
>


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk