6

Most of my production code has fixed types of dependencies, illustrated below: the House for example, at run time, always depends on the same Kitchen.

Therefore, I am wondering what I gain by having the House force whoever constructs it to inject its dependencies as opposed to provide the option to inject the dependencies, i.e. provide both a default constructor and a DI-constructor?

What you lose by not allowing default construction is easily seen if you compare mainDI and mainDefault. Even if each class has only has two dependencies, if the dependency hierarchy depth is 3, as for example in the House -> Bedroom -> Bed path, you have to instantiate 2^3 objects in your top level class which seems unfeasible for large projects with a dependency hierarchy depth of 10 or more.

Having the DI constructor, I am still able to unit test a class.

So for all the DI experts out there, what is the downside of this approach?

struct House {
  House(Bedroom ib, Kitchen ik) : b(ib), k(ik) {}
  House() : b(Bedroom()), k(Kitchen()) {}
  private:
    Bedroom b;
    Kitchen k;
}

struct Bedroom {
  Bedroom(Bed ib, Lamp il) : b(ib), l(il) {}
  Bedroom() : b(Bed()), l(Lamp()) {}
  private:
    Bed b;
    Lamp l;
}

struct Bed {
  Bed(Frame if, Mattress im) : f(if), m(im) {}
  Bed() : f(Frame()), m(Mattress()) {}
  private:
    Frame f;
    Mattress m;

}


mainDefault() {
  House h()
}


mainDI() {
    Frame f;
    Mattress m;
    Bed b;
    Lamp l;
    Kitchen k;

    Bed b(f, m);
    Bedroom br(b, l);    
    House h(br, k);        
}
0

3 Answers 3

5

Even if each class has only has two dependencies, if the dependency hierarchy depth is 3, as for example in the House -> Bedroom -> Bed path, you have to instantiate 2^3 objects in your top level class which seems unfeasible for large projects with a dependency hierarchy depth of 10 or more.

Make no mistake, you're still instantiating those objects somewhere.

Therefore, I am wondering what I gain by having the House force whoever constructs it to inject its dependencies as opposed to provide the option to inject the dependencies, i.e. provide both a default constructor and a DI-constructor?

You gain visibility. If your main class really is the thing that decides what is instantiated deep into your hierarchy, that's a smell. A huge smell depending on the depth of the hierarchy, since your classes are leaking implementation details all the way down.

By hiding that behind a default constructor (or worse, and IoC container), you're hiding all of that coupling. You should instead work to remove that coupling where it makes sense. You'll get simplified constructors and all of the other benefits of decoupled code.

13
  • Do you mean "(or worse, a ServiceLocator)"?
    – MetaFight
    Commented Mar 18, 2017 at 18:09
  • 1
    @MetaFight - that is also worse, but not what I meant.
    – Telastyn
    Commented Mar 18, 2017 at 18:31
  • 1
    I'm not sure I follow, then. What's wrong with asking your IoC container to construct an object (and resolve its dependency tree) for you?
    – MetaFight
    Commented Mar 18, 2017 at 18:53
  • 2
    @metafight - it hides dependencies, hides coupling. And these days, abundantly overused. There are times it's useful, but it hides things worse that constructors that just make things for you.
    – Telastyn
    Commented Mar 18, 2017 at 18:57
  • Are we talking about the same thing? I'm not talking about parameterless constructors. I mean using an IoC container with DI constructors that make dependencies explicit. If that is an anti-pattern, what is an alternative?
    – MetaFight
    Commented Mar 18, 2017 at 18:59
2

When using Dependency Injection you are not supposed to pass an exponential number of arguments through the constructor. Instead you have an Assembler class (similar to a Factory) that can be configured to build objects for you from any configured class. This should leave your class code clean, without an exponential number of parameters but also give you flexibility to change the implementation easily.

The problem with your approach is that you will have to pass an exponential number of arguments when you test integration.

Martin Fowler has a detailed explanation on how to use DI: https://martinfowler.com/articles/injection.html#FormsOfDependencyInjection

4
  • 4
    I think you may be falling into the trap of assuming Dependency Injection can only be achieved using an IoC container. This is false. The inaptly named "Poorman's DI" is a perfectly acceptable approach to DI in many scenarios.
    – MetaFight
    Commented Mar 18, 2017 at 17:51
  • 1
    Yes, but the question specifically complains about the number of arguments that are needed to be passed down through different level of the hierarchy. If you don't want to do that you need to create the dependent objects in your class by either directly constructing them or delegating this work to some type of Factory (IoC container, ServiceLocator, etc.). Commented Mar 18, 2017 at 18:19
  • @MetaFight I am also confused, how with the Poorman's DI you can get around the exponential blowup of things to instantiate in your Integration Test/Main class
    – user695652
    Commented Mar 18, 2017 at 20:28
  • @user695652 it doesn't solve that problem. I was simply challenging the statement Instead you have an Assembler class (similar to a Factory) that can be configured to build objects for you.... I was trying to highlight that even that isn't always necessary. For simple applications Poorman's DI is a valid alternative. In your case, however, it probably isn't.
    – MetaFight
    Commented Mar 18, 2017 at 20:39
0

It is ok for small solutions with one project/library.
In this case your Home class responsible for building objects it depends on.

For larger solutions, where Home, Bedroom and Bed classes are in different libraries, this approach will violate "Inversion of Control" principle. Because your higher level types will depend on lower level types.
So when you makes internal change in Bed class, you will need to recompile whole solution.

Instead if higher level classes depend on abstractions, then you can work with lower level classes without recompiling whole system.

In this case you will need to create one entry point project with Main method. This will be only project which will know about all dependencies and build whole object graph for your application.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.