Out of the Ether: Dependency Injection
I enjoy Frank Kelly's blog. He has a recent post about Dependency Injection. I had been sitting on a thought for awhile with respect to DI, and thought I'd write it out here instead of in a comment window.
(Editor's note: be sure to see the post script for strong criticism against the example and the post.)
Here's how I think of DI. We all know this is bad:
class MyClass {
private HashMap myMap = new HashMap();
}
because we should program to the interface, yes? Like so:
The conventional wisdom was always:
class MyClass {
// more flexible, in theory
private Map myMap = new HashMap();
}
Program to the interface, since it will be easy to change the implementation.True that. But then the implicit kicker was:
And when you are done with your application and have been given a couple of weeks by management to tweak performance while wearing your PJ's and drinking a fine Chardonnay, you can go through the code and select the implementation that's right for you.Whoa, Nelly! *throws RealityCheckException*. When would that be, again?
What if we tried this:
class MyClass {
private Map myMap;
public MyClass( ... , Map myMap ) {
this.myMap = myMap;
}
}
The beauty of this, IMHO, is truly profound; it took me a long time to appreciate it. Clearly, the class just uses the interface and there is less coupling, more flexibility, etc. But there is more:
The Ether
The power of DI is twofold:
(1) a given class is simplified and works with a minimal contract (the interface)
(2) there are tremendous tools available, outside the class, that allow us to really, truly make different implementations available, and swap 'em out easily. Essentially, these tools control the creation of the MyClass object, and allow us to dictate the given implementation for an interface (e.g. Map). Where are these implementations? Where do they come from? The answer is, from the view of the class: we don't know, they come out of the ether.
And that ether is powerful stuff: DI tools allow us to create mock implementations, single-threaded implementations, file-based implementations, DB implementations, carrier-pigeon implementations -- you name it. The configurations (or annotations) make it truly possible, unlike the hypothetical "free 2 weeks of tweaking paradise" mentioned earlier. You could potentially run several entirely different testing suites, simply by swapping out the ether.
To press the point even further, take the Map interface used above and now multiply this idea by any and all interfaces in your application. That ether is strong mojo.
Whether you use Guice or Spring: tap into this ether and re-discover the power of interfaces. If you're like me, you may discover they are cooler than we ever realized.
Final Score: Dependency Injection 1 Management 0
Post Script
Eric and others have made valid criticisms. See the article/comments, but here is a summary:
- I blew it on the phrase "any and all interfaces". The coolness of DI doesn't give one license to inject everything. A better version is: consider all of the interfaces in your project and then apply DI judiciously.
- My example was poor. I wanted to concentrate on the ether, and didn't have the energy to construct a "real-world" example so I went with Ye Olde Collections. I think it is an ok example to illustrate the ether, as long as one understands the criticisms against it. In fact, it might even be a better example because of the criticisms (though this was not by intent: mea culpa).
- I have used Spring's XML configuration, and have wondered about losing encapsulation as a danger of DI. I haven't yet tried Guice or any of Spring's newer stuff, so I don't know how that might mitigate the issue.