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
Фронтенд      17/01/2023

Патерни проєктування для фронтенду та піца — що спільного? Розбираємо на зрозумілих прикладах

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

Frontend Developer у NIX

Можливо, ви не помічали, але в повсякденних задачах часто використовуєте різні патерни. Як інструмент розробника вони роблять нашу роботу простішою та ефективнішою, дозволяють писати більш якісний код. Як саме? Про це розповім далі.

У своєму блозі я хочу познайомити вас із поширеними патернами для фронтенду та ситуаціями, коли їх слід використовувати.

Патерни в IT — що це

З англійської мови pattern перекладається як шаблон. У нашому випадку це шаблон проєктування, який є простим розв’язанням типової проблеми. Патерни можуть використовуватись на рівні функцій, створення об’єкта чи на рівні архітектури. Певною мірою ці інструменти нагадують математичні формули для розв’язання задач.

Поняття патернів започаткував архітектор Крістофер Александер. Він помітив, що після ремонту та облаштування помешкань, його клієнти часто переробляли щось під себе. З’ясовуючи, що не влаштовує людей, він виявив декілька шаблонів — найбільш доцільне для багатьох розташування вікон і стін, висоту стель тощо. Всі напрацювання лягли в основну його книги — A Pattern Language.

Через деякий час чотири програмісти прочитали цю книгу і так надихнулися нею, що написали свою. Це були Еріх Гамма, Річард Хелм, Ральф Джонсон та Джон Вліссідес. В історію вони увійшли як Банда чотирьох (Gang of Four). Їхня книга — Design Patterns — описує шаблони проєктування для об’єктноорієнтованих систем. Автори виділили базові патерни, від яких походять інші. Саме ці шаблони розглянемо у статті.

Фахівці виділили три категорії патернів:

  • Породжувальні — відповідають за створення об’єкта
  • Структурні — призначені для опису ієрархії
  • Поведінкові — визначають взаємодію елементів.

Для того, щоб ви краще зрозуміли принцип використання патернів, пропоную розглянути їх на прикладі… роботи піцерії.

Курс Full-stack розробки від Mate academy.
Станьте Full-stack розробником з нуля. Mate academy дає комплексні знання і навички для розробки повноцінних веб-рішень — від візуальної частини до серверної логіки. Ви освоїте технології, щоб створити власний проєкт від а до я — без допомоги інших.
Ознайомитися з курсом

Породжувальні патерни

Такі патерни допомагають створювати різні об’єкти: без копіпасту, з гнучкістю та можливістю перевикористання. До цієї категорії відносяться багато шаблонів. Розглянемо кілька з них у деталях.

Prototype

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

Проводячи паралель із роботою піцерії, такий патерн допоміг би автоматизувати випічку піци. Зробити вручну сотню «Маргарит» складно. Однак можна написати формулу з інгредієнтами та рецептом піци, віддати її машині — і вона зробить за шаблоном 100 копій.

У коді цей патерн реалізується досить легко. У прикладі нижче наведено об’єкт прототипу з інформацією про піцу. За допомогою Object.create створюються необхідні копії:

Приклад коду

Приклад коду

Factory

Надає інтерфейс для створення об’єктів. Його можна порівняти з фабрикою та її конвеєром. Якщо ви замовляєте на підприємстві партію продукції, у працівників це не викликає питань. Вони знають, які деталі потрібні, який процес збірки та який має бути результат. Ви ж отримуєте готову продукцію в потрібній кількості.

Цей патерн має кілька переваг. Насамперед зберігається незалежність між Фабрикою та об’єктами, які вона виробляє. Також дотримується single responsibility principle — принцип єдиної відповідальності. Відповідно до нього, вся логіка створення об’єкта перебуває всередині класу і не контролюється зовні. Також тут діє open-closed principle — принцип відкритості/закритості. Тобто коли всі об’єкти незалежні одне від одного. Але є і суттєвий недолік. Цей патерн стає завеликим і складним, коли прописано багато логіки. Його важко підтримувати, якщо потрібно створювати безліч об’єктів.

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

У коді цього патерну є два об’єкти у перших рядках: піци «Маргарита» та «Карбонара». Вони містять певні дані: склад піци, її розміри тощо. Далі розміщується сама Фабрика з інформацією pizzaClass — це конфігурація піци. Потім — метод createPizza, який дозволяє встановити саму піцу. Фабрика приймає назву піци та під капотом асоціює це з певним об’єктом чи сутністю, що містить всю необхідну інформацію. В результаті ви віддаєте назву у метод createPizza — і Фабрика повертає потрібний об’єкт:

