The goal of this series is to introduce programming patterns and practices to developers who have little to no familiarity with them. This series does not intend to dive into the intricacies of each pattern / practice, but to give a brief overview that will (hopefully) inspire developers to learn more about them.
What is it?
In a nutshell, dependency injection is a design pattern where external dependencies are "injected" into components rather than baked in.
If that made your eyes glaze over, think about it like this: imagine your friend asked you to drive him to the supermarket:
You would probably just hop in your car and take him:
Now, what if your friend asked you to drive him, and his 5 friends, to the supermarket, but your car only seats 4?
You would need a bigger car, right? Well, the good news is that since most cars implement the same interface (steering wheel, accelerator, brakes, etc.), you're not only capable of driving your own car but many other cars as well. So if you had access to say, your mom's minivan, you could complete the trip:
At the root of it, that's what dependency injection is all about. Instead of you being stuck having to always use your car for your trips, you will be given the correct car to use based on the circumstances.
Bringing this back into software terminology, in the analogy above you (the driver) are a class and the car is your dependency. You depend on the car to drive your friend(s) to the supermarket. It doesn't matter which car you use, so long as it's familiar to you. Without dependency injection, the You class might look something like this:
Notice how you'll always be using that particular instance of Car to complete the trip. With dependency injection however, the You class would look something like this:
Notice how now you don't know (or care) what car you'll be getting to complete your trip. The car is given to you by an outside entity via the constructor.
Ok great, so why should I care?
One reason is the one alluded to above. Dependency injection helps you create "loosely coupled" classes. What this means is that your classes will have less knowledge about their dependencies. This is a good thing. First, because it allows you to only expose functionality in your dependencies that your classes need (e.g. you don't care what make or model or engine the car has, only that it drives and you're capable of driving it). Second, because it lets you quickly and easily substitute out your dependencies (e.g. replacing the car with a minivan), which makes your code more flexible and also lets you swap out implementation without having to modify or recompile your dependent class.
Another reason is for unit testing. Dependency injection allows for
something called "mocking". What this means is that in your unit tests,
you don't actually have to inject concrete dependencies. Instead, you
can inject "mock objects", which are easy to create (via mocking
frameworks) and define behavior for. This lets you test the behavior of
your dependent class without having to worry about whether the
dependencies are working as expected (you can test the dependencies in
another unit test). This means your unit tests will be more in line
with what unit tests are supposed to be: tests on individual units of
an application (as opposed to tests on a unit and its dependencies and
its dependencies dependencies, etc.)
Where can I learn more?
There's been a lot written about dependency injection, but I think the wiki article covers it pretty well.
Once you've got that down, the next thing you'll probably be interested in is Inversion of Control Containers. These containers make dependency injection a breeze by allowing you to configure default implementations to inject into your classes. Martin Fowler's article on dependency injection covers this well.
Note: Yes, I did draw all the pictures using Paint and a laptop trackpad. No, I do not plan on selling my artwork anytime soon.
Enjoyed this post? Share it with others!