The Dependency Inversion Principle- 4 mins
One 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:
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
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.
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:
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.