🧩 Розділ 2 · Питання #2

Які категорії патернів існують

Класичні патерни проектування банди чотирьох (GoF — Gang of Four) поділяються на три основні категорії залежно від їхнього призначення в архітектурі програми:


🟢 Junior Level

Класичні патерни проектування банди чотирьох (GoF — Gang of Four) поділяються на три основні категорії залежно від їхнього призначення в архітектурі програми:

  1. Породжувальні (Creational) — відповідають за процес створення об’єктів. Вони інкапсулюють логіку інстанціювання, приховуючи, які конкретно класи створюються і в який спосіб. Код клієнта зв’язується з абстракціями, а не з викликом оператора new.
    • Приклади: Singleton, Factory Method, Abstract Factory, Builder, Prototype.
  2. Структурні (Structural) — визначають, як класи та об’єкти компонуються для утворення більших і гнучкіших структур. Вони допомагають організувати зв’язки між сутностями без надмірного та жорсткого успадкування.
    • Приклади: Adapter, Decorator, Proxy, Facade, Composite, Bridge, Flyweight.
  3. Поведінкові (Behavioral) — організовують ефективну, гнучку та безпечну взаємодію між об’єктами, розподіляючи обов’язки та алгоритми.
    • Приклади: Strategy, Observer, Command, Iterator, State, Chain of Responsibility, Template Method, Visitor.

Проста аналогія з реального життя (Будівництво будинку):

  • Породжувальні: Завод ЗБВ і столярна майстерня — вирішують, як виготовити стандартні цеглини, вікна, двері та балки потрібної специфікації.
  • Структурні: Монтаж та оздоблення — перехідники для труб різного діаметра (Adapter), зовнішнє утеплення стін (Decorator), розподільчий електрощиток (Facade).
  • Поведінкові: Розумний дім та розпорядок мешканців — датчик руху вмикає світло (Observer), термостат регулює нагрів за розкладом (Strategy), пульт управління надсилає команди приладам (Command).

Мінімальний приклад усіх трьох категорій в одному сценарії

// === 1. Creational: Фабричний метод приховує інстанціювання ===
interface MessageSender {
    void send(String msg);
}

class EmailSender implements MessageSender {
    @Override
    public void send(String msg) {
        System.out.println("Email: " + msg);
    }
}

class SenderFactory {
    public static MessageSender createEmailSender() {
        return new EmailSender();
    }
}

// === 2. Structural: Декоратор загортає об'єкт, додаючи логування ===
class LoggingSenderDecorator implements MessageSender {
    private final MessageSender target;

    public LoggingSenderDecorator(MessageSender target) {
        this.target = target;
    }

    @Override
    public void send(String msg) {
        System.out.println("[LOG] Sending message: " + msg);
        target.send(msg);
    }
}

// === 3. Behavioral: Стратегія змінює алгоритм форматування ===
@FunctionalInterface
interface FormatStrategy {
    String format(String text);
}

public class PatternCategoriesDemo {
    public static void main(String[] args) {
        // Породження
        MessageSender baseSender = SenderFactory.createEmailSender();
        
        // Структурне загортання
        MessageSender decoratedSender = new LoggingSenderDecorator(baseSender);
        
        // Поведінкове налаштування алгоритму
        FormatStrategy strategy = text -> text.toUpperCase();
        
        String payload = strategy.format("Hello, Architecture!");
        decoratedSender.send(payload);
    }
}

🟡 Middle Level

1. Породжувальні патерни (Creational Patterns)

Головна мета: Ізолювати клієнтський код від конкретних типів та деталей конструювання об’єктів, забезпечуючи дотримання принципів OCP (Open/Closed Principle) та DIP (Dependency Inversion Principle).

Патерн Призначення Приклад у JDK / Spring
Singleton Гарантує єдиний екземпляр класу в JVM / ClassLoader. Runtime.getRuntime(), біни у Spring (@Scope("singleton"))
Factory Method Делегує створення об’єктів підкласам або статичним методам. List.of(), Integer.valueOf(), Calendar.getInstance()
Abstract Factory Створює сімейства взаємопов’язаних об’єктів без прив’язки до конкретних класів. DocumentBuilderFactory.newInstance(), Connection.createStatement()
Builder Покроково конструює складні складені об’єкти, відокремлюючи представлення від конструювання. StringBuilder, HttpRequest.newBuilder(), Lombok @Builder
Prototype Створює копії наявних об’єктів без жорсткої прив’язки до їхніх класів. Object.clone(), Prototype scope у Spring (@Scope("prototype"))

