October 16, 2017
Manual Refactoring : Dependency Injection
One of the most foundational object oriented design patterns is dependency injection. Yes, dependency injection is a design pattern. (Not a framework or an architectural philosophy.)
DI is how we can make dependencies easily swappable, so that a client doesn't know what specific type of object it's collaborating with.
When a dependency isn't swappable, we lose flexibility. Consider this Ruby example where we have some code that prices video rentals based on their IMDB rating, charging a premium for highly-rated titles and knocking a quid off for poorly-rated ones.
What if we wanted to write a fast-running unit test for VideoPricer? The code as it is doesn't enable this, because we can't swap the imdbRatings dependency - which always connects to the IMDB API - with a stub that pretends to.
What if we wanted to get video ratings from another source, like Rotten Tomatoes? Again, we'd have to rewrite VideoPricer every time we wanted to change the source. Allowing a choice of ratings source at runtime would be impossible.
This dependency needs to be injected so the calling code can decide what kind of ratings source to use.
This refactoring's pretty straightforward. First of all, let's introduce a field for imdbRatings and initialise it in a constructor.
NOW RUN THE TESTS!
Next, introduce a parameter for the expression ImdbRatings.new().
So the calling code decides which kind of ratings source to instantiate.
AND RUN THE TESTS!
Now, technically, this is all we need to do in a language with duck typing like Ruby to make it swappable. In a language like, say, C# or C++ we'd have to go a bit further and introduce an abstraction for the ratings source that VideoPricer would bind to.
Some, myself included, favour introducing such abstractions even in duck-typed languages to make it absolutely clear what methods a ratings source requires, and help the readability of the code.
Let's extract a superclass from ImdbRatings and make the superclass and the fetchRating() method abstract. (Okay, so in C# or Java, this would be an interface. Same thing; an abstract class with only abstract methods.)
DON'T FORGET TO RUN THE TESTS!
One variation on this is when the dependency is on a method that isn't an instance method (e.g., a static method). In the next post, we'll talk about converting between instance and static methods (and functions).
Posted 6 months, 4 days ago on October 16, 2017