A while ago I was faced with an interesting challenge: I was busy introducing dependency injection into an existing system. Almost every method call inside this system was a static call, so every class was hard coded to their dependencies.
But, I might be going a little bit too fast here. What is dependency injection?
Dependency injection (DI) is a software design pattern that allows the removal of hard-coded dependencies and makes it possible to change them, whether at run-time or compile-time.
The situation looked somewhat like the one on the right. One class with access to the container, which calls methods on another class, and then another class, etcetera. I had to get an object that was found inside the DI container to some of the lower classes, say D and E in this picture.
Normally dependency injection is really good in solving this kind of problems, every object will receive it’s dependencies in their constructor. The problem here is the absence of the constructor, since everything was being called statically.
As always there are a couple of solutions to this problem. One might suggest to pass the container around. Don’t do this. Depending on the container is evil.
Another solution would be to pass the dependencies in all methods. Depending on how many dependencies you have to pass down, this might work. This has some downsides:
- It clutters your interfaces. Every time you call this method, you have to provide these arguments.
- Dependencies usually don’t make any sense to pass as an argument, they ‘feel’ more like properties of a class.
- Should you be able to get rid of that dependency, you have to remove the arguments everywhere. This can be error prone, especially in a dynamically typed language.
There is one interesting bit here. Those are all problems that can be solved by DI! Lets start introducing dependency injection. Make all those static methods non static.
Introducing dependency injection
When refactoring existing systems your first victim would be the class with the least dependencies, see my blog post about unit-testing for an explanation on that subject. So normally you’d first refactor class E, then D/C, followed by B and finished by A. Basically a reversed breadth first search.
This did not work very well for this change, I found doing a breadth first search (So, A -> B -> C -> D -> E) was more convenient for introducing dependency injection!
- Make all methods of A non static.
- Add A to the DI container.
- Make sure to get A from the container where it used to be called, or to inject it into classes that require it.
- Fix unit-tests.
After this, do the same for the other classes, and you’re done. You could even release your software after every class that you fix.