The Dependency Inversion Principle
- 5 minsOne of the tasks I had for this week was to extract my tictactoe core functionality into a gem. There were three components that made up the core of my game: Board
, Computer
and Game
.
Sorting out the first two went smoothly, however when I went to extract the Game
section, I realised I had violated one of the SOLID principles. In fact the specific violation indicated issues related to more than just one of the principles, which shows how closely related the SOLID principles are.
The first violation was that of the Dependency Inversion Principle which states the following:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
To show how this affected the Game
class let’s look at the constructor:
I’m setting the defaults of display
, board
and computer
to their equivalent classes. At the time that I was putting this together, I only had to build a command line interface so I thought that injecting the display into the game made sense. Now though, since I’m trying to create a gem which could then be used with other interfaces such as a GUI or Web interface, the above code won’t be suitable for a number of reasons.
First, the command line interface is not needed. Second, not only will building a different interface require me to implement the names of the methods that CliDisplay
already has, an even bigger problem is that the flow of the game will vary depending on the interface used. It’s fine using while
loops and blocking the stream with gets
as you wait for input on the command line, but these things won’t be necessary with the other interfaces.
Solution
The first step I took was to delete the contents of Game
class (source control has our back) and the equivalent tests. This is something I have learned to do more frequently whilst at 8th Light. Trying to fiddle with existing code in order to create something completely different feels weird and furthermore, you might end up writing code that you don’t need.
I then reconstructed the Game
class such that the CliDisplay
and any reference to it was removed. The Game
class now simply provide an interface which any other module could use. Below is the new constructor with some of the other methods:
The next step was creating a CliGame
class which takes a new game and a display as arguments and then executes the job that my old Game
class used to do. For example, the method marking a position on the board based on user input is identical:
The CliGame
now acts simply as a plugin, that is it extends the core functionality of the tictactoe game by providing a command line interface. Additional plugins for it like the ones mentioned above can be created without having to worry about the Game
details anymore. I simply need to inject the Game
and the rest of the implementation is up to that specific interface, whether that’s a CLI or a GUI.
Since the original state of my game did not allow for extension, the Open-Closed Principle, which I described in this post, was also violated, however I felt that the Dependency Inversion was the more pressing concern given the task at hand.
To sum it up the old code was attached to a concrete class and an idea that could change; it is more likely to change the interface than it is to change the game rules. Reversing the dependency to point to a more stable idea (game rules) allows for the extension of the program if there is a need to do so.