ru:https://highload.today/blogs/patterny-proektirovaniya-dlya-frontenda-i-pitstsa-chto-obshhego-razbiraem-na-ponyatnyh-primerah/ ua:https://highload.today/uk/blogs/paterni-proyektuvannya-dlya-frontendu-ta-pitsa-shho-spilnogo-rozbirayemo-na-zrozumilih-prikladah/
logo
Фронтенд      18/01/2023

Паттерны проектирования для фронтенда и пицца — что общего? Разбираем на понятных примерах

Вікторія Цукан BLOG

Frontend Developer у NIX

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

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

Паттерны в IT — что это

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

Понятие паттернов ввел архитектор Кристофер Александер. Он заметил, что после ремонта и обустройства квартир его клиенты часто переделывали что-то под себя. Выясняя, что не устраивает людей, он обнаружил несколько шаблонов – наиболее целесообразное для многих расположение окон и стен, высоту потолков и т.п. Все наработки легли в основную его книги – A Pattern Language.

Через некоторое время четыре программиста прочли эту книгу и так вдохновились ею, что написали свою. Это были Эрих Гамма, Ричард Хелм, Ральф Джонсон и Джон Влиссидес. В историю они вошли как Банда четырех (Gang of Four). Их книга – Design Patterns – описывает шаблоны проектирования для объектно-ориентированных систем. Авторы выделили базовые паттерны, от которых произошли другие. Именно эти шаблоны мы рассмотрим в статье.

Специалисты выделили три категории паттернов:

  • порождающие — отвечают за создание объекта;
  • структурные – предназначены для описания иерархии;
  • поведенческие – определяют взаимодействие элементов.

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

Курс QA Manual (Тестування ПЗ мануальне).
Навчіться знаходити помилки та контролювати якість сайтів та додатків.
Записатися на курс

Порождающие паттерны

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

Prototype

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

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

В коде этот паттерн реализуется достаточно просто. В примере ниже приведен объект прототипа с информацией о пицце. С помощью Object.create создаются нужные копии:

Пример кода

Пример кода

Factory

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

Этот паттерн имеет несколько преимуществ. В первую очередь сохраняется независимость между Фабрикой и производимыми ею объектами. Также следует single responsibility principle — принцип единой ответственности. Согласно ему, вся логика создания объекта находится внутри класса и не контролируется снаружи. Также здесь действует open-closed principle – принцип открытости/закрытости. То есть, когда все объекты независимы друг от друга. Но есть и существенный недостаток. Этот паттерн становится слишком большим и сложным, когда прописано много логики. Его тяжело поддерживать, если нужно создавать множество объектов.

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

В коде этого паттерна есть два объекта в первых строках: пиццы «Маргарита» и «Карбонара». Они содержат определенные данные: состав пиццы, ее размеры и т.п. Далее размещается сама Фабрика с информацией pizzaClass – это конфигурация пиццы. Затем метод createPizza, который позволяет установить саму пиццу. Фабрика принимает название пиццы и под капотом ассоциирует это с определенным объектом или сущностью, содержащим всю необходимую информацию. В результате вы отдаете название метод createPizza — и Фабрика возвращает нужный объект:

Пример кода

Пример кода

Builder

Позволяет создавать объекты шаг за шагом, таким образом контролируя этот процесс. Также сохраняется принцип единой ответственности. Хотя мы и управляем созданием объекта, но логика находится внутри паттерна. Недостаток Строителя подобен тому, что имеет Фабрика: если требуется сложный, универсальный объект, то этот паттерн будет трудно поддерживать из-за большого количества методов внутри.

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

На иллюстрации показана реализация этого паттерна. Вызываем метод changeName и по одному добавляем ингредиенты:

Пример кода

Пример кода

Структурные паттерны

Описывают взаимосвязь нескольких сущностей объектов. Их цель – построить гибкую и эффективную систему с четкой иерархией. Сейчас поговорим о ключевых структурных паттернах.

Adapter

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

Этот паттерн придерживается принципов единой ответственности и открытости/закрытости. Логика перехода между форматом находится отдельно от бизнес-логики. Можем добавлять и убирать адаптеры, которые не будут связаны. Относительно минусов: применение Адаптера не всегда оправдано. Иногда проще добавить изменения в бизнес-логику, а не встраивать паттерн.

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

Вернемся в нашу пиццерию. В примере кода есть две пиццы: «Маргарита» и кастомная, в соответствии с полем для названия и без него. Ниже вы можете увидеть, как создаются обе пиццы. Маргарита в этом случае создается обычным способом по вызову new MargaritaPizza(). Для «безымянной» пиццы используется адаптер, устанавливающий ей имя. Таким образом, margarita и adoptedPizza имеют одинаковый интерфейс. Теперь после обработки заказа в чеке все будет обозначено как следует:

Пример кода

Пример кода

Decorator

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

Приведу пример в виде скидки на некоторые пиццы. Вы можете добавлять в коде ко всем пиццам поле Скидки или логику с таким полем, но это будет долго. Проще использовать декораторы. У нас есть класс simplePizza. Это стандартная пицца с методом getCost, возвращающим стоимость. Также есть декоратор PizzaWithDiscount, который принимает пиццу и размер скидки, перезаписывает в пиццу getCost, а затем ставит новое значение с учетом скидки:

SQL для аналітики.
Навчіться аналізувати дані за допомогою власного SQL коду.
Зареєструватися
Пример кода

Пример кода

Также здесь можно увидеть метод getCost return pizza.getCost – минус discount. Дисконт – это конкретная сумма, а не процент. В конце мы создаем пиццу через SimplePizza и проверяем ее дефолтное значение. Например, 100. Далее вращаем пиццу в декоратор PizzaWithDiscont и устанавливаем скидку 20. В следующий раз после проверки стоимости паттерн сосчитает скидку, перезапишет метод и вернет нужный результат — 80.

Поведенческие паттерны

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

Chain of responsibility

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

Это напоминает звонок в службу техподдержки. Сначала попадаете на первый обработчик типа «нажмите такую-то кнопку, если хотите поговорить с оператором». Дальше вас перенаправляет на следующий обработчик – оператора. Рассказываете ему свою проблему с техникой, и оператор предлагает совершить простые операции. Если ничего не помогает, вас перенаправляют на другого обработчика – технического эксперта. Так вы проходите всю цепочку.

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

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

Для реализации такого паттерна в коде создаем два обработчика с напитками и салатами. Также есть класс AbstractHandler, содержащий логику с методом setNext. Он указывает текущему обработчику на следующий. Функция askQuestions выступает в роли кассира, который задает вопросы и принимает обработчик. Далее создаем обработчик для напитков – New DrinksHandler, и аналогичный для салатов. Добавляем setNext(salads) для напитков. Он укажет, что после вопроса о напитках кассир должен спросить о салатах:

Пример кода

Пример кода

В нижней части изображена реализация. Вопросы ставятся в правильной последовательности. Ответ для напитков и салатов будет “Да”. Но если речь идет только о салатах, то для них обработчик не указан. Поэтому для салатов будет просто “Да”.

Strategy

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

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

Главный плюс такого паттерна – быстрая переориентация системы.

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

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

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

На уровне кода это выглядит так:

Пример кода

Пример кода

Есть две стратегии с логикой, описывающей процесс получения пиццы. Также имеется более высокоуровневый ReleaseOrderSystem. Она содержит логику, метод setStrategy для изменения Стратегии и метод release для выполнения Стратегии. В нижней части показано, как это используется. Первоначально создается объект OrderSystem. Далее ставим стратегию доставки и вызываем release – и пиццу доставили. В качестве альтернативы ставим самовывоз, снова вызываем release — и самовывоз произошел.

Антипатерны

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

  • Magic numbers

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

  • Hard code

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

  • Boat anchor

Этот паттерн описывает сущности, функции и классы, которые остаются в проекте «на всякий случай». Вы можете написать крутую универсальную фичу и использовать ее. Однако с течением времени бизнес-логика меняется, и ваша функция становится ненужной. Логическое решение – убрать ее. Но разработчик может оставить, потому что вдруг она понадобится и затем сэкономит время написания кода. Это потом может так и не наступить, а якорь все будет оставаться.

  • Lava flow

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

  • Reinventing wheel

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

  • Reinventing square wheel

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

Использовать паттерны или нет?

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

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

If you have found a spelling error, please, notify us by selecting that text and pressing Ctrl+Enter.

SQL для аналітики.
Навчіться аналізувати дані за допомогою власного SQL коду.
Зареєструватися

Этот материал – не редакционный, это – личное мнение его автора. Редакция может не разделять это мнение.

Топ-5 самых популярных блогеров февраля

Всего просмотровВсего просмотров
229
#1
Всего просмотровВсего просмотров
229
Всего просмотровВсего просмотров
209
#2
Всего просмотровВсего просмотров
209
QA в CodeGeeks Solutions
Всего просмотровВсего просмотров
156
#3
Всего просмотровВсего просмотров
156
Senior Project Manager at Nemesis
Всего просмотровВсего просмотров
99
#4
Всего просмотровВсего просмотров
99
Software Architect at Devlify
Всего просмотровВсего просмотров
95
#5
Всего просмотровВсего просмотров
95
Рейтинг блогеров

Ваша жалоба отправлена модератору

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: