9

I am playing with dependency injection, but i am not sure I am doing it right. Especially, I am not sure what should be the correct way to build classes with injected dependencies.

Say I have a class A that creates class B. Class B depends on class C and class C depends on class D. Who should be responsible for creating class D?

  1. It could be class A. However, in a large system, class A might end up creating and assembling a very large number of objects.

  2. A separate builder class that will create D, C and B. A will use this builder class.

  3. Some other option.

In addition, I read a lot about DI containers. However, it seems that there are no major frameworks for C++. Also, if I understand correctly, DI can be performed well even without containers. Am I correct?

gnat
  • 21,442
  • 29
  • 112
  • 288
Erik Sapir
  • 231
  • 1
  • 3
  • 5

3 Answers3

6

Say I have a class A that creates class B. Class B depends on class C and class C depends on class D. Who should be responsible for creating class D?

You're jumping steps. Consider a set of conventions optimized for loose coupling and exception safety. The rules go like this:

  • R1: if A contains a B, then the constructor of A receives a fully constructed B (i.e. not "B's construction dependencies"). Similarly, if B's construction requires a C, it will receive a C, not C's dependencies.

  • R2: if a full chain of objects are required to construct an object, the chained construction is extracted/automated within a factory (function or class).

Code (std::move calls omitted for simplicity):

struct D { int dummy; };
struct C { D d; };
struct B { C c; }

struct A { B make_b(C c) {return B{c}; };

In such a system, "who creates D" is irrelevant, because when you call make_b, you need a C, not a D.

Client code:

A a; // factory instance

// construct a B instance:
D d;
C c {d};
B = a.make_b(c);

Here, D is created by the client code. Naturally, if this code is repeated more than once, you are free to extract it into a function (see R2 above):

B make_b_from_d(D& d) // you should probably inject A instance here as well
{
    C c {d};
    A a;
    return a.make_b(c);
}

There is a natural tendency to skip the definition of make_b (ignore R1), and write the code directly like this:

struct D { int dummy; };
struct C { D d; };
struct B { C c; }

struct A { B make_b(D d) { C c; return B{c}; }; // make B from D directly

In this case, you have the following problems:

  • you have monolithic code; If you come to a situation in client code where you need to make a B from an existent C, you cannot use make_b. You will either need to write a new factory, or the definition of make_b, and all the client code using the old make_b.

  • Your view of dependencies is muddled when you look at the source: Now, by looking at the source you get to think that you need a D instance, when in fact you may just need a C.

Example:

void sub_optimal_solution(C& existent_c) {
    // you cannot create a B here using existent_C, because your A::make_b
    // takes a D parameter; B's construction doesn't actually need a D
    // but you cannot see that at all if you just have:
    // struct A { B make_b(D d); };
}
  • The omission of struct A { B make_b(C c); } will greatly increase coupling: now A needs to know the definitions of both B and C (instead of just C). You also have restrictions on any client code using A, B, C and D, imposed on your project because you skipped a step in the definition of a factory method (R1).

TLDR: In short, do not pass the outermost dependency to a factory, but the closest ones. This makes your code robust, easily alterable, and renders the question you posed ("who creates D") into an irrelevant question for the implementation of make_b (because make_b no longer receives a D but a more immediate dependency - C - and this is injected as a parameter of make_b).

utnapistim
  • 5,285
  • 16
  • 25
  • 2
    The question of should should create D still remains. I am still trying to understand which part of the system is responsible to construct D – Erik Sapir Mar 30 '14 at 10:41
4

There is a DI framework for C++ (still under development AFAIK): Boost.DI.

There are some useful comments about the framework on reddit.

Nemanja Trifunovic
  • 6,815
  • 1
  • 26
  • 34
0

Who should be responsible for creating class D?

Every time the question like that pops up, it indicates a dependency to inject. The whole idea is to "delegate" responsibility outside of the object that seems to be having a problem figuring how to handle it.

Using your example, since class A doesn't seem to possess sufficient "knowledge" to figure how to create D, you invert the control and expose this as a dependency needed for class A, by making it require a factory that knows how to create instances of class D.

gnat
  • 21,442
  • 29
  • 112
  • 288
  • It makes sense, but at the end i will get to the first class in the system (Main) which can't get any dependencies. It seems that this class would have to initialize a bunch of factories/objets – Erik Sapir Mar 26 '14 at 19:44
  • Looks like the question about how to handle responsibility for creation of class D is solved isn't it? As for how things "wire" at the first class in the system (Main), that would be a **[different question](http://meta.stackexchange.com/questions/43478/exit-strategies-for-chameleon-questions "at Stack Exchange sites, “chameleon questions” are not quite welcome")**, consider posting it separately. – gnat Mar 26 '14 at 19:48
  • Actually that is not exactly a different question - the question was on who is responsible to perform all the initializations (new operations). I am not sure it deserves another questions. In any case, i would really love to here an answer to this question – Erik Sapir Mar 26 '14 at 19:53
  • @ErikSapir DI is one of my favorite tricks, I use it a lot, and per my experience "root" initialization design deserves a separate question, or maybe it's even [too broad](http://meta.programmers.stackexchange.com/questions/6483/why-was-my-question-closed-or-down-voted/6490#6490) to be generally addressed in a single question – gnat Mar 26 '14 at 19:56
  • 1
    I posted an additional question: http://programmers.stackexchange.com/questions/233828/initialization-of-objects-in-a-system-using-dependency-injection – Erik Sapir Mar 26 '14 at 20:05
  • -1 Inversion of control has got nothing to do with dependency injection. Two are completely different things. IoC container allows you to commit to certain implementation during runtime. From the article you quoted: "dependent object is coupled to the object it needs at run time" - runtime is a keyword here. – CodeART Mar 27 '14 at 23:29
  • 1
    "Software frameworks, callbacks, schedulers, event loops and *dependency injection* are examples of design patterns that follow the *inversion of control* principle..." ([Wikipedia](http://en.wikipedia.org/wiki/Inversion_of_control)) – gnat Jan 22 '15 at 15:13