When it comes to DI (as with many things in software) people tend to have one of two very strong opinions: love it or hate it.
Full disclosure: I love it, but I’ve done my best to examine the topic from both sides. Since I am in the “love it” camp, when I started researching Dependency Injection I knew that confirmation bias would probably lead me to arguments for it and little else. I was surprised at how easy it was to find heated arguments lengthy conversations going back and forth about the merits and drawbacks, there is a lot of good discussion online. So I feel safe in saying the advantages are not as obvious and clear-cut as the pro-DI camp would like to believe.
I thought some good arguments against DI were
- Introduces complexity
- A dependency is an implementation detail, specifying it externally breaks encapsulation
- Some members should be final but you can’t inject final attributes.
- Rarely change dependency configuration anyway
- Ties you to a framework
- Uses XML to configure your application
Let’s look at each of these in turn.
It’s true that DI brings complexity, there are plenty of things that are more complex with DI than without. You need to teach the concept to new developers. You need to bring in yet another framework to your code and learn how to use it. Some parts of your application may require more code as constructor argument lengths and/or number of setter methods increase. When the framework is wiring things behind the scenes, it may be harder to see what was instantiated when and why.
There is definitely the possibility of breaking encapsulation. The members of a class constitute its implementation, and listing its members in a constructor list is just advertising through the API what the implementation is and by extension how this class works. If you use an empty constructor instead, the user of a class does not know (indeed can not know) what is going on underneath the class, which in general is what we want.
When designing a class, sometimes it is appropriate to declare some members as final. But DI puts some constraints on this part of your class design. Once a final attribute is assigned it cannot be reassigned, and it cannot be assigned after the constructor returns, so you cannot use setter injection for final members. If you feel like DI is forcing you to not use final members so you can use setter injection, you will not be happy with that.
DI advertises that it allows you to configure you object graph at runtime rather than compile time. However, in many real world applications we rarely need to change the dependency configuration. There are some cases (plugins, for example) where implementations need to be swapped at runtime, but those are not that common. If you do need to change a dependency configuration, generally you just change it and recompile your code.
The definition of DI that I’m using for this blog series is using the design pattern along with a DI container to structure an entire application. With that definition, yes there is another framework you need to bring in to your application and learn how to use. Besides increasing the overhead of learning your application, using more libraries and frameworks increases your chances of dependency hell.
Finally, some DI containers use XML files to wire applications together. Using XML files to describe your application has some disadvantages such as being brittle in the face of refactoring tools, and moving what would otherwise be code into places away from your code. Generally things that change together should stay together.