Dependency Injection 101 — What and Why

AJ Ribeiro
Bigeye
Published in
4 min readJun 23, 2020

[Part 2 of this series, Part 3 of this series]

I recently instrumented Toro Data Labs’s codebase to follow the Dependency Injection (DI) pattern. Following DI can provide us with several benefits which will be discussed in the second section of this post.

The first time I encountered DI, I found it confusing and jarring, but luckily I had great mentors who took the time to sit with me and break it down. Not everyone is as lucky though, and I thought it would be worthwhile to write a post to help explain it to folks who have a similar experience to mine.

Although I will be writing this in terms of Java, the concepts are applicable to any language.

What is Dependency Injection

DI is a coding pattern where dependencies of a class are injected into the class via its constructor. For example, let’s say that we have a house, which needs a bathroom and a kitchen. Similarly, a bathroom needs a sink and a shower, and a kitchen needs a sink and a stove. We would then say that class House has dependencies on class Bathroom and class Kitchen. Class Bathroom then has dependencies on class Sink and class Shower, while class Kitchen has dependencies on class Sink and class Stove. These classes could look like so:

Another aspect of DI is the use of interfaces. Interfaces define the behavior of a class, but not the implementation of that behavior. To go back to our example, Bathroom has a Sink and Shower dependency, and Kitchen has a Sink and Stove dependency. But as we know, kitchen sinks and bathroom sinks, although having similar behavior, have slightly different properties. So we will make Sink an interface with two implementations. The classes would then look like so:

Now, I have all my classes, and all of my classes have constructors such that their dependencies are injected via their constructors. As such, I can build a House in my main function like so:

As you can see, all classes are receiving their dependencies through their constructors. This is the DI pattern. Okay, so what?

Why should I use DI?

Now that we know what DI is, why should we use it? The answer is that this pattern brings us several benefits over generating dependencies other ways (e.g., within a constructor).

Separation of Concerns

In this architecture, each class only needs to worry about what is going on inside the class. For example, a House doesn’t have to worry about how to construct a Bathroom, and likewise a Bathroom doesn’t have to know how to construct a Shower. All of that is handled by the creator of the object, in this case our application’s main. In the second post in this series we will discuss DI tools which make the creation process even simpler.

Configurability

Consider the example of the Sink interface. Both Bathroom and Kitchen need a sink, and neither of them care which implementation of Sink they get. For this example, Bathroom gets BathroomSink and Kitchen gets KitchenSink, but there’s no reason that both couldn’t get KitchenSink. Or in the future if some great MagicSink is invented, they could both get that.

If we go back to our brick and mortar house example, consider the sink hookup in the kitchen. If the hookup in a kitchen is standardized, then any sink at the hardware store which meets that standard can be installed in my kitchen. The standard hookup can be thought of as an interface and the sink we purchase can be thought of as the implementation.

Ease of testing

As a unit-testing zealot, one of my favorite aspects of DI is how testable it makes your code. For comparison, let’s consider a House class that doesn’t use DI:

If I wanted to unit test this, I would want a mock Bathroom and mock Kitchen, but there is no straightforward way to get these dependencies into house. The only way to do this would be to use Powermock, which is a great tool for unit testing legacy code, but should be avoided in favor of following better coding patterns for new services.

If we now go back to our DI-following house, all of the dependencies can be mocked and then injected into the constructor to make unit tests easy to write. For example, if we wanted to test the house class (using Mockito):

As we can see in the test code above, all of the dependencies of House are mocked, which will make it very easy to unit test only the behavior of House.

What’s next?

Okay, so DI the pattern is cool, but we also have a lot of boilerplate in our main method. Luckily, the good folks at Google and Square have written tooling (Guice and Dagger[2]) to help us reduce the boilerplate that we need. In Dependency Injection 102, we will discuss what these tools are and how to use them.

This code is available to clone from GitHub.

--

--