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.

Direct Construction of Collaborator
class Worker
def initialize
@helper = Helper.new
end
def operation
@helper.help_me
end
end

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.

Injecting a Collaborator
class Worker
def initialize(helper)
@helper = helper
end
def operation
@helper.help_me
end
end
class WorkerTest < Minitest::Test
def setup
@helper = Minitest::Mock.new
@worker = Worker.new(@helper)
end
# ...
end
def domain_code
worker = Worker.new(Helper.new)
worker.operation
end

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:

Dependency Injection in C++
class Helper
{
public:
virtual ~Helper() {}
virtual void helpMe() const = 0;
};
class RealHelper : public Helper
{
public:
virtual void helpMe() const
{
// Implementation
}
};
class Worker
{
public:
Worker(const Helper& helper) :
helper{helper}
{
}
void operation()
{
helper.helpMe();
}
private:
const Helper& helper;
};
class FakeHelper : public Helper
{
public:
virtual void helpMe() const
{
// Implementation
}
};
void testCode()
{
FakeHelper helper;
Worker worker{fakeHelper};
worker.operation();
}
void domainCode()
{
RealHelper helper;
Worker worker{realHelper};
worker.operation();
}

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.

Default Collaborator in C++
class Worker
{
public:
Worker(const std::unique_ptr<Helper> helper = std::make_unique<RealHelper>()) :
helper{helper}
{
}
// ...
};
// ...
void testCode()
{
Worker worker{std::make_unique<FakeHelper>()};
worker.operation();
}
void domainCode()
{
Worker worker;
worker.operation();
}

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:

Default Collaborator in Smalltalk
Worker class>>new
^self helper: Helper new
Worker class>>helper: aHelper
^self basicNew initializeHelper: aHelper
Worker>>initializeHelper: aHelper
helper := aHelper

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:

Default Collaborator in Ruby
class Worker
def initialize(helper = Helper.new)
@helper = helper
end
# ...
end

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.