🧩 Раздел 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). Lifecycle бинов, состояния соединений 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-методы там, где достаточно обычного процедурного вызова.

Связанные темы