"Принципы дизайна" и "Принципы организации компонентов"

"Принципы дизайна" и "Принципы организации компонентов"

Цель принципов - создать программные структуры, которые будут:

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

Роберт Мартин начал работу над этими принципами с 1980 г, однако лишь в 2004 г Майкл Физерс сообщил в электронном письме,что если взять первые буквы из них можно составить слово SOLID

  • (S)Принцип единственной ответственности -

Модуль должен иметь только одну причину для изменения (отвечать за одну группу пользователей – актора). Проблема может возникнуть, когда в одном классе содержатся публичные методы, используемые концептуально разными акторами. При этом, они могут обращаться к одним и тем же закрытым методам внутри класса. Тогда при внесении изменений в логику работы методов, относящихся к одной группе пользователей, она может непреднамеренно измениться и для другой группы. Поэтому необходимо разделять классы и модули по акторам, которых они обслуживают.

Single-responsibility principle(SRP)

  • (O)Принцип открытости/закрытости

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

Open–closed principle(OCP)

  • (L)Принцип подстановки Барбары Лисков

Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы. Стандартный пример невыполнения этого принципа — проблема “прямоугольник-квадрат”. Математически квадрат является прямоугольником, но, если подставить квадрат в объект типа прямоугольник, можно столкнуться с неожиданным поведением, изменяя его длину и высоту по отдельности.

  • (I)Принцип разделения интерфейсов

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

Interface segregation principle(ISP)

  • (D)Принцип инверсии зависимостей

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

Dependency inversion principle(DSP)

Под понятием компонентами принято понимать единицы развертывания. Например, это может быть библиотека или исполняемый файл, в Java таковым назвать можно jar-файлы. Принципы компонентов в разработки ПО помогают сгруппировать классы в компоненты и сделать их более структурируемыми и управляемыми.

Принципы разделяются на две группы: связность(component cohesion) (какие классы стоит поместить?) и сочетаемость(component coupling) компонентов (как должны взаимодействовать друг с другом?).

Рассмотрим три принципа связности компонентов:

  • Принцип эквивалентности повторного использования и выпусков. Единица повторного использования есть единица выпуска. Этот принцип требует, чтобы компоненты проходили процесс выпуска и получали версии. В один компонент должны объединяться классы c одной целью, которая будет выражена в очередном выпуске. По документации же пользователи и разработчики должны понимать, нужно ли им переходить на новую версию.

(REP): The ReuseReleaseEquivalencePrinciple

  • Принцип согласованного изменения. В один компонент должны объединяться классы, изменяющиеся по одним причинам. Этот принцип эквивалентен SRP, но для компонентов. Идея заключения вместе сущностей, которые могут изменяться в одно и то же время и по одним причинам, является одной из ключевых идей архитектуры ПО, поэтому она проходит красной нитью по всем структурным уровням.

(CCP): The CommonClosurePrinciple

  • Принцип совместного повторного использования. Не вынуждайте пользователей компонента зависеть от того, чего им не требуется. Главная мысль этого принципа в объединении в компоненты тех классов, которые имеют множественные зависимости друг от друга. Кроме того, нужно избегать слабых зависимостей от других компонентов — даже зависимость от одного редко используемого класса наверняка потребует повторной компиляции и тестирования всего компонента в случае изменений в зависимом.

(CRP): The CommonReusePrinciple


Перечисленные принципы несколько противоречат друг другу и преследуют разные цели. REP стремится к объединению классов для обеспечения удобства пользователя, CCP — для удобства разработки и сопровождения, а CRP призывает разделять компоненты, чтобы избежать лишних выпусков. Задача архитектора — найти золотую середину среди них.

Теперь перейдем ко второй группе принципов. Сочетаемость компонентов:

  • Принцип ацикличности зависимостей. Нельзя допускать циклов в графе зависимостей. Это позволяет разбить проект на компоненты, которые будут выпускаться независимо. При возникновении цикла между зависимостями для тестирования и выпуска новой версии придется отладить и подготовить все компоненты, входящие в цикл. Они превращаются в один большой компонент.