Онлайн-курс "Computer Vision" від robot_dreams.
Застосовуйте Machine Learning / Deep Learning та вчіть нейронні мережі розпізнавати об’єкти на відео. Отримайте необхідні компетенції Computer Vision Engineer.
Дізнатись більше про курс
Приклад коду

Приклад коду

Builder

Дозволяє створювати об’єкти крок за кроком, таким чином контролюючи цей процес. Також зберігається принцип єдиної відповідальності. Хоча ми й керуємо створенням об’єкта, але логіка знаходиться всередині патерну. Недолік Будівельника подібний до того, що має Фабрика: якщо потрібен складний, універсальний об’єкт, то цей патерн буде важко підтримувати через велику кількість методів усередині.

У піцерії такий патерн стане в нагоді, коли клієнт хоче замовити піцу не з меню, а за власним рецептом. У коді для цього використовується клас PizzaBuilder з інформацією про піцу, що зберігається у змінній _pizza. Також є методи, які можуть оновлювати цю інформацію. У нашому випадку це можливість змінити інгредієнти та назву піци.

На ілюстрації показана реалізація цього патерну. Викликаємо метод changeName і по одному додаємо інгредієнти:

Приклад коду

Приклад коду

Структурні патерни

Описують взаємозв’язок кількох сутностей об’єктів. Їхня мета — побудувати гнучку та ефективну системи з чіткою ієрархією. Зараз поговоримо про ключові структурні патерни.

Adapter

Розповсюджений патерн, який перетворює одні дані на інші. Наприклад, ви отримуєте з бекенду певні дані, формат яких вам не підходить. Потрібні інші поля, назви полів, кількість полів тощо. І саме Адаптер виконає необхідну трансформацію.

Цей патерн дотримується принципів єдиної відповідальності та відкритості/закритості. Логіка переходу між форматом знаходиться окремо від бізнеслогіки. Можемо додавати та прибирати Адаптери, які не будуть пов’язані. Щодо мінусів — застосування Адаптера не завжди виправдане. Інколи простіше додати зміни до бізнеслогіки, а не вбудовувати патерн.

У піцерії цей патерн може знадобитись у різних ситуаціях. Припустимо, для зміни назви піци. Запропоноване меню містить поля Назва, Інгредієнти та інші. Однак у кастомних моделей немає назви. У коді це не викликає питань, але при замовленні такої піци у чеку у відповідній графі буде порожньо. Проблему можна вирішити зміною логіки з додаванням поля Name.

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

Онлайн курс з промт інжинірингу та ефективної роботи з ШІ від Powercode academy.
Курс-інтенсив для отримання навичок роботи з ChatGPT та іншими інструментами ШІ для професійних та особистих задач, котрі допоможуть як новачку, так і професіоналу.
Записатися на курс
Приклад коду

Приклад коду

Decorator

Патерн дозволяє додавати функціонал до наявного класу. Завдяки йому не треба описувати багато логіки всередині. Достатньо винести її з класу в окреме місце, описати там і потім навісити Декоратор. Подібні патерни можна легко додавати та прибирати, якщо їх забагато. Щоправда, звідси витікає їхній мінус. Якщо Декоратор являє собою функцію, яка набуває значення і додає щось своє, то за наявності безлічі таких функцій-шарів прибрати один Декоратор складно. Шари мають залежність один від одного. До того ж Декоратори виглядають некрасиво, через що доводиться дробити всі функції. Загалом із цим патерном виконується принцип єдиної відповідальності, і логіка розбита на класи. В залежності від ситуації, це може бути і зручно, і одночасно викликати складнощі.

Наведу приклад у вигляді знижки на деякі піци. Ви можете додавати в коді до всіх піц поле Знижки або логіку з таким полем, але це буде довго. Простіше використовувати Декоратори. У нас є клас simplePizza. Це стандартна піца з методом getCost, який повертає вартість. Також є декоратор PizzaWithDiscount, який приймає піцу та розмір знижки, перезаписує у цієї піци getCost, а потім ставить нове значення з урахуванням знижки:

Приклад коду

Приклад коду

