When I started programming, one subject I felt I never fully understood was the Inversion of Control design principle, in the context of using dependency injection when testing. These were commonly mentioned in tutorials, blogs, and documentation. My main takeaway was “make it another class’s problem”, so I think it’s time for a refresher on this topic to see if that holds true. Inversion of Control extends the Dependency Inversion principle, the D in SOLID.
Once again Martin Fowler has a great article on the subject, this time with code examples. Before I summarize his description, what is the problem that IoC solves? A picture says approximately 2¹⁰ words, so:
Fowler describes a MovieLister class that can act on the MovieFinder interface. This is great for the methods in MovieLister and makes it easy to modify, but MovieLister is also creating the implementation. It would be nice to change the implementation without modifying MovieLister, especially if we want to package MovieLister for use by others, who might need a different implementation of MovieFinder.
“We would prefer it if it were only dependent on the interface, but then how do we make an instance to work with?”Martin Fowler, Inversion of Control Containers and the Dependency Injection pattern
The basic idea is simple: create the implementation of MovieFinder in another class and “inject” it into the MovieLister. This can be done in a separate Container class, or an Assembler class.
Dependency Containers allow you to keep track of the classes that you must create and their dependencies. This container will construct the dependencies, create the object you need, and return said object. This isolates all changes to dependencies into a single class. Note that this is encapsulating what varies. The user only needs to decide on concrete implementations in their own custom Container. They can implement MovieFinder in a custom class and use in in their Container.
There are three types of dependency injections: constructor, setter, and interface. The difference is straightforward: either inject the dependency with a constructor at instantiation, a public setter method, or an interface method. Fowler prefers constructors and I’m inclined to agree. If possible, it is better to have a completely constructed object immediately.
I do think this really boils down to “make it another class’s problem”, but this phrase was misleading to me a few years ago. The problem I had with it was figuring out where to end the abstraction and create a concrete class, thinking it should be yet another class’s problem. It was tempting to misuse the design pattern, which left me feeling confused when I didn’t have a good answer. At some point, you need a concrete class to actually do the work. With this design pattern that concrete class is the Dependency Container.