Разработка кода. SOLID принципы построения кода

SOLID это аббревиатура пяти основных принципов проектировании классов в объектно-ориентированном программировании — Single responsibility, Open-closed, Liskov substitution, Interface segregation и Dependency inversion.  Таким образом, мы имеем 5 принципов, которые и рассмотрим ниже:

  • Принцип единственной ответственности (Single responsibility)
  • Принцип открытости/закрытости (Open-closed)
  • Принцип подстановки Барбары Лисков (Liskov substitution)
  • Принцип разделения интерфейса (Interface segregation)
  • Принцип инверсии зависимостей (Dependency Invertion)

Принцип единственной ответственности (Single Responsibility Principle)

Принцип единственной ответственности гласит — «На каждый объект должна быть возложена одна единственная обязанность». Т.е. другими словами — конкретный класс должен решать конкретную задачу — ни больше, ни меньше.

Типовые примеры нарушения:
1) смешивание логики и инфраструктуры: бизнес-логика смешана с представлением и т.п.
2) класс/модуль решает задачи разных уровней абстракции: вычисляет CRC и отправляет уведомления по электронной почте; разбирает json-объект и анализирует его содержимое и т.п.

Anti-SRP – Принцип размытой ответственности. Чрезмерная любовь к SRP ведет к обилию мелких классов/методов и размазыванию логики между ними.

Принцип открытости/закрытости (Open-Closed Principle)

Данный принцип гласит — «программные сущности должны быть открыты для расширения, но закрыты для модификации». На более простых словах это можно описать так — все классы, функции и т.д. должны проектироваться так, чтобы для изменения их поведения, нам не нужно было изменять их исходный код.

Типичные примеры нарушения: размазывание информации об иерархии типов по всему приложению.

Anti-OCP – Принцип фабрики-фабрик: Чрезмерная любовь к OCP ведет к переусложненным решениям с чрезмерным числом уровней абстракции.

Принцип подстановки Барбары Лисков (Liskov Substitution Principle)

Пожалуй, принцип, который вызывает самые большие затруднения в понимании. Принцип гласит — «Объекты в программе могут быть заменены их наследниками без изменения свойств программы». Своими словами я бы это сказал так — при использовании наследника класса результат выполнения кода должен быть предсказуем и не изменять свойств метод.

Типичные примеры нарушения: несогласованное поведение наследников, что приводит к необходимости приводить экземпляры базового класса к конкретным типам наследников.

Anti-LSP – Принцип непонятного наследования. Данный анти-принцип проявляется либо в чрезмерном количестве наследования, либо в его полном отсутствии, в зависимости от опыта и взглядов местного главного архитектора.

Принцип разделения интерфейса (Interface Segregation Principle)

Данный принцип гласит, что «Много специализированных интерфейсов лучше, чем один универсальный» Соблюдение этого принципа необходимо для того, чтобы классы-клиенты использующий/реализующий интерфейс знали только о тех методах, которые они используют, что ведёт к уменьшению количества неиспользуемого кода.

Типичные примеры нарушения:
1) класс или интерфейс содержит несколько методов со схожей семантикой, которые используются разными клиентами;
2) интерфейс класса слишком разнороден и содержит методы, отвечающие за слабосвязанные операции.

Anti-ISP – Принцип тысячи интерфейсов. Интерфейсы классов разбиваются на слишком большое число составляющих, что делает их неудобными для использования всеми клиентами.

Принцип инверсии зависимостей (Dependency Inversion Principle)

Принцип гласит — «Зависимости внутри системы строятся на основе абстракций. Модули верхнего уровня не зависят от модулей нижнего уровня. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций». Данное определение можно сократить — «зависимости должны строится относительно абстракций, а не деталей».

Типичные примеры нарушения: использование синглтонов, сервис-локаторов или же создание ключевых зависимостей класса по ходу дела в закрытых методах.

Anti-DIP – Принцип инверсии сознания или DI-головного мозга. Интерфейсы выделяются для каждого класса и пачками передаются через конструкторы. Понять, где находится логика становится практически невозможно.

Резюме

Резюмируя всё выше изложенное, хотелось бы сделать следующую шпаргалку:

  • Принцип единственной ответственности (Single responsibility) - «На каждый объект должна быть возложена одна единственная обязанность» Для этого проверяем, сколько у нас есть причин для изменения класса — если больше одной, то следует разбить данный класс.
  • Принцип открытости/закрытости (Open-closed) - «Программные сущности должны быть открыты для расширения, но закрыты для модификации» Для этого представляем наш класс как «чёрный ящик» и смотрим, можем ли в таком случае изменить его поведение.
  • Принцип подстановки Барбары Лисков (Liskov substitution) - «Объекты в программе могут быть заменены их наследниками без изменения свойств программы» Для этого проверяем, не усилили ли мы предусловия и не ослабили ли постусловия. Если это произошло — то принцип не соблюдается
  • Принцип разделения интерфейса (Interface segregation) - «Много специализированных интерфейсов лучше, чем один универсальный» Проверяем, насколько много интерфейс содержит методов и насколько разные функции накладываются на эти методы, и если необходимо — разбиваем интерфейсы.
  • Принцип инверсии зависимостей (Dependency Invertion) - «Зависимости должны строится относительно абстракций, а не деталей» Проверяем, зависят ли классы от каких-то других классов(непосредственно инстанцируют объекты других классов и т.д) и если эта зависимость имеет место, заменяем на зависимость от абстракции.