Також тут можна побачити метод getCost return pizza.getCost мінус discount. Дисконт — це конкретна сума, а не відсоток. У кінці ми створюємо піцу через SimplePizza та перевіряємо її дефолтне значення. Наприклад, 100. Далі обертаємо піцу в декоратор PizzaWithDiscont і встановлюємо знижку 20. Наступного разу після перевірки вартості патерн порахує знижку, перезапише метод та поверне потрібний результат — 80.

Поведінкові патерни

Вони описують логіку поведінки між об’єктами та відповідають за розподіл відповідальності між сутностями та частинами. Дещо схожі на алгоритми. Хоча алгоритми теж є своєрідними патернами — тільки не проєктування, а обчислення. З багатьох поведінкових патернів я виокремлю два таких…

Chain of responsibility

Цей патерн є набором обробників. Замість обробки по черзі в одній функції, розбиваємо все на окремі функції, обробники та класи. В результаті запит проходить кілька етапів.

Це нагадує дзвінок до служби техпідтримки. Спочатку потрапляєте на перший обробник на кшталт «Натисніть таку-то кнопку, якщо хочете поговорити з оператором». Далі вас перенаправляє на наступний обробник — оператора. Розповідаєте йому свою проблему з технікою, й оператор пропонує зробити прості операції. Якщо нічого не допомагає, вас перенаправляють на іншого обробника — технічного експерта. Так ви проходите весь ланцюжок.

У цьому патерні руйнується принцип єдиної відповідальності. Логіка розділяється на дрібні частини: логіку для виконання операцій, для виклику і менеджменту запиту. При цьому процес може завершитись нічим. Наприклад, якщо в техпідтримку зателефонує людина з неадекватним запитом, оператор може перервати розмову. Тоді людина не отримає відповідь, оскільки запит від початку був некоректним.

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

Онлайн-курс "Директор з продажу" від Laba.
Як стратегічно впливати на дохід компанії, мотивувати сейлзів перевиконувати KPI та впроваджувати аналітику — навчить комерційний директор Laba з 12-річним досвідом у продажах.
Приєднатись до курсу

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

Приклад коду

Приклад коду

У нижній частині зображена реалізація. Питання ставляться у правильній послідовності. Відповідь для напоїв і салатів буде «Так». Але якщо йдеться лише про салати, то для них обробник не вказаний. Тому для салатів буде просто «Так».

Strategy

Цей патерн досить складний і схожий на алгоритми, з яких ми створюємо окремі сутності та розподіляємо їх за класами. Існує більш високорівневий обробник, який приймає один з алгоритмів. Він і є Стратегією, яку ми використовуємо чи перемикаємо на іншу.

Згадайте складання маршруту в Google-картах. Уявімо, ви хочете доїхати з дому на роботу на громадському транспорті. Відкриваєте застосунок, вказуєте стартову та фінішну точки та натискаєте на іконку з автобусом. Система будує маршрут згідно із заданою Стратегією. Раптом вирішуєте, що хочете йти пішки та натискаєте іконку з чоловічком. Система змінює Стратегію та перебудовує маршрут під нові критерії.

Головний плюс такого патерну — швидка переорієнтація системи.

Вам не треба застосовувати множину «if» з описом логіки в одному місці. Також цей спосіб допомагає використовувати композицію замість успадкування. Без цього зазвичай доводиться робити один загальний шаблонний алгоритм і успадковуватись від нього. Це призводить до переписування логіки. На противагу цьому з композицією всі алгоритми незалежні. Тому можна додавати принцип відкритості/закритості або підставляти щось нове, і це не вплине на старе.

Зверність увагу, цей патерн може бути громіздким. Раджу не використовувати його, якщо стратегія змінюється рідко. Також слід добре розуміти наявність кількох стратегій та різницю між ними. А для цього — ретельно продумати інтерфейс сервісу. У тих же Google-картах користувач бачить іконки автобуса, чоловічка або машини та відразу бачить, які типи маршрутів йому доступні.

Якщо ж повернутися до піцерії, то тут цей патерн допоможе побудувати логіку для отримання замовлення (доставлення або самовивіз). Ці Стратегії міститимуть два алгоритми та дві логіки. При виборі доставки маршрут виглядає так: кур’єр отримує замовлення, забирає піцу, привозить її клієнту, приймає оплату. У разі самовивозу клієнт робить замовлення, приходить до закладу в певний час, оплачує, отримує чек і забирає піцу. Якщо у користувача змінилися плани, він може перемикатися між стратегіями.

На рівні коду це виглядає так:

