This article serves as an introduction to the concept of Dependency Injection, and why you’d want to use it. It is not a getting started guide for using containers. If you’re interested in those, my personal preference is Castle Windsor and you can find their getting started guide here.
What are dependencies? Also referred to as couplings, dependencies are other modules that one module requires to fulfil it’s purpose.
Are dependencies bad? No, of course they’re not, otherwise we wouldn’t be able to create anything. However, highly coupled code can cause a lot of problems for your application.
If your code requires knowledge of how a dependency works, then your code is highly coupled. If your code is tied explicitly to an implementation, then your code is also highly coupled.
Take the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
The above example is highly coupled to the
SqlDataStore. Firstly, it directly creates an instance, which means there’s no way for us to replace that instance if we need to (I’ll come to why you’d want to do that in a bit). Secondly, it relies on a great deal of knowledge of
SqlDataStore’s implementation. In this code we can see that you need to create a connection and open it before you can update the record; that’s quite a bit of implementation knowledge. If the
SqlDataStore was to change so that the
OpenConnection method created a connection if one didn’t already exist, then we’d need to change every caller of that code to remove the
CreateConnection call; in large system situations like that can quickly lead to a fear of change and refactoring.
I mentioned directly creating an instance stops us from replacing it if need be. Well when would you actually want to do this? For those unfamiliar with unit testing, you probably haven’t encountered test doubles; there are different types of test doubles, but for the purposes of this example they’re interchangeable.
A test double serves as a swap-in replacement for one of your dependencies. These allow you to execute a piece of code under test, without having to worry about whether things are being put in your database, or e-mails sent for example. If your code is creating instances within methods, those instances cannot be replaced by a test double. Without that ability, testing becomes considerably more difficult.
Tightly tying your code to an instance of a class reduces the flexibility and reuse of your code. Take the above example, that same code could be used to update the product in a cache instead of the database; similarly, you could use an in-memory storage instead of a database for when you’re running in a test or demo environment. If only your method wasn’t so tightly tied to the implementation.
[DIP] seeks to “invert” the conventional notion that high level modules in software should depend upon the lower level modules. The principle states that high level or low level modules should not depend upon each other, instead they should depend upon abstractions. – Wikipedia
Essentially, dependency injection allows you to stop instantiating your dependencies. Instead they’re “injected” into your object when it is instantiated itself. This allows the dependencies to be swapped out like we mentioned above.
So taking the original example, here’s a version of it that’s been updated to use dependency injection.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
The above implementation now allows us to create a test double, and replace the
SqlDataStore in our tests. As I mentioned before, we could now easily push in an in-memory implementation without any changes to the code required.
We can take this further though, because we’re still tied to a concrete class. Lets make
SqlDataStore implement an interface, so we can create other implementations.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
Now our example is no longer specifically tied to a
SqlDataStore, so we could quite easily pass it a
FileSystemDataStore, or an
InMemoryDataStore, or anything else that implements
IDataStore. All that without having to touch a single line within the
That’s the power of dependency injection, and why you should stop hard-coding your dependencies.