Еволюція в Modern Java (8–21)

  • Замість громіздких фабрик часто використовуються статичні фабричні методи інтерфейсів: Map.of(), Set.of(), List.of().
  • Замість класичного патерну Factory у функціональному стилі передається Supplier<T>:
    // Сучасна фабрика через Supplier
    public Order processOrder(Supplier<Order> orderSupplier) {
      Order order = orderSupplier.get();
      order.validate();
      return order;
    }
    

2. Структурні патерни (Structural Patterns)

Головна мета: Забезпечити зручну композицію об’єктів і класів, мінімізуючи зв’язність (coupling) та запобігаючи вибуховому зростанню ієрархій успадкування.

Патерн Призначення Приклад у JDK / Spring
Adapter Перетворює інтерфейс одного класу на інтерфейс, очікуваний клієнтом. Arrays.asList(), InputStreamReader(InputStream)
Decorator Динамічно додає об’єкту нові обов’язки в runtime через загортання. BufferedInputStream(new FileInputStream(...)), Servlet Filters
Proxy Надає об’єкт-замінник, що контролює доступ до оригінального об’єкта. java.lang.reflect.Proxy, Spring AOP (@Transactional, @Cacheable)
Facade Надає простий уніфікований інтерфейс до складної підсистеми з багатьох класів. DriverManager.getConnection(), Spring JdbcTemplate
Composite Групує об’єкти у деревоподібні структури «частина — ціле» для однакової роботи. java.awt.Container, DOM-дерева XML/JSON (Jackson JsonNode)
Bridge Розділяє абстракцію та реалізацію, дозволяючи їм змінюватися незалежно. Архітектура JDBC-драйверів (DriverManager vs реалізації вендорів)
Flyweight Заощаджує пам’ять за рахунок спільного використання стану багатьма об’єктами. Пул рядків (String Pool), Integer.valueOf(-128..127)

Порівняння різниці між Decorator, Adapter та Proxy

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

3. Поведінкові патерни (Behavioral Patterns)

Головна мета: Формалізувати алгоритми, потоки керування та комунікацію між незалежними об’єктами, позбавляючи код від заплутаних конструкцій if-else та switch.

Патерн Призначення Приклад у JDK / Spring
Strategy Визначає сімейство алгоритмів, інкапсулює кожен і робить їх взаємозамінними. Comparator.comparing(), Spring AuthenticationProvider
Observer Механізм підписки «один до багатьох» для сповіщення про зміну стану. Flow.Publisher/Subscriber, Spring ApplicationEventPublisher
Template Method Визначає скелет алгоритму в базовому класі, делегуючи кроки підкласам. AbstractList.get(), Spring JdbcTemplate.execute()
Chain of Resp. Передає запит ланцюжком потенційних обробників до першого успішного. FilterChain у Spring Security, Logger.log()
Command Інкапсулює запит як об’єкт, дозволяючи параметризувати клієнтів і ставити в чергу. Runnable, Callable, java.util.concurrent.ForkJoinTask
Iterator Надає послідовний доступ до елементів колекції без розкриття її структури. java.util.Iterator, Spliterator
State Змінює поведінку об’єкта при зміні його внутрішнього стану (State Machine). Життєвий цикл бінів, стани з’єднань TCP / SSLEngine

4. Додаткові категорії патернів в Enterprise-розробці

Класифікація GoF (1994 р.) створювалася для настільних об’єктно-орієнтованих систем. У сучасній серверній Java виділяють ще дві ключові категорії:

  1. Concurrency Patterns (Багатопоточні патерни):
    • Producer-Consumer: розділення виробництва та обробки задач через потокобезпечну чергу (BlockingQueue).
    • Read-Write Lock: паралельне читання багатьма потоками при монопольному записі (ReentrantReadWriteLock).
    • Thread Pool / Worker Thread: повторне використання потоків для виключення витрат на їхнє створення (ThreadPoolExecutor).
    • Double-Checked Locking: оптимізація лінивої ініціалізації спільного ресурсу з ключовим словом volatile.
  2. Cloud-Native & Distributed Patterns (Розподілені патерни):
    • Circuit Breaker (Запобіжник): запобігання каскадним збоям при падінні зовнішнього мікросервісу (Resilience4j).
    • Saga: управління розподіленими транзакціями через ланцюжок локальних кроків та компенсуючих дій (Choreography або Orchestration).
    • Outbox Pattern: гарантована атомарна доставка подій у брокер повідомлень (Kafka/RabbitMQ) разом зі збереженням у БД.
    • Bulkhead: ізоляція ресурсів (пулів потоків, з’єднань), щоб збій однієї підсистеми не вичерпав ресурси всього сервісу.