Онлайн-курс "Project Manager" від Laba.
Станьте проджектом, що вміє передбачати ризики наперед і доводити проєкт до результату, який хочуть замовники. Поділиться досвідом Павло Харіков, former Head of PMO в Kyivstar.
Програма курсу і реєстрація
Приклад коду

Приклад коду

Є дві стратегії з логікою, яка описує процес отримання піци. Також є більш високорівнева ReleaseOrderSystem. Вона містить логіку, метод setStrategy для зміни Стратегії та метод release для виконання Стратегії. У нижній частині показано, як це використовується. Спочатку створюється об’єкт OrderSystem. Далі ставимо стратегію доставки та викликаємо release — і піцу доставили. Як альтернатива, ставимо самовивіз, знову викликаємо release — і самовивіз відбувся.

Антипатерни

Ці рішення викликають безліч проблем із кодом. Щоб уникнути помилок в роботі, варто обов’язково знати про типові антипатерни:

  • Magic numbers

Це прописані в коді змінні або значення, походження яких сторонньому спостерігачеві не є очевидним. Наприклад, ваш проєкт пов’язаний із математичними обчисленнями, де щось множиться на два. Автор коду чітко розуміє, звідки взялася цифра. Однак інший розробник не знає цього. Так ускладнюється процес рев’ю, правок та роботи над продуктом. Для вирішення проблеми варто виносити все як мінімум у змінні. Також можна називати функції по-іншому, що зробить код зрозумілішим.

  • Hard code

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

  • Boat anchor

Цей патерн описує сутності, функції та класи, які залишаються у проєкті «про всяк випадок». Ви можете написати круту універсальну фічу та використовувати її. Однак з часом бізнес-логіка змінюється, і ваша функція стає непотрібною. Логічне рішення — прибрати її. Та розробник може залишити, бо раптом вона знадобиться і потім заощадить час написання коду. Це «потім» може так і не настати, а «якір» усе залишатиметься.

  • Lava flow
  • Практичний інтенсивний курс з дизайну - Design Booster від Powercode academy.
    Навчіться дизайну з нуля за 3 місяці і заробляйте перші $1000, навіть якщо ви не маєте креативного мислення, смаку або вміння малювати. Отримайте практичні навички, необхідні для успішної кар'єри в дизайні.
    Зарееструватися

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

  • Reinventing wheel

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

  • Reinventing square wheel

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

Використовувати патерни чи ні?

Єдиної правильної відповіді на це питання немає. Варто оцінювати переваги та недоліки різних патернів у конкретній ситуації. З одного боку, шаблони зручні та надійні. Крім того, завжди можна дізнатися у профільній спільноті про нюанси їх використання та адаптацію під свої задачі. Команді вони теж зрозумілі. Ви можете запропонувати в якомусь місці коду використати певний патерн — й інші розробники одразу побачать цілісну картину, як це працюватиме.

З іншого боку, патерни не завжди підходять для певних задач. Потрібно вміти аналізувати свої можливості, можливості цих шаблонів та зусилля, які витрачаються на їхнє впровадження. Варто прорахувати результат у перспективі. Недаремно кажуть: коли в тебе в руках молоток — увесь світ здається цвяхами. Після вивчення та освоєння патерну часто хочеться застосовувати його мало не в кожному проєкті. Однак це може спровокувати додаткові проблеми. Тому завжди замислюйтесь, які з ваших рішень дійсно спростять написання коду.

Якщо ви знайшли помилку, будь ласка, виділіть фрагмент тексту та натисніть Ctrl+Enter.

Онлайн-курс "Архітектура високих навантажень" від robot_dreams.
Досвід та інсайти від інженера, який 12 років створює програмне забезпечення для Google.
Програма курсу і реєстрація

Цей матеріал – не редакційний, це – особиста думка його автора. Редакція може не поділяти цю думку.

Топ-5 найпопулярніших блогерів березня

PHP Developer в ScrumLaunch
Всего просмотровВсього переглядів
2434
#1
Всего просмотровВсього переглядів
2434
Founder at Shallwe, Python Software Engineer (Django/React)
Всего просмотровВсього переглядів
113
#2
Всего просмотровВсього переглядів
113
Career Consultant в GoIT
Всего просмотровВсього переглядів
95
#3
Всего просмотровВсього переглядів
95
CEO & Founder в Trustee
Всего просмотровВсього переглядів
94
#4
Всего просмотровВсього переглядів
94
Рейтинг блогерів

Найбільш обговорювані статті

Топ текстів

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

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

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