Какие категории паттернов существуют
Классические паттерны проектирования банды четырёх (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). | Lifecycle бинов, состояния соединений 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