🔴 Senior Level

Архітектурна глибина: Категорії як рівні розв’язки (Decoupling)

Кожна категорія GoF атакує конкретний тип жорсткого зв’язку (Coupling) в архітектурі:

[Архітектурна проблема]               [Категорія GoF]              [Архітектурне рішення]
1. Зв'язність за інстанціюванням -->  Породжувальні (Creational)  --> Інверсія керування (IoC), DI
2. Зв'язність за типами і зв'язками -->  Структурні (Structural)  --> Композиція замість успадкування
3. Зв'язність за алгоритмами     -->  Поведінкові (Behavioral)    --> Поліморфізм і слабка зв'язаність
  • Creational мінімізують знання про те, як влаштований життєвий цикл об’єкта. Порушення веде до засилля оператора new, жорстких залежностей від конкретних класів і неможливості unit-тестування.
  • Structural запобігають комбінаторному вибуху класів. Принцип «Надавай перевагу композиції перед успадкуванням» вирішує проблему жорстких ієрархій: для $N$ можливостей успадкування вимагає $2^N$ класів, тоді як композиція (Decorator/Composite) вимагає лише $N$ класів.
  • Behavioral усувають роздуті монолітні методи з процедурною логікою. Вони розподіляють обов’язки згідно з принципом єдиної відповідальності (SRP).

JIT-компіляція та продуктивність поліморфізму

Часте застосування структурних і поведінкових патернів базується на поліморфізмі (інтерфейсах та абстрактних класах). У HotSpot JVM спосіб виклику методів критично впливає на оптимізацію JIT:

                      HotSpot Method Invocation Inline Cache
                      
   [Call Site] ──(1 реалізація)───> Monomorphic  --> JIT Inlining (0 ns overhead, прямий перехід)
        │
        ├───(2 реалізації)───────> Bimorphic    --> Inline Cache: 2 branch checks, possible inlining
        │
        └───(> 2 реалізації)─────> Megamorphic  --> V-Table / I-Table lookup (непрямий виклик,
                                                    інлайнінг неможливий, скидання branch prediction)
  1. Monomorphic dispatch: Якщо інтерфейс Strategy має рівно одну реалізацію в runtime, JIT-компілятор (C2) виконує агресивний інлайнінг (Inlining), повністю усуваючи накладні витрати на віртуальний виклик.
  2. Bimorphic dispatch: За наявності двох реалізацій JIT вставляє умовну перевірку типів (instanceof) та інлайнить обидві гілки.
  3. Megamorphic dispatch: За наявності 3 і більше активних реалізацій в одній точці виклику (call site) JIT здається. Відбувається пошук за таблицею віртуальних методів (itable/vtable), процесор не може передбачити перехід розгалуження, а інлайнінг блокується.

[!TIP] Hot-path оптимізація: Якщо патерн Strategy використовується в критичному циклі обробки мільйонів транзакцій на секунду, уникайте мегаморфних інтерфейсів. Ефективніше використовувати enum зі стратегіями або switch-вираз за sealed interface, де компілятор генерує компактну інструкцію tableswitch.


Вплив Modern Java (Java 17–21) на класичні категорії

Специфікація сучасної мови Java зробила багато класичних GoF-патернів застарілими в їхньому традиційному багатослівному вигляді:

  1. Заміна GoF Visitor & Strategy через Sealed Types + Pattern Matching: ```java // Замість ієрархії Strategy та подвійної диспетчеризації Visitor: public sealed interface PaymentMethod permits CreditCard, Sbp, Crypto {}

public record CreditCard(String pan, String cvv) implements PaymentMethod {} public record Sbp(String phoneNumber) implements PaymentMethod {} public record Crypto(String walletAddress) implements PaymentMethod {}

// Вичерпний (exhaustive) switch гарантує обробку всіх гілок на етапі компіляції: public static void processPayment(PaymentMethod method, BigDecimal amount) { switch (method) { case CreditCard cc -> chargeCard(cc.pan(), amount); case Sbp sbp -> initiateFastPayment(sbp.phoneNumber(), amount); case Crypto crypto -> transferTokens(crypto.walletAddress(), amount); } }


2. **Заміна Builder для DTO через Records:**
   Компактні конструктори та незмінність `record` закривають 90% потреб у зовнішніх будівельниках для простих структур даних.
   
3. **Віртуальні потоки (Virtual Threads / Project Loom):**
   Усувають потребу в складних реактивних ланцюжках та асинхронних патернах для більшості I/O-bound задач, повертаючи парадигму до простого синхронного блокуючого коду.

---

### Spring Framework як інтегратор трьох категорій

Будь-який Spring-контейнер об'єднує патерни всіх трьох категорій у єдиний конвеєр життєвого циклу:

[Creational Phase] BeanFactory / ApplicationContext ├─ Читає конфігурацію (Factory Method, Abstract Factory) └─ Інстанціює біни (Singleton / Prototype Scope) │ ▼ [Structural Phase] BeanPostProcessor & Dynamic Proxy / CGLIB ├─ Огортає біни у Proxy (Structural) └─ Додає інфраструктурні аспекти (@Transactional, @Cacheable, @Async) │ ▼ [Behavioral Phase] Runtime Interception & Event Bus ├─ Обробка запитів через Chain of Responsibility (HandlerInterceptor) ├─ Публікація подій (Observer via ApplicationEventPublisher) └─ Вибір стратегій серіалізації/валідації (Strategy via HttpMessageConverter) ```


