What is dependency injection?
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:
But 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.