Шаблоны проектирования помогают нам унифицировать код под уже существующие лучшие практики. Используя нужный шаблон проектирования в определёной ситуации формирует стерильный код. Здесь описаны в общей сумме 17 паттернов: порождающие, структурные и поведенческие.
- Шаблоны проектирования
- Оглавление
- Порождающие паттерны
- Структурные паттерны:
- Поведенческие паттерны
Порождающие паттерны - это шаблоны, которые описают создание объектов.
Данный шаблон необходимо применять, когда необходим лишь один экземпляр класса, например для методов, которые описывают конфигурацию или создают CRUDGeneric класс.
Суть паттерна - описывать методы класса через модификаторы public и static. Таким образом с помощью модификатора public методы будут доступны всем, а модификатор static, позволяет получать доступ к методам класса без создания экземпляра класса.
Паттерн используется, к примеру в фреймворке Express, где объект app
является Singleton, с публичными методами по типу use
.
Пример кода: Singleton
Данный шаблон применяется когда необходимо клонировать полностью другой класс не углубляясь в реализацию этого класса.
Создать метод внутри класса, который позволяет полностью скопировать другой класс.
Глубинное клонирование классов для параллельной разработки.
Пример кода: Prototype
Данный шаблон применяется, когда необходимо создавать большие объекты с разными конфигурациями или фильтрами.
Суть паттерна - создать большое количество простых методов, через модификатор public, которые можно будет последовательно через точку реализовывать на объекте, например методы фильтрации данных приходящих из базы данных.
Объект позволяющий сделать объект запроса к базе данных, например агрегатор пакета mongoose.
Пример кода: Builder
Данный шаблон применяется при создании возможности на основе базового класса создавать разные системы, которые подключаются к базовому классу через единый базовый интерфейс этого класса.
Создать базовый класс системы и описать API к подключению этой системы, после чего от этого базового класса системы наследовать все методы API для подключения и описания конкретной другой системы.
Система отправки товара интернет-магазина, как базовая система со своим API, от которой могут наследоваться системы описывающее реализацию отправку Новой Почтой, Укрпочтой, или отправка курьером
Пример кода: Factory
Структурные паттерны - это шаблоны проектирования, которые типизируют структурирования кода.
Данный шаблон применяется, когда есть потребность реализовать одинаковые формат API, для разных сервисов, которые выполняют одну и ту же функциональность.
Создать общий интерфейс для каждого из класса сервиса, где описать все методы API. Реализовать каждый класс, такого сервиса через имплементацию этого интерфейса, после чего реализовать класс, в конструктор которого передается этот же интерфейс и в общие методы API группируются в один метод, при этом этот метод в себе ссылается на this аргумента реализующий этот интерфейс.
Приложение, по отправке сообщений. При этом оно должно реализовывать функциональность мгновенных и отложенных сообщений, а так же реализовывать эту функциональность для разного типа мессенджеров, таких как Telegram, WatchApp или Viber.
Пример кода: Bridge
Данный шаблон применяется, когда необходимо за простым API спрятать перечень сервисов или несколько бизнес-логик, которые не связаны между собой.
Создать классы сервисов, которые должны быть реализованы в ходе работы примитивного действия. Также создания класса, который реализовывает простой API интерфейс и под его же колпаком, внедряются экземпляры классов различных сервисов. Эти сервисы внедряются с модификатором private поскольку сервисы должны быть инкапсулированы и не должны быть допустимы к использованию вне класса.
Клавиша отправки уведомления на почту, которая под капотом имеет следующий сайд эффект в формате реализации нескольких логик:
- в виде логгирования (добавления реализации LoggerService).
- сервис автологгирования, который автоматически подтягивает данные из регистрации, как данные для логирования в почтовом, другом, сервисе (LoginService).
- реализация сервиса шаблона, который реализовывает разметку данного типа уведомления (MailService).
Пример кода: Facade
Данный шаблон используется, когда необходимо подсоединить два различных класса с различными типами данных.
Создать или получить класс, который имеет неподходящий тип данных для портирования (соединения с другим классом). Создать класс, который бы наследовался от этого класса, и одновременно с этим, внутри своего конструктора принимает класс, к которому нужно адаптить, после чего внутри такого класса создать метод, который бы уже реализовывал приходящие данные не адаптированого класса в нужный нам формат.
Получение данных от стороннего сервиса, и которые необходимо как-то обработать, для получения необходимого формата данных, которые пользуются другим сторонним сервисом.
Пример кода: Adapter
Данный шаблон используется, когда необходимо с работой отдельно взятого класса, происходили сопутствующие действия (side effects).
Создать общий интерфейс, который имплементит каждый из классов: класс, который выполняет логику и класс, который проксирует свою логику поверх первого класса. Необходимо понимать, что Proxy не расширяет один класс другим, как при классическом extends, а именно что оборачивает одну логику поверх другой.
Получение данных из базы данных, только если данные пользователя подходят под авторские права. Так логику доступа к базе данных описывается в просто классе, а логика проверки на соответствие пользователя в классе, который проксирует первый.
Пример кода: Proxy
Данный шаблон используется при реализации древовидных бизнес-логик и инкапсулирования их в простое API.
Создать абстрактный класс с абстрактными методами, которые наследуются разными вариантами реализации абстрактного класса, в ходе описания реализующих классов абстрактные классы перезаписываются и модифицируются модификатором overrite, для защиты от исчезновения методов в абстрактном классе. После чего создается результирующий класс, который реализовывает те же абстрактные методы в простое API.
Интернет-магазин имеет возможность продажи как в готовой заводской упаковке - первая ветка дерева, так и cамостоятельно упаковать несколько товаров - другая ветка дерева. Необходимо реализовать метод, который обходил бы дерево и давал итоговую стоимость как в случае с упаковкой, так и без.
Пример кода: Composite
Поведенческие паттерны - это паттерны, которые унифицируют поведение объектов, классов или функций.
Данный шаблон используется при реализации цепочки различных обработчиков
Создать абстрактный класс, который в себе реализовывает 2 метода:
- next - метод принимающий другой обработчик. Этот, другой, обработчик передаётся по цепочке.
- handle - условие. Если условие возвращает true, то данные передаются по цепочке следующему обработчику. Если нет - выход из цепочки.
Middleware в работе express.js или nest.js
Пример кода: Chain of command
Паттерн в основном используется на Frontend, чтобы связать компоненты, которые разрознено должны знать о друг друге.
Например, какое-то событие EventEmitter необходимо выпустить какое-то событие, при этом, это событие, когда воспроизводится, то должно производить логирование этого события, а также само событие попадать в кэш. Таким образом выходит, что каждый из этих компонентов имеет между собой какие-то связи, таким образом создавая лишние зависимости. Mediator же является посредником между всеми этими компонентами, таким образом отвязывая все зависимости между компонентами и завязывает их только на себе.
Например, компонент формы
Пример кода: Mediator
Паттерн предназначен для отделения отдельной задачи, как входной точки в модуль, в отдельную команду, через которую и управляется соединения модуля с другими модулями, а точнее управление бизнес-логики между этими модулями.
При написании приложений на Nest.js используются модули, таким образом отделяя логику по отдельным сущностям, но что если одна сущность используется в различных других модулях? Например, UserService используется непосредственно в своём контроллере, шлёт какие-то данные по WebSockets через Gateway о том, что такой-то пользователь был создан или куда-то вошел, а также дополнительно ещё идёт какая-то синхронизация очереди в базе данных.
Таким образом при изменении бизнес логики самого UserService, например отложенное добавления пользователя, вынуждает менять логику каждой реализации с контроллером, веб-сокетами или базой данных.
Чтобы этого избежать создаётся команда, например команда getUser и уже в рамках этой команды изменяется самая бизнес логика, поскольку эта команда является входной точкой к сущности User.
Используется например как CQRS - разделения запросов чтения и записи.
Пример кода: Command
Состояние отдельного элемента.
Когда у объекта может быть несколько состояний.
По сути замена горы if`ов или же switch case на плавный переход к классам, которые имеют свои условия и ограничения. Получается, что вместо того, чтобы хранить всё состояние внутри нашего компонента, организовывается ссылка на текущее состояние у объекта, которая является надклассом над состоянием этого компонента (оно является абстрактным). После чего вся логика которая относится к каждому из этапов лежит лишь в классе этого этапа.
Например, у нас есть статья в блог, которая может быть в трёх состояниях:
- черновик, если статья закончена, она отправляется на модерацию.
- на модерации, при этом если статья не проходит модерацию, то статья откатывается на черновик, иначе переходит в состояние публикации.
- опубликована, если статья не понравилась, то снять её с публикации и вернуть в черновик.
Пример кода: State
Удаление дублирования кода, путём интегрирования его в один класс.
Выделение схожих алгоритмов поведения в общие классы, после чего использования эти алгоритмы через классы путём создания общего интерфейса для одного алгоритма и уже различные стратегии имплементируют этот интерфейс.
Часто используются для авторизации, где есть наборы схожих алгоритмов, чтобы проверить авторизован ли пользователь. Но при этом может быть разные провайдеры: JWT, GitHub, GoogleAuthToken и так далее - к примеру Passport.js
Пример кода: Strategy
Когда необходимо обойти ряд объектов по какому-то принципу - по приоритету или по зависимостям. То есть паттерн используется в любом месте, где необходимо сделать какой-то обход коллекции.
Создавая общий интерфейс итератора, от которого имплементируются классы различных вариантов обхода. После этого в каждый список можно добавить этот интерфейс, через который реализовываются классы итераторов.
Типы обхода различных коллекций
Пример кода: Iterator
Метод используется, когда работа определенного объекта связанного с разными объектами связана общим шаблоном действий.
Создаётся некий абстрактный класс, который в себе имеет различные методы. При этом часть этих методов может быть абстрактными и не зависеть от API, а часть может быть обычными, поскольку завязаны на самом API. После этого создаются различные классы этих API, которые специфичные каждый для своего API.
Например, есть форма заявки, которая работает с несколькими API
Пример кода: Template Method
Создание моста из события и функции, которые реагируют на это событие и после этого делают соответствующую логику.
Есть интерфейс, который описывает схему работы непосредственно объекта генерирующего событие, а также интерфейс наблюдателя. У интерфейса наблюдателя есть метод подписаться на прослушку различных событий. Получается, чтобы получить обновление данных, не нужно к объекту ходить, он сам генерирует событие и остальные объекты могут получать уведомление о том, что событие сработало.
Ситуации, где есть подписчики на событие и есть функция, которая генерирует это событие.
Пример кода: Observer