"Dependency Injection" Considered Harmful

Dependency Injection and Dependency Injection frameworks are a frequent topic on the GOOS discussion group. We are often asked why we didn't use Dependency Injection (usually meaning a Dependency Injection framework) in the code examples.

GOOS does use dependency injection througout, but we don't call it that because I find the term "dependency injection" is a really bad name for the pattern.

There are two aspects to Dependency Injection. Firstly, an object's interface should define the services that the object requires as well as those it provides. Secondly, the code that satisfies the requirements of an object by giving it a reference to the services of is collaborators is external to both the object and its collaborators. For this reason, the pattern also used to be called "third-party binding" or "third-party connect": some third party is responsible for connecting a satisfying a the service requirements of a component (the party of the first part) with those provided by another component (the party of the second part).

The name "Dependency Injection" only relates to the second aspect. And worse, makes it sound like dependencies are "injected" through the object's encapsulation boundary rather than explicitly defined as part of the object's API. And so we get "dependency injection" APIs, like that in JavaEE 5, which use reflection to poke dependencies into an object's private fields, bypassing the constructor, adding a great deal of complexity to client & test code while not providing the benefits that the Dependency Injection pattern should deliver.

In GOOS we were careful to distinguish between the various practices that are all lumped together under the term "Dependency Injection". We define our objects to be context independent (p 54). We clearly distinguish at an object's API between the object's dependencies (which must be passed to the constructor to ensure that the object can never be instantiated in an invalid state) and its other collaborators (notification listeners, adjustments) (p 52). And we separate the code that composes objects from the objects being composed (p 64-65).

We don't use a DI framework. Personally, I find them more of a hindrance than a help in most situations because they don't distinguish between "internals" & "peers" (p 50) and so often guide design to a violation of the "composite simpler than the sum of its parts" principle (p 53). I find that I need to clearly understand, and therefore express in code, the dependencies between the objects in the system. Hiding that information away in auto-wiring frameworks or obfuscating it in XML files that, furthermore, cannot be easily refactored just gets in my way when I later need to understand the code and modify it.

GOOS instead advocates clearly expressing the dependencies between objects in the code that composes them, so that the system structure can easily be refactored, and aggressively refactoring that compositional code to remove duplication and express intent, and thereby raising the abstraction level at which we can program (again, p 64-65). The end goal is to need less and less code to write more and more functionality as the system grows.

The one situation I have found a DI framework useful is when dynamically loading plug-ins into a running program. But it's not often I write a program that has to do that, and try to keep that technical gubbins clearly separated from the rest of the system.

Copyright © 2011 Nat Pryce. Posted 2011-01-06. Share it.

Comments powered by Disqus