(ADP) The AcyclicDependenciesPrinciple

  • Принцип устойчивых зависимостей. Зависимости должны быть направлены в сторону устойчивых компонентов. Нужно использовать ссылки на компоненты, которые будут редко меняться и избегать зависимостей от изменчивых компонентов. Это добавит гибкость в разработке, так как всегда нужны компоненты, которые можно легко изменять. Если же создать большое число зависимостей от такого компонента, эта возможность легких изменений испарится.

(SDP) The StableDependenciesPrinciple

  • Принцип устойчивости абстракций. Этот принцип проводит связь между устойчивыми и абстрактными компонентами. Компоненты, которые содержат интерфейс для высокоуровневой бизнес-логики, должны быть абстрактными и почти не меняться. Реализации же этого интерфейса должны быть неустойчивыми — избегайте множественных зависимостей от таких компонентов.

(SAP) The StableAbstractionsPrinciple

Принципы организации компонентов показывают, почему их проектирование сверху вниз — не лучшая идея. Формирование новых компонентов часто происходит не только для выделения некоторой функциональности или отделения бизнес-логики; часто нам приходится создавать компоненты для избавления от цикличных зависимостей или других проблем, которые усложняют поддержку кода. Поэтому проектировать компоненты до начала программирования и создания конкретных классов обычно не имеет смысла.

Р. Мартин - Uncle Bob 👇

Архитектура программной системы - это форма, которая придается системе её создателями. Эта форма образуется делением системы на компоненты, их организацией и определением способов взаимодействия между ними.
Цель формы - упростить разработку, развертывание и сопровождение программной системы, содержащийся в ней.

Главная стратения такого упрощения в том, чтобы как можно дольше иметь как можно больше вариантов

Создание архитектуры состоит в проведении границ между программными элементами. Отделять границами нужно все, что не относится к бизнес-правилам. Это детали, выбор которых можно отложить и впоследствии без труда заменить. Например, это может быть ввод/вывод или хранилище данных. Границы могут не иметь физического представления — распологаться на уровне исходного кода. Такой вариант архитектуры называется монолитом. Также границы могут представлять из себя отдельные компоненты развертывания, локальные процессы или микросервисы.

Еще один признак хорошей архитектуры заключается в следующем: посмотрев на высокоуровневую структуру пакетов исходного кода вашего продукта, вы должны понимать, в чем суть вашего приложения. Если вместо того, чтобы увидеть, что это система документооборота или онлайн-магазин, вы видите только то, что это ASP.NET MVC или Spring приложение, то у вас проблемы. Архитектура должна отражать бизнес-правила и варианты использьвания, а фреймворки — это детали.

Все это приводит к многоуровневому варианту чистой архитектуры. На верхнем уровне находятся сущности системы. Каждая сущность представляет собой бизнес-правила предприятия — правила, которые действуют и в отсутствие электронного приложения. Ниже находятся варианты использования. Это бизнес-правила, которые относятся конкретно к вашему продукту. Далее следует уровень адаптеров интерфейсов. На нем данные из сущностей и вариантов использования преобразуются в формат удобный для внешних агентов. Это могут быть различные шлюзы, презенторы и т.д. Паттерн MVC целиком находится на этом уровне. На последнем же уровне находятся фреймворки, внешние хранилища и прочие детали.

Помимо разделения уровней границами есть еще одно правило, применимое к такой архитектуре. Зависимости в исходном коде должны быть направлены вверх, в сторону высокоуровневых политик. Это правило вытекает из тех принципов, что мы уже обсудили ранее. Такой вариант архитектуры позволит создать легко тестируемое и поддерживаемое приложение.



В будущем поговорим о том, что такое Кричащая архитектура, а на сегодня всё...