While dependency injection (aka, "DI") is a natural technique of composing objects in OOP (known long before the term was introduced by Martin Fowler), Spring IoC, Google Guice, Java EE6 CDI, Dagger and other DI frameworks turn it into an anti-pattern.
I'm not going to discuss obvious arguments against "setter injections" (like in Spring IoC) and "field injections" (like in PicoContainer). These mechanisms simply violate basic principles of object-oriented programming and encourage us to create incomplete, mutable objects, that get stuffed with data during the course of application execution. Remember: ideal objects must be immutable and may not contain setters.
Instead, let's talk about "constructor injection" (like in Google Guice) and its use with dependency injection containers. I'll try to show why I consider these containers a redundancy, at least.
What is Dependency Injection?
This is what dependency injection is (not really different from a plain old object composition):
data is called a "dependency".
Budget doesn't know what kind of database it is working with. All it needs from the database is its ability to fetch a cell, using an arbitrary SQL query, via method
cell(). We can instantiate a
Budget with a PostgreSQL implementation of the
DB interface, for example:
In other words, we're "injecting" a dependency into a new object
An alternative to this "dependency injection" approach would be to let
Budget decide what database it wants to work with:
This is very dirty and leads to 1) code duplication, 2) inability to reuse, and 3) inability to test, etc. No need to discuss why. It's obvious.
Thus, dependency injection via a constructor is an amazing technique. Well, not even a technique, really. More like a feature of Java and all other object-oriented languages. It's expected that almost any object will want to encapsulate some knowledge (aka, a "state"). That's what constructors are for.
What is a DI Container?
So far so good, but here comes the dark side—a dependency injection container. Here is how it works (let's use Google Guice as an example):
Pay attention: the constructor is annotated with
Then, we're supposed to configure a container somewhere, when the application starts:
Some frameworks even allow us to configure the injector in an XML file.
From now on, we are not allowed to instantiate
Budget through the
new operator, like we did before. Instead, we should use the injector we just created:
The injection automatically finds out that in order to instantiate a
Budget it has to provide an argument for its constructor. It will use an instance of class
Postgres, which we instantiated in the injector.
This is the right and recommended way to use Guice. There are a few even darker patterns, though, which are possible but not recommended. For example, you can make your injector a singleton and use it right inside the
Budget class. These mechanisms are considered wrong even by DI container makers, however, so let's ignore them and focus on the recommended scenario.
What Is This For?
Let me reiterate and summarize the scenarios of incorrect usage of dependency injection containers:
Passing injector as a dependency
Making injector a global singleton
If we put all of them aside, all we have left is the constructor injection explained above. And how does that help us? Why do we need it? Why can't we use plain old
new in the main class of the application?
The container we created simply adds more lines to the code base, or even more files, if we use XML. And it doesn't add anything, except an additional complexity. We should always remember this if we have the question: "What database is used as an argument of a Budget?"
The Right Way
Impressive? This is a true object composition. I believe this is how a proper object-oriented application should be instantiated.
And DI containers? In my opinion, they just add unnecessary noise.