1. Adapter

Адаптер (Adapter) — структурний шаблон проєктування, призначений для організації використання функцій об'єкта, недоступного для модифікації, через спеціально створений інтерфейс.

Офіційне визначення трохи складно сприймається, але якщо описати його своїми словами, то адаптер – це патерн проєктування, який дозволяє об'єктам із несумісними інтерфейсами працювати разом.

Використовується для організації застовування функцій об'єкта, недоступного для модифікації через спеціально створений інтерфейс. Створюється додатковий клас, який має потрібний інтерфейс, а цей клас у свою чергу викликає методи потрібного об'єкта (який не має необхідного інтерфейсу).

Важливо! Якщо в коді ти зустрічаєш у класу суфікс Adapter, маєш повне право вважати, що цей клас виконує роль адаптера і пов'язаний з групою класів, які працюють за схемою, що описана вище.

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

Сильні сторони:

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

2. Decorator

Декоратор (Decorator) — структурний шаблон проєктування, призначений для динамічного підключення додаткової поведінки до об'єкту. Шаблон Декоратор надає хорошу та гнучку альтернативу практиці створення підкласів з метою розширення функціональності.

Використовується для динамічного підключення до об'єкта додаткових зобов'язань.

Багато хто з вас запитає: як можна динамічно (під час роботи програми) додати об'єкту нову поведінку? Об'єкт можна зібрати зі шматочків, тобто невеликих об'єктів. Пам'ятаєш ланцюжки фільтрів у сервлетах? Або Stream API, коли ти писав запит із використанням filter(), map(), list()?

IntStream.of(50, 60, 70, 80, 90).filter(x -> x < 90).map(x -> x + 10).limit(3).forEach(System.out::print);

Сильні сторони патерну Decorator:

  • Не потрібно створювати підкласи для розширення функціональності об'єкта.
  • Можливість динамічно підключати нову функціональність у будь-якому місці: до або після основної функціональності об'єкта ConcreteComponent.

3. Proxy

Замісник (Proxy) — структурний шаблон проєктування, що надає об'єкт, який контролює доступ до іншого об'єкта, перехоплюючи і пропускаючи всі його виклики.

Патерн Proxy надає об'єкт-замінник замість цього об'єкта. Цей об'єкт контролює доступ до оригінального об'єкта. Використовується дуже часто.

Пам'ятаєш, як ми працювали з фреймворком Mockito та перехоплювали звернення до реального об'єкта за допомогою методу Mockito.spy() чи анотації @Spy? Саме тоді й створювався спеціальний Proxy-об'єкт, через який проходили всі виклики до оригінального об'єкту.

І тоді за допомогою додавання об'єкту правил ми могли цими викликами управляти. Саме так: оригінальний об'єкт не змінюється, а робота з ним стає значно гнучкішою. Особливо корисно буває, коли ми з нашого коду викликаємо proxy-об'єкт, а передаємо його кудись. Контролюючи в такий спосіб спілкування двох незалежних від нас об'єктів.

Види проксі за призначенням:

  • Протокол проксі: зберігає в лог всі виклики "Суб'єкта" з їх параметрами.
  • Віддалений заступник (remote proxies): забезпечує зв'язок із “Суб'єктом”, який знаходиться в іншому адресному просторі або на віддаленій машині. Також може відповідати за кодування запиту та його аргументів та надсилання закодованого запиту реальному "Суб'єкту".
  • Віртуальний заступник (virtual proxies): забезпечує створення реального “Суб'єкта” лише тоді, коли він справді знадобиться. Також може кешувати частину інформації про реальний Суб'єкт, щоб відкласти його створення.
  • Копіювати-при-записі: забезпечує копіювання "суб'єкта" при виконанні клієнтом певних дій (частковий випадок "віртуального проксі").
  • Захищаючий заступник (protection proxies): може перевіряти, чи має об'єкт, що викликає, необхідні для виконання запиту права.
  • Кешуючий проксі: забезпечує тимчасове зберігання результатів розрахунку до віддавання їх численним клієнтам, які можуть розділити ці результати.
  • Екрануючий проксі: захищає "Суб'єкт" від небезпечних клієнтів (або навпаки).
  • Синхронізуючий проксі: здійснює синхронізований контроль доступу до "Суб'єкта" в асинхронному багатопотоковому середовищі.
  • "Розумне" посилання (smart reference proxy): робить додаткові дії, коли на "Суб'єкт" створюється посилання, наприклад, розраховує кількість активних посилань на "Суб'єкт".

4. Bridge

Шаблон Міст (Bridge) — структурний шаблон проєктування, який використовується для розділення абстракції та реалізації таким чином, щоб вони могли змінюватися незалежно.

Шаблон міст використовує інкапсуляцію, агрегування і може використовувати успадкування, щоб розділити відповідальність між класами.

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

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

У той самий час малювання графіки може відрізнятися залежно від типу ОС чи графічної бібліотеки. Фігури повинні мати можливість малювати себе у різних графічних середовищах. Але реалізовувати у кожній фігурі всі способи малювання або модифікувати фігуру щоразу при зміні способу малювання непрактично.

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

Зв'язок, що зображується стрілкою на діаграмах, може мати два сенси: а) "різновид", відповідно до принципу підстановки Лісков і б) одна з можливих реалізацій абстракції. Зазвичай у мовах використовується успадкування для реалізації як а), так і б), що призводить до розбухання ієрархій класів.

Міст допомагає вирішити саме цю проблему: об'єкти створюються парами з об'єкта класу ієрархії А та ієрархії B, успадкування всередині ієрархії А має сенс "різновид" за Лісков, а для поняття "реалізація абстракції" використовується посилання з об'єкта A до парного об'єкту B.

5. Facade

Шаблон Фасад (Facade) — структурний шаблон проєктування, що дозволяє приховати складність системи шляхом зведення всіх можливих зовнішніх викликів до одного об'єкта, який делегує їх відповідним об'єктам системи.

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

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

Реалізація інших компонентів підсистеми закрита і не помітна зовнішнім компонентам. Фасадний об'єкт забезпечує реалізацію GRASP патерну Стійкий до змін з погляду захисту від змін у реалізації підсистеми.

Важливо! Цей шаблон застосовується, коли ми хочемо повністю приховати якусь групу об'єктів та всю комунікацію з ними пропустити через наш об'єкт. Якщо ж ти просто хочеш забезпечити певний контроль процесу комунікації об'єктів і приховувати їх не обов'язково, краще скористатися патерном Proxy.