Перечитывая «Чистую архитектуру» (3): принципы SOLID

Tommy Cheese | · Время чтения: 3 мин

Перечитывая «Чистую архитектуру» (3): принципы SOLID

При построении программных модулей преследуют три основные цели:

  • Сделать ПО устойчивым к изменениям.
  • Сделать ПО более понятным.
  • Создавать компоненты, пригодные для повторного использования в разных программных системах.

Принципы SOLID прежде всего объясняют, как объединять данные и функции в классы, а затем связывать классы в программу. Они направляют проектирование модулей; на уровне архитектуры действуют другие принципы.

SOLID включает принцип единственной ответственности SRP, принцип открытости/закрытости OCP, принцип подстановки Лисков LSP, принцип разделения интерфейсов ISP и принцип инверсии зависимостей DIP.

Принцип единственной ответственности — SRP

SRP определяет для каждого модуля одну ответственность: должна существовать только одна причина для его изменения. Каждый программный модуль должен отвечать только перед одной категорией действующих лиц.

На уровне компонентов SRP называется принципом общей закрытости — CCP.

Что произойдёт, если проектировать модуль без соблюдения SRP? Рассмотрим пример.

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

  • CalculateSalary: расчёт зарплаты.
  • CalculateTime: расчёт рабочего времени.
  • Save: сохранение информации.

Три отдела спокойно пользуются программой. Однажды у финансового отдела меняется порядок расчёта зарплаты, и его специалисты изменяют функцию CalculateSalary. После этого происходит неожиданное: одновременно меняется зарплата у кадровой службы и отдела разработки…

Принцип открытости/закрытости — OCP

«Приветствовать добавление, сопротивляться изменению!»

OCP утверждает: чтобы программную систему было легче менять, её устройство должно позволять изменять поведение добавлением нового кода, а не только правкой существующего. Хорошо спроектированное ПО легко расширяется и сопротивляется изменению.

OCP относится не только к классам и модулям, но и к компонентам. Он вырос из идеи независимости от устройств. Например, инверсия зависимостей позволяет оформить устройства IO как плагины, чтобы легко добавлять новые, не изменяя прежние.

OCP реализуют, разделяя систему на компоненты и организуя их зависимости в иерархию, при которой изменения низкоуровневых компонентов не затрагивают высокоуровневые.

Принцип подстановки Лисков — LSP

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

Суть LSP — взаимозаменяемость: если для каждого объекта o1 типа S существует объект o2 типа T, причём поведение программы P, работающей с T, не меняется при замене o2 на o1, то S можно назвать подтипом T.

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

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

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

Инверсия зависимостей — DIP

DIP утверждает: код высокоуровневых правил не должен зависеть от кода низкоуровневых деталей. Напротив, низкоуровневая реализация должна зависеть от высокоуровневых правил.

Для простого объяснения инверсии зависимостей нужно различать поток управления и зависимости исходного кода. Пусть управление идёт от компонента A к B. В таком устройстве B должен знать реализацию A, а A должен подключать модуль B, поэтому зависимость кода тоже направлена от A к B. Поведение системы определяет поток управления, а он — зависимости кода, и у архитектуры не остаётся выбора. После введения интерфейса B лишь реализует его; связь с B проходит через интерфейс, а A подключает интерфейс. A больше не нужно подключать модуль B. Направления потока управления и зависимости исходного кода становятся противоположными — это и есть инверсия зависимостей.

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

Из DIP следуют несколько правил написания кода:

  • Чаще используйте абстрактные интерфейсы и по возможности избегайте изменчивых конкретных классов реализации.
  • Не создавайте производные классы от конкретных классов реализации.
  • Не переопределяйте (override) функции, содержащие конкретную реализацию.
  • Избегайте в коде имён, привязанных к конкретной реализации, и других легко меняющихся сущностей.

(Конец третьей статьи)

comments powered by Disqus