Наиболее эффективные способы применения паттернов проектирования при разработке сложных программных продуктов

Наиболее эффективные способы применения паттернов проектирования при разработке сложных программных продуктов

При создании в объектно-ориентированной парадигме больших высоконагруженных программных продуктов с необходимостью масштабирования, разработчики сталкиваются с множеством задач, которые препятствуют созданию эффективных систем. Часть этих задач уже были решены и подходы к их решению шаблонизированы. Эти шаблоны получили название “Паттерны проектирования” и используются для решения широкого спектра задач разработки. Особенно эффективным является применение паттернов не по одному, а совокупно, для большей гибкости и результативности.

Авторы публикации

Рубрика

IT-Технологии

Журнал

Журнал «Научный лидер» выпуск # 20 (65), май ‘22

Дата публицакии 16.05.2022

Поделиться

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

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

Шло время и такой подход перестал удовлетворять нуждам индустрии из-за низкой гибкости. Поэтому  потребовался новый подход к написанию программного обеспечения. Так появилось объектно ориентированное программирование, суть которого заключается в представлении каждого логического блока программы в виде класса, объекты которого могут быть созданы для выполнения задач. Объекты классов должны обмениваться друг с другом сообщениями, возвращая значения с помощью методов и сохраняя состояние с помощью атрибутов.

Такой подход дал возможность сделать исполнение кода нелинейным, гибким, поскольку управление программой передавалось не от строки к строке, а от объекта к объекту. Более того, объектно-ориентированное программирование дало возможность многократно переиспользовать написанный код, контролировать область видимости данных и выйти на недостижимый для процедурной парадигмы уровень абстракции. Во многом так произошло из-за того, что эта парадигма реализует концепции, которые существуют в реальном мире, например, наследование[2].

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

В этот момент паттерны вступают в игру. В 1994 году знаменитая “Банда четырех” выпускает книгу «Приемы объектно-ориентированного проектирования. Паттерны проектирования», которая получила множество наград и до сих пор остается фундаментальной для программных инженеров любого уровня. Во-первых, эта книга дает общее понимание ОО-парадигмы программирования, фиксируя набор общих правил для разработчиков. Во-вторых, в ней собраны и описаны 23 решения распространенных архитектурных задач, известные как паттерны или шаблоны проектирования.

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

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

Банда четырех выделила все шаблоны проектирования в три группы: порождающие, структурные и поведенческие.[6] Каждая группа отвечает за решение проблем в своих областях.

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

Структурные паттерны - набор проектировочных приемов, которые отвечают за вопросы о грамотной композиции классов в более массивные структуры, без усиления зависимостей, нагромождения лишних сущностей и усложнения интерфейсов. Отличным примером этого принципа является паттерн Фасад, суть которого - предоставление небольшого и удобного интерфейса к совокупности объектов, которые должны взаимодействовать между собой. Простым примером из жизни может послужить кнопка включения компьютера, за интерфейсом “вкл/выкл” скрывается сложное взаимодействие десятков компонентов системы, запуск операционной системы и так далее. Применение структурных паттернов позволяет делать узкие интерфейсы для сложных объектов, не плодить множество сущностей без надобности, заменяя наследование композицией, ослаблять зависимости между компонентами системы с помощью работы с абстракциями, вместо конкретных реализаций.

Поведенческие паттерны - это группа архитектурных решений, позволяющих нескольким равноправным объектам системы кооперироваться для выполнения задачи, которую ни один из них не способен выполнить самостоятельно. В таких паттернах большую роль играет правильное распределение ответственностей между объектами разных классов, чтобы каждый участник процесса дополнял собой других. Такой подход позволяет сохранять принцип единой ответственности, чтобы один огромный объект не выполнял всю задачу целиком, уменьшая тем самым связность в системе. Также поведенческие паттерны уменьшают сцепленность системы, поскольку не имеют жестких зависимостей между собой, общаясь через узкие интерфейсы, как в “цепочке обязанностей”,  или через посредников, как в “команде” или в “посреднике”. Простым примером поведенческого паттерна в жизни является обслуживание человека в ресторане. Клиент делает заказ, зная только то, что он хочет поесть, официант передает заказ на кухню, зная только о том, что заказал клиент и где находится повар. Повар готовит блюдо, зная только то, как его приготовить. Все три участника процесса четко разделены по своим обязанностям, четко выполняют свою роль и в сумме выполняют действие, на которое не способны по одному - клиент уходит сытым и довольным.[7]

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

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

В случае, когда реализаций в системе может быть множество, применяется структурный паттерн Мост (Bridge). Его суть заключается в разделении абстракции и реализации в системе для обеспечения возможности  замены реализаций между собой. Этот механизм осуществляется за счет композиции[6]. Применение данного паттерна решает проблему управления несколькими реализациями одной абстракцией. Однако остается проблема того, что через мост может быть передана некорректная реализация, с которой абстракция не умеет взаимодействовать. Для решения этой проблемы вместо строительства моста непосредственно между абстракцией и реализациями можно построить мост между абстракцией и абстрактной фабрикой реализаций.

Абстрактная фабрика - порождающий паттерн, который позволяет создавать объекты классов одного семейства отдельно от их применения. Классу фабрики делегируется менеджмент создания семейства объектов. Такая практика нужна для того, чтобы контролировать, что не создается объект, не присутствующий в семействе[4].

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

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

Для реализации создания семейств объектов можно прибегнуть к имплементации паттерна Фабричный метод. Таким образом будет реализован принцип разделения создания объектов от их прямого использования. Однако остается не решенной задача по дополнительному присваиванию каждому новому объекту вложенного объекта. С этим может помочь имплементация паттерна Прототип. Суть в том, что каждый объект, который попадает под действие паттерна Прототип имеет возможность создать свою полную копию, скрывая детали реализации от клиентского кода, которому потребовался новый объект[6].

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

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

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

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

Список литературы

  1. URL: https://refactoring.guru/ru/design-patterns/
  2. Тепляков С. “Паттерны проектирования на платформе .NET” | СПб.: Питер, 2015. — 320 с.
  3. Уринцов А.И. Структурный анализ и проектирование распределенных экономических систем Экономика и математические методы. | Т. 33. № 4. 1997 — С. 141-152.
  4. Уринцов А.И. Система формирования и принятия решений в условиях информатизации общества Москва, 2008.
  5. Эванс Эрик. “Предметно-ориентированное проектирование (DDD). Структуризация сложных программных систем” | ООО “И. Д. Вильямс” 2010 г. — 448 с.
  6. Вайсфельд Мэтт, “Объектно-ориентированное мышление” | СПб.: Питер, 2014 — 304 с.
  7. Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. “Паттерны объектно-ориентированного программирования” | СПб.: Питер, 2021 — 448 с.
  8. Александр Швец, “Погружение в паттерны проектирования” | Refactoring.Guru, 2021 — 399 с.
  9. Фримен Э., Фримен Э., Сьерра К., Бейтс Б. “Паттерны проектирования” | СПб.: Питер, 2011 — 656 с.
  10. Босуэлл Д., Фатчер Т. “Читаемый код, или программирование как искусство” | СПб.: Питер, 2012 — 208 с.

Предоставляем бесплатную справку о публикации,  препринт статьи — сразу после оплаты.

Прием материалов
c по
Осталось 5 дней до окончания
Размещение электронной версии
Загрузка материалов в elibrary