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?

8

3 Answers 3

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

1
  • 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 Commented Mar 30, 2014 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.

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.

7
  • 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 Commented Mar 26, 2014 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, consider posting it separately. Commented Mar 26, 2014 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 Commented Mar 26, 2014 at 19:53
  • 1
    I posted an additional question: programmers.stackexchange.com/questions/233828/… Commented Mar 26, 2014 at 20:05
  • 1
    "Software frameworks, callbacks, schedulers, event loops and dependency injection are examples of design patterns that follow the inversion of control principle..." (Wikipedia) Commented Jan 22, 2015 at 15:13

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.