Найти тему

#18 Приципы SOLID в Go. Часть II

Оглавление

Подстановки Лисков

Liskov Substitution Principle - LSP

Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы.

В классических ООП языках подкласс наследует функциональность родителей. В Go вместо наследования используется композиция. Структура может использовать функциональность встроенной родительской структуры, однако тип родителя или какая-либо иерархия отсутствует. То есть мы не можем просто так подставить одну структуру вместо другой. Данный принцип для Go не выполняется!

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

Представим, что есть интерфейс Shape с методом Area() и возвращает площадь фигуры. Определим разные структуры, такие как Circle и Rectangle, реализующие этот интерфейс. Функция, которая вычисляет и выводит площадь, может использовать любой Shape, не заботясь о конкретной реализации.

ссылка на Playground

Circle и Rectangle являются подтипами Shape. Мы можем передать экземпляр Circle или Rectangle в функцию PrintArea, и она корректно выведет площадь. Подтипы (Circle, Rectangle) заменяют супертип (Shape) без нарушения ожидаемого поведения.

Принцип разделения интерфейса

Interface Segregation Principle - ISP

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

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

-2

В Go, желательно разделять большие интерфейсы на мелкие и конкретные, таким образом, чтобы клиентский код использовал только тот функционал, который ему необходим. Отсюда еще один плюс - маленькие интерфейсы проще тестировать/мокать.

-3

ссылка на Playground

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

Dependency Inversion Principle - DIP

Целью DIP является уменьшение зависимости между высокоуровневыми и низкоуровневыми модулями. Принцип состоит из двух частей:

1. Высокоуровневые модули не должны зависеть от низкоуровневых модулей. Оба типа модулей должны зависеть от абстракций.

2. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций. В контексте DIP, абстракция обычно представляется в виде интерфейса, определяющего набор методов, которые должны быть реализованы. Реализация (детализация) абстракции - конкретная структура, которая содержит специфическую логику.

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

Рассмотрим пример, где система логирования зависит от абстрактного интерфейса, а не от конкретной реализации.

-4

ссылка на Playground

В примере, Application зависит от абстрактного интерфейса Logger, а не от конкретной реализации FileLogger. Можно заменить FileLogger на другую реализацию Logger, например, ConsoleLogger, не изменяя код Application.