How to avoid Dependency Injection constructor madness?

83,878

Solution 1

You are right that if you use the container as a Service Locator, it's more or less a glorified static factory. For lots of reasons I consider this an anti-pattern (also see this excerpt from my book).

One of the wonderful benefits of Constructor Injection is that it makes violations of the Single Responsibility Principle glaringly obvious.

When that happens, it's time to refactor to Facade Services. In short, create a new, more coarse-grained interface that hides the interaction between some or all of the fine-grained dependencies you currently require.

Solution 2

I don't think your class constructors should have a reference to your IOC container period. This represents an unnecessary dependency between your class and the container (the type of dependency IOC is trying to avoid!).

Solution 3

The difficulty of passing in the parameters is not the problem. The problem is that your class is doing too much, and should be broken down more.

Dependency Injection can act as an early warning for classes getting too big, specifically because of the increasing pain of passing in all of the dependencies.

Solution 4

I came across a similar question about constructor based dependency Injection and how complex it was getting to pass in all the dependencies.

One of the approach, I have used in past is to use the application facade pattern using a service layer. This would have a coarse API. If this service depends on repositories, It would use a setter injection of the private properties. This requires creating an abstract factory and moving the logic of creating the repositories into a factory.

Detailed code with explanation can be found here

Best practices for IoC in complex service layer

Solution 5

Injecting the container is a shortcut that you will ultimately regret.

Over injection is not the problem, it is usually a symptom of other structural flaws, most notably separation of concerns. This is not one problem but can have many sources and what makes this so difficult to fix is that you are going to have to deal with all of them, sometimes at the same time (think of untangling spaghetti).

Here is an incomplete list of the things to look out for

Poor Domain Design (Aggregate root’s …. etc)

Poor separation of concerns (Service composition, Commands, queries) See CQRS and Event Sourcing.

OR Mappers (be careful, these things can lead you into trouble)

View Models and other DTO’s (Never reuse one, and try to keep them to a minimal !!!!)

Share:
83,878
JP Richardson
Author by

JP Richardson

https://www.exodus.com - all-in-one app to secure, manage and exchange blockchain assets like Bitcoin and Ethereum.

Updated on July 08, 2022

Comments

  • JP Richardson
    JP Richardson almost 2 years

    I find that my constructors are starting to look like this:

    public MyClass(Container con, SomeClass1 obj1, SomeClass2, obj2.... )
    

    with ever increasing parameter list. Since "Container" is my dependency injection container, why can't I just do this:

    public MyClass(Container con)
    

    for every class? What are the downsides? If I do this, it feels like I'm using a glorified static. Please share your thoughts on IoC and Dependency Injection madness.

  • Ryan Emerle
    Ryan Emerle about 14 years
    +1 for quantifying the refactoring effort into a single concept; awesome :)
  • irreputable
    irreputable about 14 years
    for real? you just created an indirection to move those parameters into another class, but they are still there! just more complicated to deal with them.
  • Mark Seemann
    Mark Seemann about 14 years
    @irreputable: In the degenerate case where we move all dependencies into an Aggregate Service I agree that it's just another level of indirection that carries no benefit, so my choice of words were slightly off. However, the point is that we move only some of the fine-grained dependencies into an Aggregate Service. This limits the number of dependency permutations both in the new Aggregate Service and for the dependencies left behind. This makes both simpler to deal with.
  • Igor Popov
    Igor Popov almost 14 years
    The best remark: "One of the wonderful benefits of Constructor Injection is that it makes violations of the Single Responsibility Principle glaringly obvious."
  • L-Four
    L-Four about 11 years
    The reason for constructor injection is to make dependencies obvious, not because it looks more natural for java developers.
  • Frederik Prijck
    Frederik Prijck about 10 years
    Late comment, but this answer made me laugh :)
  • Tseng
    Tseng almost 10 years
    +1 Depending on a IoC container makes it hard to change that container later on w/o changing bunch of code in all other classes
  • Andrew
    Andrew almost 9 years
    Correct me if I am wrong, but at some point you have to 'glue everything together', and thus you have to get more than a few dependencies for that. For example in the View layer, when building templates and data for them, you have to grab all the data from various dependencies (e.g. 'services') and then put all this data into template and to the screen. If my web page has 10 different 'blocks' of information, so I need 10 different classes to provide me with that data. So I need 10 dependencies into my View/Template class?
  • Piotr Kula
    Piotr Kula about 7 years
    +1 for setter based injections. If I have services and repositories defined in my class its pretty damn obvious they are dependencies.. I don't need to write massive VB6 looking constructors, and do stupid assigning code in the constructor. Its pretty obvious what the dependencies are on the fields required.
  • Don Box
    Don Box about 7 years
    Would it be correct to say that another benefit of constructor injection is it technically forces you to solve any cyclic dependency? (otherwise you get a an infinite recursion creating objects) Because cyclic dependencies is code smell, it's something you should never have. Is this reasoning correct?
  • Mark Seemann
    Mark Seemann about 7 years
    @DonBox Not necessarily. Tree structures are typically defined with recursion. As long as you have a way to express leaf nodes as empty objects, you can still have recursion.
  • Don Box
    Don Box about 7 years
    @MarkSeemann Here's an example of what I mean: softwareengineering.stackexchange.com/questions/348221/… Is this a good example?
  • Mark Seemann
    Mark Seemann about 7 years
    @DonBox In that case you can write null object implementations to stop the recursion. Not what you need, but the point is that Constructor Injection doesn't prevent cycles - it only makes it clear that they're there.
  • J Hunt
    J Hunt almost 7 years
    How would you implement IOC without having interface parameters on the constructors? Am I reading your post wrong?
  • John Doe
    John Doe over 4 years
    As per 2018, Spring officially recommends not using setter injection except for dependencies that have a reasonable default value. As in, if the dependency is mandatory to the class, constructor injection is recommended. See discussion on setter vs ctor DI
  • John Doe
    John Doe over 4 years
    @J Hunt I do not understand your comment. To me, interface parameters mean parameters that are interfaces for the dependencies, ie, if your dependency injection container initializes MyClass myClass = new MyClass(IDependency1 interface1, IDependency2 interface2) (interface parameters). This is unrelated to @derivation's post, which I interpret as saying a dependency injection container should not inject itself into its objects, ie, MyClass myClass = new MyClass(this)
  • Golden Lion
    Golden Lion almost 4 years
    The resolver will dependency inject the datacontext and interfaces in the constructor.
  • Mike
    Mike over 2 years
    I don't see how in some cases you can avoid passing the IoC container. Example (probably the only valid one): a factory. cs class MyTypeFactory { private readonly IServiceProvier mServices; public MyTypeFactory(IServiceProvier services) => mServices = services; MyType Create(int param) => ActivatorUtilities.CreateInstance<MyType>(mServices, param); }
  • mr5
    mr5 about 2 years
    How does this answer the question?
  • Shai Cohen
    Shai Cohen almost 2 years
    @Mike - ActivatorUtilities is how you can avoid passing the container. see the answer here: stackoverflow.com/questions/72465953/…
  • Mike
    Mike almost 2 years
    That is in my example. And to use ActivatorUtilities, one needs to pass on the container.