SOLID — принцип инверсии зависимостей

🎉 Вы можете найти новые и обновленные сообщения на ellehallal.dev

Это краткий блог о моем понимании инверсии зависимостей. Есть еще элементы, в которых я не уверен, поэтому, пожалуйста, не стесняйтесь оставлять отзывы.

Что такое принцип инверсии зависимостей?

Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.

Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

— Роберт С. Мартин

Это здорово, но что это значит? 🤷🏽‍♀️ Продемонстрирую свое понимание на примере класса в моем приложении Крестики-нолики.

Инверсия зависимостей в крестики-нолики

В приложении есть класс с именем GameFactory. Целью GameFactory является создание экземпляра класса Game с указанными игроками и доской.

Вот сокращенная версия класса:

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

В методе new_game внутри него создаются новые экземпляры классов Board и Game. Однако это нарушает принцип инверсии зависимостей.

Что с этим не так?

Высокоуровневый класс GameFactory зависит от низкоуровневых классов Board и Game, поэтому они тесно связаны. Изменение в низкоуровневом классе повлияет на высокоуровневый класс.

Если имя класса Board или Game было изменено, метод new_game в классе GameFactory работать не будет. В результате его необходимо будет изменить, чтобы учесть переименованные классы.

Если бы для создания новой игры использовались подклассы Board и Game (например, BestBoard и FunGame), метод new_game нужно было бы снова изменить, чтобы учесть это.

Метод решения вышеуказанных проблем заключается в передаче классов в конструктор GameFactory:

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

Все, что передается как board и game во время инициализации, становится @board и @game внутри GameFactory.

Если бы имена классов Board и Game изменились, инициализация GameFactory с переименованными классами не повлияла бы на GameFactory.

Если бы подклассы Board и Game (
например, BestBoard и FunGame) использовались для инициализации экземпляра GameFactory, это не повлияло бы на работу new_game.

В заключение, я понимаю, что инициализация определенного класса или классов внутри другого приводит к тесной связи. Возможность вводить классы через конструктор помогает сделать классы слабо связанными.