Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Эта серия статей – вольный и очень краткий пересказ книги Роберта Мартина (Дяди Боба) «Чистая Архитектура», выпущенной в 2018 году. Предыдущая часть здесь.
Принципы дизайна
Принципы SOLID говорят нам, как нам соединять наши функции и данные в классы, и как эти классы должны быть взаимосвязаны. То есть эти принципы относятся к уровню модулей (mid-level). Однако они также применимы и к уровню компонентов (high-level архитектура).
Принципы SOLID зародились в конце 80-х и стабилизировались в начале 2000-х.
SRP: Принцип единственной ответственности
Исторически этот принцип был сформулирован так: модуль должен иметь одну и только одну причину для изменения.
Однако Дядя Боб предлагает другую, более точную формулировку: модуль должен быть ответственным за одного и только за одного актёра.
Модуль – это некоторое связанное множество функций и структур данных.
Пример нарушения принципа: класс Employee
с тремя методами calculatePay()
, reportHours()
, save()
. Все эти три метода относятся к разным актёрам. calculatePay()
– это бухгалтерия, reportHours()
– это отдел кадров, save()
– это администратор базы данных. Если один из актёров просит внести изменения в свой метод, то это может сломать логику других методов. Также это вносит трудности при слиянии изменений от разных актёров.
Решение проблемы – вынести функции в отдельные классы. В каждом классе будут только та логика, которая относится к соответствующему актёру.
OCP: Принцип открытости/закрытости
Программный артефакт должен быть открытым для расширения, но закрытым для изменения.
Простыми словами: артефакт должен быть расширяемым без необходимости изменения его кода.
Этот принцип очень важен для архитектурных компонент.
Пример: финансовый отчёт умеет отображаться на веб-странице. Теперь клиент просит печатать его на чёрно-белом принтере. Если OCP выполняется, то доля кода, которого придётся изменить, будет близка к нулю. Чтобы этого достичь, компонент представления отчётов должен быть защищённым от изменения, а для этого он должен зависеть не от конкретной реализации это представления, а от интерфейса. Далее конкретные компоненты будут добавляться путём расширения, т.е. имплементации интерфейса.
Компоненты, которые находятся на более высоких уровнях иерархии зависимостей, должны быть защищены от изменений в компонентах более низкого уровня.
Проявлением нарушения OCP является также зависимость от вещей, которые не используются.
LSP: Принцип подстановки Лисков
Принцип замещения: если S является подтипом T, тогда объекты типа T в программе могут быть замещены объектами типа S без каких-либо изменений желательных свойств этой программы.
Другими словами, все реализации интерфейса должны строго соответствовать контракту, описанном в этом интерфейсе.
Пример нарушения LSP: классическая проблема квадрата/прямоугольника.
Пример более высокоуровневого нарушения LSP: агрегатор сервисов такси, где в одном из сервисов название одного из параметров REST-запроса отличается от остальных. Тогда добавление этого сервиса приведёт к нарушению работы всего агрегатора. Эту проблему можно решить, например, путём вынесения маппинга параметров во внешнюю БД.
ISP: Принцип разделения интерфейса
Программные сущности не должны зависеть от методов, которые они не используют.
Толстые интерфейсы нужно разделять на более мелкие, чтобы сущности знали только о тех методах, которые необходимы им в работе.
В динамических языках меньше деклараций зависимостей, поэтому они подвержены этой проблеме меньше. Однако ISP распространяется и на более высокий уровень, где он означает, что не должно быть зависимостей от компонент, которые содержат слишком много лишнего. Например, зависимость от фреймворка, который тянет за собой специфичную базу данных. Изменения в этой базе данных приводят к необходимости редеплоя всего компонента.
DIP: Принцип инверсии зависимостей
Программные сущности должны зависеть от абстракций, не от деталей.
В Java это значит, что модули должны зависеть от интерфейсов или абстрактных классов.
Однако существуют платформенные классы, которые являются чрезвычайно стабильным, например, java.lang.String
. На такие классы этот принцип не распространяется. С другой стороны, есть наша система, в которой есть много часто изменяемых классов. Для них этот принцип нужно соблюдать.
Абстракции должны быть стабильными. Нужно делать зависимости от них, а не от их конкретных реализаций, которые могут меняться слишком часто.
Не рекомендуется наследоваться от часто изменяемых классов.
Как создавать инстансы конкретных классов, если мы не можем от них зависеть? Для этого есть несколько решений:
Шаблон Абстрактная Фабрика
Контейнеры, которые сами внедряют зависимости (XML, аннотации)
Сервисы
В дальнейшем к принципу DIP мы будем обращаться чаще, чем к остальным принципам, потому что он диктует нам важнейшее правило построения грамотной архитектуры: Правила Зависимостей.
Продолжение следует...