Affordances: Dependency Injection
This post is part of an ongoing series about Affordances and Programming Languages.
In an object-oriented system, many objects work with other objects to do their jobs. When working with these collaborators, one of the first questions is always, “How does this object find out who its collaborators are?”
If the collaborator is a simple helper object, the most direct solution is for the object to create the collaborator itself.
The direct solution can work well in many cases. However, if the collaborator performs expensive computation or accesses external resources, the owning object can become difficult or expensive to test. When that happens, it is helpful for the tests to provide an alternate collaborator, such as a stub or mock object. This keeps the tests running fast and avoids dependencies on external resources.
This pattern of providing a collaborator is called Dependency Injection. Martin Fowler has written a very good introduction to the topic. I won’t debate the pros and cons of using Dependency Injection here. Instead, my focus is on the affordances provided by various programming languages to support this pattern.
In a dynamically-typed language like Ruby, explicit interfaces are not necessary. We can just take advantage of “duck-typing”. However, even though we don’t have to create an explicit interface, it’s good to remember that we are still defining an implicit interface that consists of all of the messages the owning object sends to the collaborator.
In an explicitly-typed language like C++, C#, or Java, it is necessary to define an explicit interface for the injected collaborator. The real collaborator and the substitute both have to implement the interface:
One of the criticisms of Dependency Injection is that it makes the client code more verbose, because all clients have to specify the dependencies. In the case of a single real implementation of the injected collaborator, this can get noisy. A possible solution is to make use of argument-passing affordances to provide a default implementation.
In C++, we’re forced to wrap the collaborator in a smart pointer; we can’t use a reference because the default collaborator wouldn’t live long enough.
(Note that std::make_unique
is part of the forthcoming C++14
standard. The
implementation is publicly available,
so you could include your own version for now).
Smalltalk requires two methods to specify the default collaborator, but it means that we can write any Smalltalk code we like to create the default collaborator before passing it on:
Ruby is simpler than Smalltalk because we don’t need the second method, but it is slightly less flexible in that we only get a single Ruby expression to create the default collaborator. If more complex creation is necessary, we might have extract a helper method or similar:
The Java community has developed several Dependency Injection frameworks that use external metadata, like XML files, to specify how collaborators should be wired together. I won’t dig deeper into those here, but will mention that reflection facilities are a required affordance for that kind of solution.
Programming languages provide several different kinds of affordances that impact the implementation of the Dependency Injection pattern, and those affordances impact the kinds of solutions we think about in those languages.