Які категорії патернів існують
Класичні патерни проектування банди чотирьох (GoF — Gang of Four) поділяються на три основні категорії залежно від їхнього призначення в архітектурі програми:
🟢 Junior Level
Класичні патерни проектування банди чотирьох (GoF — Gang of Four) поділяються на три основні категорії залежно від їхнього призначення в архітектурі програми:
- Породжувальні (Creational) — відповідають за процес створення об’єктів. Вони інкапсулюють логіку інстанціювання, приховуючи, які конкретно класи створюються і в який спосіб. Код клієнта зв’язується з абстракціями, а не з викликом оператора
new.- Приклади:
Singleton,Factory Method,Abstract Factory,Builder,Prototype.
- Приклади:
- Структурні (Structural) — визначають, як класи та об’єкти компонуються для утворення більших і гнучкіших структур. Вони допомагають організувати зв’язки між сутностями без надмірного та жорсткого успадкування.
- Приклади:
Adapter,Decorator,Proxy,Facade,Composite,Bridge,Flyweight.
- Приклади:
- Поведінкові (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 виділяють ще дві ключові категорії:
- Concurrency Patterns (Багатопоточні патерни):
- Producer-Consumer: розділення виробництва та обробки задач через потокобезпечну чергу (
BlockingQueue). - Read-Write Lock: паралельне читання багатьма потоками при монопольному записі (
ReentrantReadWriteLock). - Thread Pool / Worker Thread: повторне використання потоків для виключення витрат на їхнє створення (
ThreadPoolExecutor). - Double-Checked Locking: оптимізація лінивої ініціалізації спільного ресурсу з ключовим словом
volatile.
- Producer-Consumer: розділення виробництва та обробки задач через потокобезпечну чергу (
- 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)
- Monomorphic dispatch: Якщо інтерфейс
Strategyмає рівно одну реалізацію в runtime, JIT-компілятор (C2) виконує агресивний інлайнінг (Inlining), повністю усуваючи накладні витрати на віртуальний виклик. - Bimorphic dispatch: За наявності двох реалізацій JIT вставляє умовну перевірку типів (
instanceof) та інлайнить обидві гілки. - Megamorphic dispatch: За наявності 3 і більше активних реалізацій в одній точці виклику (call site) JIT здається. Відбувається пошук за таблицею віртуальних методів (
itable/vtable), процесор не може передбачити перехід розгалуження, а інлайнінг блокується.
[!TIP] Hot-path оптимізація: Якщо патерн
Strategyвикористовується в критичному циклі обробки мільйонів транзакцій на секунду, уникайте мегаморфних інтерфейсів. Ефективніше використовуватиenumзі стратегіями або switch-вираз заsealed interface, де компілятор генерує компактну інструкціюtableswitch.
Вплив Modern Java (Java 17–21) на класичні категорії
Специфікація сучасної мови Java зробила багато класичних GoF-патернів застарілими в їхньому традиційному багатослівному вигляді:
- Заміна 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 каверзних питання з відповідями
- У чому принципова архітектурна різниця між Proxy та Decorator, якщо їхні сигнатури та структура класів можуть бути ідентичними?
- Відповідь: Різниця в намірі (Intent).
Decoratorрозширює поведінку наявного об’єкта (додає фічі: стиснення, логування, шифрування) і зазвичай передається клієнтом ззовні.Proxyкерує доступом до об’єкта (лінива ініціалізація, безпека, кешування, мережевий виклик, межі транзакцій) і часто сам контролює життєвий цикл реального суб’єкта.
- Відповідь: Різниця в намірі (Intent).
- Як надмірне використання патерну Strategy може негативно вплинути на продуктивність застосунку під навантаженням?
- Відповідь: Через створення мегаморфних точок виклику (Megamorphic Call Sites). Якщо в інтерфейсу
Strategyпонад дві реалізації, HotSpot JIT C2 деоптимізує Inline Cache: непрямий виклик черезitable/vtableне може бути заінлайнений, а процесор стикається зі штрафами провісника переходів (Branch Misprediction).
- Відповідь: Через створення мегаморфних точок виклику (Megamorphic Call Sites). Якщо в інтерфейсу
- До якої категорії належить патерн Repository і чи входить він до GoF?
- Відповідь:
Repositoryне входить до оригінального каталогу GoF 1994 року. Це архітектурний патерн корпоративних застосунків (PoEAA за Мартіном Фаулером). По суті, він поєднує структурну роль (Facade / Adapter над джерелом даних) з абстракцією доступу до колекції доменних сутностей.
- Відповідь:
- Чому
java.util.Observableвизнано застарілим (deprecated) у Java 9 і чим його замінюють?- Відповідь: Клас
Observableбув класом, а не інтерфейсом (ламав множинне успадкування), не був потокобезпечним, не підтримував типізацію (приймавObject) і не забезпечував зворотного тиску (Backpressure). На зміну прийшли Reactive Streams /java.util.concurrent.Flow, Project Reactor, SpringApplicationEventPublisherабо брокери повідомлень.
- Відповідь: Клас
Типові помилки та 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-методи там, де достатньо звичайного процедурного виклику.
Пов’язані теми
- Що таке патерни проектування — огляд, рівні архітектури та антипатерни
- Що таке Singleton — класичний породжувальний патерн
- У чому різниця між Factory Method та Abstract Factory — порівняння породжувальних патернів
- У чому переваги Decorator перед успадкуванням — структурний патерн у деталях
- Коли використовувати Strategy — поведінковий патерн та його оптимізація
- Які типи Proxy існують — динамічні проксі та CGLIB у Spring