Подстановки Лисков
Liskov Substitution Principle - LSP
Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы.
В классических ООП языках подкласс наследует функциональность родителей. В Go вместо наследования используется композиция. Структура может использовать функциональность встроенной родительской структуры, однако тип родителя или какая-либо иерархия отсутствует. То есть мы не можем просто так подставить одну структуру вместо другой. Данный принцип для Go не выполняется!
Все же можно натянуть сову на глобус и сказать, что принцип подстановки Лисков применим через использование интерфейсов. В контексте Go, разные структуры, реализующие один и тот же интерфейс, могут быть использованы взаимозаменяемо.
Представим, что есть интерфейс Shape с методом Area() и возвращает площадь фигуры. Определим разные структуры, такие как Circle и Rectangle, реализующие этот интерфейс. Функция, которая вычисляет и выводит площадь, может использовать любой Shape, не заботясь о конкретной реализации.
Circle и Rectangle являются подтипами Shape. Мы можем передать экземпляр Circle или Rectangle в функцию PrintArea, и она корректно выведет площадь. Подтипы (Circle, Rectangle) заменяют супертип (Shape) без нарушения ожидаемого поведения.
Принцип разделения интерфейса
Interface Segregation Principle - ISP
Слишком большие интерфейсы необходимо разделять на маленькие и конкретные, чтобы клиенты этих маленьких интерфейсов знали только о методах, которые необходимы им в работе. В итоге при изменении метода интерфейса не должны меняться клиенты, которые этот метод не используют.
Если у вас есть многофункциональный принтер, пользователи, которым нужна только печать, не должны иметь дело с функциями сканирования и копирования.
В Go, желательно разделять большие интерфейсы на мелкие и конкретные, таким образом, чтобы клиентский код использовал только тот функционал, который ему необходим. Отсюда еще один плюс - маленькие интерфейсы проще тестировать/мокать.
Принцип инверсии зависимостей
Dependency Inversion Principle - DIP
Целью DIP является уменьшение зависимости между высокоуровневыми и низкоуровневыми модулями. Принцип состоит из двух частей:
1. Высокоуровневые модули не должны зависеть от низкоуровневых модулей. Оба типа модулей должны зависеть от абстракций.
2. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций. В контексте DIP, абстракция обычно представляется в виде интерфейса, определяющего набор методов, которые должны быть реализованы. Реализация (детализация) абстракции - конкретная структура, которая содержит специфическую логику.
В Go, интерфейсы позволяют определить абстрактные типы, которые не содержат конкретных реализаций. Это обеспечивает возможность для разных структур реализовать эти интерфейсы, следовательно, зависимость создается на абстрактном, а не на конкретном уровне.
Рассмотрим пример, где система логирования зависит от абстрактного интерфейса, а не от конкретной реализации.
В примере, Application зависит от абстрактного интерфейса Logger, а не от конкретной реализации FileLogger. Можно заменить FileLogger на другую реализацию Logger, например, ConsoleLogger, не изменяя код Application.