Boost logo

Boost :

From: Andrzej Krzemienski (akrzemi1_at_[hidden])
Date: 2021-02-22 07:44:25


niedz., 21 lut 2021 o 00:27 Krzysztof Jusiak via Boost <
boost_at_[hidden]> napisał(a):

> 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 :(
>

Can you demonstrate how this library works with types that are not
default-constructible? E.g., the tutorial example has this code:

```
int main() {
  logger logger_;
  renderer renderer_;
  view view_{renderer_, logger_};
  model model_{logger_};
  controller controller_{model_, view_, logger_};
  user user_{logger_};
  app app_{controller_, user_};
}
```

Suppose I want all my objects to have labels so that I can easily identify
them. I introduce a type label, which actually only stores a std:string,
following the advice from the tutorial, but it serves only one purpose: to
designate labels:

```
class label
{
  std::string _s;
public:
  label() = delete; // no empty labels
  label(std::string_view s);
};
label operator ""_l(const char * s, size_t l ) { return
label(std::string_view(s, l); } // to use "label"_l
```

Now, suppose that all the classes in the example -- logger, renderer, view,
model, controller, user, app -- only expose constructors that take such a
label as the first argument. Additionally, suppose that classes model and
controller need a distinct logger. So when I manually initialize this herd,
I do it like this:

```
int main() {
  logger log1{"app.logger.lo"_l};
  logger log2{"app.logger.hi"_l};
  renderer renderer_{"main_renderer"_l};
  view view_{"main_view"_l, renderer_, log1};
  model model_{"main_model"_l, log2}; // note: the other logger
  controller controller_{"main_controller"_l, model_, view_, log2}; //
note: the other logger
  user user_{"main_user"_l, log1};
  app app_{"main_app"_l, controller_, user_};
}
```
How is it going to look like when replaced with the proposed DI-library?

Regards,
&rzej;

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