🎯 Шпаргалка для інтерв’ю

30-секундна відповідь (Elevator Pitch)

«Патерни GoF поділяються на три категорії: Породжувальні ізолюють процес створення об’єктів (Builder, Factory, Singleton), запобігаючи зв’язності з конкретними класами. Структурні формують зв’язки та композицію між об’єктами без розростання ієрархій успадкування (Adapter, Decorator, Proxy, Facade). Поведінкові розподіляють алгоритми та обов’язки між незалежними сутностями (Strategy, Observer, Command, State). У розподілених та багатопоточних системах їх доповнюють Concurrency (Worker Thread, Read-Write Lock) та Cloud-Native патерни (Circuit Breaker, Saga, Outbox). У сучасній Java багато патернів спростилися завдяки лямбдам, record та sealed-інтерфейсам».


3–4 каверзних питання з відповідями

  1. У чому принципова архітектурна різниця між Proxy та Decorator, якщо їхні сигнатури та структура класів можуть бути ідентичними?
    • Відповідь: Різниця в намірі (Intent). Decorator розширює поведінку наявного об’єкта (додає фічі: стиснення, логування, шифрування) і зазвичай передається клієнтом ззовні. Proxy керує доступом до об’єкта (лінива ініціалізація, безпека, кешування, мережевий виклик, межі транзакцій) і часто сам контролює життєвий цикл реального суб’єкта.
  2. Як надмірне використання патерну Strategy може негативно вплинути на продуктивність застосунку під навантаженням?
    • Відповідь: Через створення мегаморфних точок виклику (Megamorphic Call Sites). Якщо в інтерфейсу Strategy понад дві реалізації, HotSpot JIT C2 деоптимізує Inline Cache: непрямий виклик через itable/vtable не може бути заінлайнений, а процесор стикається зі штрафами провісника переходів (Branch Misprediction).
  3. До якої категорії належить патерн Repository і чи входить він до GoF?
    • Відповідь: Repository не входить до оригінального каталогу GoF 1994 року. Це архітектурний патерн корпоративних застосунків (PoEAA за Мартіном Фаулером). По суті, він поєднує структурну роль (Facade / Adapter над джерелом даних) з абстракцією доступу до колекції доменних сутностей.
  4. Чому java.util.Observable визнано застарілим (deprecated) у Java 9 і чим його замінюють?
    • Відповідь: Клас Observable був класом, а не інтерфейсом (ламав множинне успадкування), не був потокобезпечним, не підтримував типізацію (приймав Object) і не забезпечував зворотного тиску (Backpressure). На зміну прийшли Reactive Streams / java.util.concurrent.Flow, Project Reactor, Spring ApplicationEventPublisher або брокери повідомлень.

Типові помилки та Red Flags

  • 🚩 «Патерни застаріли, тому що у Spring усе робиться через анотації» — нерозуміння того, що Spring AOP побудований на патерні Proxy, JdbcTemplate — на Template Method, ApplicationContext — на Factory/Singleton, а події контексту — на Observer.
  • 🚩 Плутанина між Adapter та Decorator — нерозуміння того, що Adapter змінює контракт (інтерфейс) для сумісності, а Decorator суворо зберігає інтерфейс вихідного об’єкта, доповнюючи його поведінку.
  • 🚩 «Патерн Singleton гарантує рівно один об’єкт на всю систему» — у розподіленому середовищі або мікросервісах Singleton ізольований межами одного вузла JVM (і навіть одного ClassLoader). Для всієї системи потрібна розподілена координація (Redis/ZooKeeper).
  • 🚩 Patternitis (Синдром гіпер-проектування) — впровадження Abstract Factory та Chain of Responsibility у найпростіші CRUD-методи там, де достатньо звичайного процедурного виклику.

Пов’язані теми