Regarding the DI library, I read the introduction and the tutorial part,
and it does not help me understand what problem this library solves or why
I should use it. Is there some other way to explain what this library is
for?
Or maybe I need to switch my mindset to Java-like OO patterns a la GoF in
order to appreciate this?
Is there a lot of theory that I have to learn prior to be able to
understand what it is for?
The docs give this example:
auto injector = di::make_injector();
injector.create<app>();
Does this create a temporary object of type `app` that vanishes
momentarily? Or is there some magical object lifetime management going on
behind the scenes?
Also, it looks like it will only work if the leaves of this dependency tree
are default-constructible and if I am happy with calling default
constructors. But usually I am not: I need to pass arguments to my objects,
even if they are leaves in the dependency tree.
I would appreciate help in understanding the motivation.
Regards,
&rzej;
I was hoping that the explantation from https://boost-ext.github.io/di/boost
is good enough but I understand it might be confusing.
DI is a very simple concept, actually. The main point is to make the code
less coupled by injecting dependencies which is usually done by passing
them through constructors.
I'd hope that the following talk -
https://www.youtube.com/watch?v=yVogS4NbL6U is a good explanation of DI as
well. I'm happy to follow-up in any form, though.
> Or maybe I need to switch my mindset to Java-like OO patterns a la GoF in
> order to appreciate this?
>
Right, Dependency Injection is related to OO patterns and especially the
SOLI-D (Dependency Inversion) principle.
> Is there a lot of theory that I have to learn prior to be able to
> understand what it is for?
>
Not sure, OO, SOLID should be more than enough, IMHO.
>
> The docs give this example:
>
> auto injector = di::make_injector();
> injector.create<app>();
>
> Does this create a temporary object of type `app` that vanishes
> momentarily? Or is there some magical object lifetime management going on
> behind the scenes?
>
That will depend on the scope. By default, it's a singleton scope so the
app will have the same lifetime as the program.
Some useful information can be found:
- https://boost-ext.github.io/di/user_guide.html#scopes
- https://boost-ext.github.io/di/tutorial.html
> Also, it looks like it will only work if the leaves of this dependency tree
> are default-constructible and if I am happy with calling default
> constructors. But usually I am not: I need to pass arguments to my objects,
> even if they are leaves in the dependency tree.
>
> DI deduces constructor parameters automatically so it works with
non-default-constructible types as well.
DI is capable of creating the whole dependency tree.
Potential examples which can maybe help
-
https://github.com/boost-ext/di/blob/cpp14/example/tutorial/basic_create_objects_tree.cpp
- https://github.com/boost-ext/di/blob/cpp14/example/motivation.cpp
- https://github.com/boost-ext/di/tree/cpp14/example/polymorphism
> I would appreciate help in understanding the motivation.
>
I hope that the examples and explanation helps a bit, but if not I'm also
more than happy/available to have a follow-up (via zoom or similar) if that
helps (feedback could be used to improve the documentation)?
> On 19/02/2021 22:20, Krzysztof Jusiak via Boost wrote:
>
> > I hope that the examples and explanation helps a bit, but if not I'm also
I’ve looked at the examples in the documentation.
DI seems to allow me to build an arbitrary tuple of objects and pass that
tuple as the argument to a constructor. DI will then match off items in my
tuple to constructor arguments by interface type.
In that sense it seems to allow me to use an arbitrary “bag” of parameters
to build another object provided sufficient arguments have a corresponding
item in the bag (by interface type).
Since this matching is at compile time, what do I gain by this rather than
simply passing the arguments directly?
Can you show an example where use of DI allows me to express code more
clearly than a hand-written call?
I can think of uses of a dynamic (run time) bag of argument objects, but
not compile time.
Thanks in advance.
--
Richard Hodges
hodg...@gmail.com
office: +442032898513
home: +376841522
mobile: +376380212
_______________________________________________
> On 19/02/2021 22:20, Krzysztof Jusiak via Boost wrote:
>
> > I hope that the examples and explanation helps a bit, but if not I'm also
> > more than happy/available to have a follow-up (via zoom or similar) if
> that
> > helps (feedback could be used to improve the documentation)?
>
> Kris I speak from experience with Outcome that you're going to have to
> be much more hand holdy than that description you just gave. You'll need
> to explain things in small, baby steps, because DI is one of those
> things that nobody understands until the lightbulb switches on, and then
> they totally get it.
>
> Given that Mr. Dimov and several others have indicated they don't
> understand the motivation behind DI, if you want to see DI get into
> Boost, you're going to have to write a lot of personalised responses to
> people and keep repeating yourself to each individual in turn until
> people twig what DI is and why it's useful. I did this for Outcome, and
> whilst it was laborious at the time, I think it paid off in the end for
> everybody here.
>
Yes. My recollection of the adventure with Outcome is similar. I guess it
takes time and energy to explain one's idea to others. I imagine it is
frustrating too. One writes a library, makes an effort to share it with the
people, and the only response back is, "do more work, convince us".
So far, my understanding is the following. Please correct me, if I am
mischaracterizing the situation.
This library is not so much about Dependency Injection. Dependency
Injection is just a style of programming. The DI-library is rather about
managing the complexity that results from choosing to use Dependency
Injection.
Suppose I have a class like the following:
```
struct Point
{
int x;
int y;
};
struct Rect
{
Point lowerLeft;
Point upperRight;
};
class State
{
Rect area;
Point location;
// invariant: location is inside area;
public:
State(int left, int lo, int right, int hi, int x, int y) : area{{left,
lo}, {right, hi}}, location{x, y} {}
};
int main()
{
State s{0, 0, 2, 2, 1, 1};
}
```
I do not like that I have to pass so many parameters, that in this
configuration can be easily confused. 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?). I get the following result:
```
int main()
{
State s = di::make_injector().create<State>(0, 0, 2, 2, 1, 1);
}
```
And now I get back to the situation where I am passing six ints and can
easily confuse which int represents what.
I am pretty sure I am now unfairly mischaracterizing the library. But this
is what I get from the motivation and tutorial sections, and the
explanations I have seen so far. You cannot see this behavior in the
tutorial example that is using class `app`, because most of the types in
there are default-constructed or constructed from values that were
themselves (perhaps recursively) default-constructed. So it looks to me
that in order to appreciate this library, I have to make most of my types
default-constructible.
At which point am I missing something?
Regards,
&rzej;
Extremely helpful, thank you.
So in summary it’s a clever bag of arguments that are matched by type to
any constructor it’s applied to.
The motivating use case is to reduce the burden of maintaining the
dependencies of application objects.
Is that a succinct, if simplistic, summary?
--
Richard Hodges
hodg...@gmail.com
office: +442032898513
home: +376841522
mobile: +376380212
_______________________________________________
I like your Safe Numerics documentation. I am certainly not against
Boost-ext docs, but was just reflecting that the confusion about
understanding DI might be solved by a different documentation approach.
> Let me try to explain DI (manual and automatic) via example.
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;
> On 20/02/2021 22:15, Andrzej Krzemienski via Boost wrote:
Thanks Niall. Maybe this explains the bad reception of the DI-library. If
injecting dependencies is so natural to C++ and the library documentation
starts from convincing me that I do not know what it is and I am STUPID
(rather than SOLID), then this builds a confusion that makes it more
difficult to conume the rest.
I am having difficulties with mapping the library-interop offered by
Outcome onto the similar library-interop that would be gained from such a
hypothetical dependency injection library. The only way I can interpret
your words is a situation where one library creates a recipe for injecting
parameters to my class widget and another library uses this recipe to
actually create objects of my type:
```
// library one:
std::function<app> make_app = [] {
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};
return app {"main_app"_l, controller_, user_};
};
// library two:
void some_fun(std::function<app> make_app)
{
app app1 = make_app();
app1.run();
app app2 = make_app();
app2.run();
}
```
But did you have something like this in mind when you wrote the above
description? And if so, what is the value added by a dedicated library, if
one can achieve the same foal with std::function? Is the only motivation
that in some subset of cases the library can try to deduce (hopefully
correctly) from the number of arguments, how I wanted to initialize the
arguments?
It looks like a more adequate name for such a library is "factory", because
what it does is create objects.
Or maybe I am still missing something?
Regards,
&rzej;
Interesting. Maybe what Boost.DI library needs is a clear formulation of
the problem that it addresses.
Of course, it should be more specific than "Provide Dependency Injection
for C++".
Regards,
&rzej;
Talking about the introduction page, a few do/don't do are a bit odd
from my point of view:
* in the “Carrying dependencies” sample, public inheritance is replaced
by composition. Unless the code was badly broken in the first place (ie,
using public inheritance where it shouldn't have), it's very unlikely
that such a change can usually be applied. A possible fix would at least
be to switch to private inheritance in the initial (bad) code.
* in the “Carrying injector” sample, it is stated that “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”. I don't
know where that statement come from – and some code bases i've worked
with disagree with that statement. There's much to say about the service
locator pattern, though, and DI is probably a better alternative, but i
need to be convinced on solid bases.
* the “Manual DI - Wiring Mess” is the part i'd like to see expanded.
Typically, with the given example, i don't get a clue on how i replace
“view” with another implementation. Which, as far as i'm concerned, is
the strong point of DI. The mocks provider sample gives an answer, IMHO
it ought to be in the introduction.
Besides, the library looks indeed very interesting.
Regards,
Julien
Does the library also support binding to class *template* types? Sort of:
auto injector = di::make_injector(
di::bind_class_template<std::allocator>.to<my_allocator>()
);
Joaquín M López Muñoz