What Is The Dependency Inversion Principle?
This is a quick blog on my understanding of dependency inversion. There are still elements I am unsure about, so please feel free to leave feedback.
What is the Dependency Inversion Principle?
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.
— Robert C. Martin
That’s great, but what does it mean? 🤷🏽♀️ I’ll demonstrate my understanding by using a class in my Tic Tac Toe application as an example.
Dependency Inversion in Tic Tac Toe
In the application, there’s a class called GameFactory
. The purpose of GameFactory
is to create an instance of the Game
class with the specified players and a board.
Here’s a condensed version of the class:
1class GameFactory2 def initialize(player_factory)3 @player_factory = player_factory4 end56 def new_game(squares)7 board = Board.new(squares)8 player1 = @player_factory.create_player('x', 1)9 player2 = @player_factory.create_player('o', 2)10 Game.new(board, player1, player2)11 end12end
In the new_game
method, new instances of the Board
and Game
classes are created within it. However, this violates the Dependency Inversion Principle.
What’s wrong with it?
The high-level class GameFactory
is dependent on the low level classes Board
and Game
. As a result, they are tightly coupled. A change in a low-level class will affect the high-level class.
If the name of the Board
or Game
class was changed, the new_game
method within GameFactory
wouldn’t work. As a result, it would need to be amended to accommodate the renamed classes.
If sub classes of Board
and Game
were to be used to create a new game, (for example, BestBoard
and FunGame
) the new_game
method would need to be changed again to accommodate this.
A method to resolve the above issues is to pass the classes into GameFactory
's constructor:
1class GameFactory2 def initialize(player_factory, board, game)3 @player_factory = player_factory4 @board = board5 @game = game6 end78 def new_game(squares)9 board = @board.new(squares)10 player1 = @player_factory.create_player('x', 1)11 player2 = @player_factory.create_player('o', 2)12 @game.new(board, player1, player2)13 end14end
Whatever is passed in as board and game during initialisation, becomes @board
and @game
within GameFactory.
If the names of the Board
and Game
classes were to change, initialising GameFactory
with the renamed classes would not affect GameFactory
.
If subclasses of Board
and Game
(for example, BestBoard
and FunGame
) were used to initialise an instance of GameFactory
, this would not affect how new_game
functions.
In conclusion, my understanding is initialising a specific class, or classes within another results in tight coupling. Being able to inject the classes via the constructor, helps to make the classes loosely coupled.
Discussion