Originally posted at ellehallal.dev👩🏽💻
This is a quick blog on my understanding of the dependency inversion principle. 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:
class GameFactory
def initialize(player_factory)
@player_factory = player_factory
end
def new_game(squares)
board = Board.new(squares)
player1 = @player_factory.create_player('x', 1)
player2 = @player_factory.create_player('o', 2)
Game.new(board, player1, player2)
end
end
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:
class GameFactory
def initialize(player_factory, board, game)
@player_factory = player_factory
@board = board
@game = game
end
def new_game(squares)
board = @board.new(squares)
player1 = @player_factory.create_player('x', 1)
player2 = @player_factory.create_player('o', 2)
@game.new(board, player1, player2)
end
end
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.
Top comments (1)
I think the code could do with better formatting :)