[I recently wrote this up for some folks at work, and thought it was worth re-posting here.]
Dependency injection is a hot topic these days. You hear terms like “dependency injection”, “inversion of control”, “IoC container”, “DI container”, etc. a lot. But like with “SOA”, people tend to define the terms however suits them. I’m going to do the same.
The first thing I want to do is distinguish between dependency injection and a container. Dependency injection is a pattern. You don’t need any software tools other than your favorite language and compiler to “do dependency injection.” The dependency injection pattern is a subcategory of the inversion of control (IoC) pattern. A lot of people use DI and IoC interchangeably, and that’s fine with me, as DI is by far the most common type of IoC.
An container is a software tool. Examples of .NET IoC/DI containers are Spring.NET, StructureMap, Castle Windsor, Autofac, and Unity (part of Enterprise Library). While all of these have their own unique bits of functionality, they have a large overlap in that they are all container implementations.
An analogy: You’ve probably heard of the Repository design pattern for data access. And you’ve probably heard of object-relational mapping (ORM) tools like NHibernate, LLBLGen Pro, Linq to SQL and Entity Framework. The relationship between them is analogous to the relationship between the dependency injection design pattern and IoC container implementations: If you’re using the design pattern, the tool makes life easier for you. But it’s entirely possible to use the pattern without using the tool.
So what is the dependency injection design pattern?
Let’s start with the problem. Most classes have to collaborate with other classes in order to fulfill their responsibilities. But having one object instantiate another creates a hard dependency that can limit flexibility, testability, and transparency to consuming developers.
Let’s write some code for a big important Boss who asks her Gopher to go get lunch:
One big problem with this code is that the only thing the Boss can ever have for lunch is pizza. Another drawback to this code is that Gopher’s dependency on PizzaService is opaque and hidden. The only way to know there is a dependency is to open up the code of the Gopher class and read it. It doesn’t appear anywhere in the constructor or method signatures. We want as much as possible to be obvious and explicit. We don’t want developers using our APIs to have to sift through our code to figure out all the dependencies.
We can start to address the first concern by creating an ILunchService interface:
But we still have the same problem of pizza every day, because the Gopher is still creating the instance of PizzaService. Even if the Boss is a real pizza fanatic, we can’t unit test Gopher without actually having a pizza made and delivered. That could get pretty expensive.
In more real-world terms, we can’t write tests without creating and accessing real instances of expensive resources (like a database). That means the unit tests will be slow, and that’s very bad. We want to run hundreds or thousands of unit tests in a few seconds. Even with larger, slower integration tests we don’t actually want to have hundreds of pizzas delivered when our automated test suite runs every night…
And we also haven’t addressed the opaque dependency of Gopher on PizzaService.
So the next step is for the Gopher to require that somebody tell him what kind of lunch service to use. (This isn’t the most proactive gopher in the company.)
At this point, we are using the dependency injection pattern in the Gopher class. Specifically, we are using constructor injection. That is, in our constructor, we are asking for someone to pass in (inject) an instance that we depend on. That’s all dependency injection is. Big words, simple concept. We’ve inverted the usual way things are done. Gopher doesn’t control how its dependencies are created, the consumer of Gopher does. “Inversion of control” is a fancy phrase for letting the consumer take control.
We could also have used property injection (setting a property value with the dependency) or method injection (passing the dependency as a parameter to a method). But the best practice is to use constructor injection for required dependencies — don’t allow someone to create an object if it doesn’t have its required dependencies fulfilled — and property injection for optional dependencies. Method injection is really only recommended when you’re adding a container implementation to an existing code base. It’s yucky (because it’s less obvious), and shouldn’t typically be used in new code.
There are two big benefits to our use of the dependency injection pattern at this point: First, the Boss can get any kind of lunch she wants by simply telling the Gopher what kind of service to use. And second, other developers who want to use the Gopher class can’t help but see that the Gopher depends on an ILunchService — you just can’t create a Gopher instance without one. This is the kind of explicit, obvious dependency we want.
But we still have a problem. We’ve made a pretty career-limiting move for the Gopher. We gave the Boss more work to do. The Boss now has to create the PizzaService in order to get lunch:
You might say “That’s only one more line of code than the Boss had before.” Well, I’ve found that giving Bosses (or developers using your API) even a tiny bit more work to do is generally a bad idea. And while we’re on the subject, why should the Boss have to create the Gopher, too? The Gopher should just be there, ready to do the Boss’s bidding. In fact, isn’t the Boss dependent on the Gopher?
Oh, wait a minute, we already know what to do with hard dependencies caused by using the new operator — we use dependency injection:
We’ve applied the dependency injection pattern to the Boss, too, and another dependency (of Boss on Gopher) has been dragged into the light of day for all to see. In this case we haven’t created an IGopher interface, but that’s okay. Sometimes you don’t need to use an interface (although often you should). With or without an interface, developers using the Boss class now have a much clearer picture of what’s going on — without digging into the actual code. Everything is explicit and obvious.
You might start to wonder whether some objects that do a lot of things are going to have 10 constructor parameters. Well, yes. But that’s a code smell: if you have an object with that many dependencies, it’s probably doing too much. It’s very likely that it has multiple responsibilities, and your classes should typically adhere to the Single Responsibility Principle as much as possible. Split that class into several that each does one thing only. This makes your software easier to change, too. Smaller, simpler classes mean each class has fewer reasons to change, and fewer collaborators means fewer potential ripple effects when you do make changes.
Here’s one of the big ideas behind dependency injection: separating the object graph from the execution graph. In code where you see new Foo() all over the place, the object graph and the execution graph are tightly intertwined. That’s usually a bad thing, because objects rarely reference each other in the exactly the same hierarchical structure that they call each other’s methods. Testing or changing a little thing in that kind of code can be a real pain.
A common solution for separating object graph creation from the execution graph is to use factory classes. The Abstract Factory pattern is one of the older and better known design patterns. All of the instantiation and injection of the dependencies would happen in the factories, out of the way of your executing business logic.
In this code, the boss is still having pizza every day for lunch, but it’s easy to imagine how we could use configuration or a UI input value to select a different implementation of ILunchService each day. The key is that the selection of that implementation doesn’t really have anything to do with the process of actually having lunch. And now our code separates those two processes very cleanly.
Further reading on dependency injection: