- PVSM.RU - https://www.pvsm.ru -
Есть проблема с описанием и толкованием принципов развития архитектуры SOLID (авторства Роберта Мартина). Во многих источниках дается их определение и даже примеры их использования. Изучая их и пробуя использованием примерить на себя, стабильно ловил себя на мысли, что не хватает объяснения магии их применения. И пытаясь увидеть внутренние шестеренки, понять — и для меня значит запомнить — разложил их по своим "терминам-полочкам". Хорошо если это будет полезно еще кому-нибудь.
Приступим "жонглировать полочками" вышеозначенного подхода проектирования.
Single Responsibility Principle (SRP) принцип единственной ответственности
Один участок кода должен меняться только в ходе реализации одной цели. Если участок кода реализует две задачи и меняется для разного использования, то следует продублировать этот участок по экземпляру для каждой цели. Это очень важно, потому что требует отступить от общепринятого принципа устранения дублирования.
Целью этого принципа является устранение неявно вносимых ошибок, получаемых из-за того, что в разработке для участка кода, процедуры, класса, компонента (далее для объединения этих понятий используется термин [компонент]) существуют следующие инварианты:
Поэтому все места использования должны располагаться в зоне [Single Responsibility] единой ответственности, то есть изменяться и учитываться разом для любой решаемой программистом задачи).
Принцип относится как к участку кода, так и к компоненту, библиотеке, программе, комплексу программ, используемых в нескольких местах.
В многих источниках приводят пример класса с одной только "функцией" как идеал SRP и класс "божественного объекта", совмещающий все функции приложения, как антипаттерн. IMHO класс с одной только "функцией" это требование преждевременной оптимизации архитектуры кода, побуждающее на пустом месте писать множества классов (кодовых сущностей), при этом забывая, что отсутствие более одного места использования позволяет программисту быстрее оценить малое количество расположенного локально (в одном классе) взаимодействующего кода, чем анализировать внешние связи разрозненных кодовых сущностей, ответственных за свою "функцию". "Божественный объект" для крошечного приложения тоже вроде не сильный криминал — он позволяет запустить разработку: выделить все необходимые сущности и, записав их рядом, отделить от внешних объектов стандартной библиотеки и внешних модулей (создать живую клеточку и обособить её мембраной). В процессе роста и развития проекта существует множество приемов помогающих следовать SRP, один из них разделения на классы и минимизация количества "функций", за которые каждый класс отвечает (деление клеточек и их специализация в организме).
Здесь хотелось бы выписать набор приемов поддержания SRP, но эта работа пока не завершена (надеюсь "руки дойдут"). Из очевидных областей, где можно поискать эти приемы:
Open-Closed Principle (OCP) принцип открытости/закрытости
Развитие кода оптимально планировать так, чтобы для реализации программистом новых задач требовалось добавлять новый код, а старый код при этом в изменениях не нуждался. Код должен быть открыт (Open) для добавления и закрыт (Closed) для изменения.
Целью для этого принципа является минимизация трудозатрат и устранение неявно вносимых ошибок, получаемых из-за того, что в разработке существуют следующие инварианты:
Чаще в практике разработки программного обеспечения затраты добавления гораздо меньше затрат изменения, что делает очевидной пользу использования [Open-Closed] принципа. При этом существует масса приемов поддержания архитектуры программы в состоянии, когда реализация новой задачи сводится только к добавлению [компонентов]. Эта работа с архитектурой тоже требует затрат времени программиста, но как показывает практика в крупных проектах гораздо меньших чем использование подхода изменений старых процедур. И, конечно, это описание разработки — идеализация. Почти не бывает реализации задачи только добавлением или только изменением. В реальных задачах применяется смесь этих подходов, но OCP подчеркивает пользу в использовании подхода добавления.
И здесь хотелось бы выписать набор приемов поддержания OCP. Из очевидных областей, где можно поискать эти приемы:
Liskov Substitution Principle (LSP) принцип подстановки Барбары Лисков
Данный принцип ограничивает использование расширения базового интерфейса [базы] реализацией, закрепляя что каждая реалицация базового интерфейса должна иметь поведение как базовый интерфейс. При этом базовый интерфейс закрепляет поведение ожидаемое в местах его использования. И наличие в поведении реализации отличия от ожидаемого поведения, закрепляемого базовым интерфесом, приведет к возможности нарушения инварианта [2].
Данный принцип основывается и уточняет прием проектирования, основанный на абстрагировании. В этом подходе вводится абстракция — закрепляется некоторое базовое свойства и поведение, характерные множеству ситуаций. Например, [компонент-процедура] "Передвинуть в предыдущую позицию" для ситуаций: "Курсор в тексте", "Книга на полке", "Элемент в массиве", "Ноги в танце" и др. И за этим [компонентом] закрепляются (часто житейским опытом и без формализации) некоторые предпосылки и поведение, например: "Наличие передвигаемого объекта", "Повтор несколько раз", "Наличие порядка элементов", "Наличие закрепленных позиций элементов". LSP требует чтобы при добавлении новой ситуации использования для [компонента] выполнялись все предпосылки и ограничения базы. И ситуация "крупица в банке сахара" не может быть описана данной абстракцией, хотя у крупицы, конечно, есть позиция, есть позиции в которых крупица пребывала ранее, и есть возможность её в них передвинуть — отсутствуют лишь закрепленные позиций элементов.
Целью для этого принципа является устранение неявно вносимых ошибок, получаемых из-за того, что в разработке существуют следующие инварианты:
Очень часто для описания этого принципа приводят пример с Прямоугольном ([базой]) и Квадратом (реализацией). Ситуация class CSquare : public CRectangle
. В [базе] вводят операции работы с шириной и высотой (Set(Get)Width, Set(Get)Height). В реализации CSquare эти Set-операции вынуждены менять оба размера объекта. Мне всегда не хватало пояснения, что "неформально" в [базе] задается следующее ограничение: "возможность независимого использования Width, Height". В реализации CSquare оно нарушается, и в местах использования простая последовательность действий, основанная на использовании этой независимости: r.SetWidth(r.GetWidth()*2); r.SetHeight(r.GetHeight()*2)
— для реализации CSquare увеличит оба размера в 4 раза, вместо 2 раз предполагаемых для CRectangle.
IMHO данный принцип указывает на сложность отслеживания подобных неформальных ограничений, что при огромной полезности и большой частоте использования подхода разработки "база-реализация" требует особого внимания.
Interface Segregation Principle (ISP) принцип разделения интерфейсов; Dependency Inversion Principle (DIP) принцип инверсии зависимости
Эти два принципа очень близки по области своих требований. Оба неявно подразумевают полезность использования минимально возможного базового интерфейса, как инструмента взаимодействия двух [компонентов]: "клиент" и "сервер" — эти названия выбраны просто для идентификации. При этом общая информация, используемая [компонентами], сосредотачивается в базовом интерфейсе. Один [компонент] ("сервер") выполняет реализацию базового интерфейса, другой [компонент] ("клиент") обращается к этой реализации.
Целью для этих принципов является минимизация зависимостей компонентов, позволяющая производить независимые изменения их кода, если он не меняет базовый интерфейс. Независимость изменения компонентов уменьшает сложность и трудозатраты, если компоненты выполняют требования принципа SRP. Подобный подход возможен, потому что в разработке существуют следующие инварианты:
При этом понятно что "размер" базового интерфейса целесообразно минимизировать, откидывая не используемый функционал и ограничения, тем самым меньше ограничивая [компонент] реализацию по принципу (LSP)
Принципом ISP подчеркивается необходимость разделения (Segregation) интерфейса "сервера", если не весь его публикуемый функционал используется данным "клиентом". При этом выделяется только требуемая клиенту [база] и обеспечивается минимизация совместно ограничивающей информации.
И здесь хотелось бы выписать набор приемов поддержания DIP. Из очевидных областей, где можно поискать эти приемы:
Возвращаясь к заголовку, объясню почему выбрано "не понимать". Отрицание добавлено для того, чтобы подчеркнуть ошибками выстраданный и очень IMHO полезное правило. Лучше не понимать и потому не использовать технологию, чем понимать неправильно, принимать на веру, тратить на применение технологии свои ресурсы и в результате не получать при этом никакого полезного выхлопа кроме самоуспокоения и возможности хвастовства о причастности к модной технологии.
Спасибо за внимание.
Автор: ai_borisov
Источник [1]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/312478
Ссылки в тексте:
[1] Источник: https://habr.com/ru/post/444932/?utm_campaign=444932
Нажмите здесь для печати.