Вопрос 1 · Раздел 2

Что такое паттерны проектирования?

GoF (Gang of Four — «банда четырёх»: Gamma, Helm, Johnson, Vlissides, авторы книги «Design Patterns», 1994).

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Паттерны проектирования — это зафиксированные в книгах и статьях решения, которые многократно применялись в реальных проектах и доказали свою работоспособность.

Зачем: не нужно проектировать решение с нуля. Экономия 2-5 часов на задачу, потому что паттерн уже отлажен и понятен другим разработчикам.

Простая аналогия: Представьте, что вы строите дом. Вам не нужно каждый раз придумывать, как сделать крышу или фундамент — есть типовые проекты, которые уже доказали свою эффективность. Паттерны — это такие “типовые проекты” для кода.

Зачем нужны:

  • Не изобретать велосипед
  • Использовать решения, которые уже работают
  • Общаться с коллегами на одном языке (“тут нужен Singleton”)

Пример:

// Паттерн Singleton — только один экземпляр класса
public class Logger {
    private static Logger instance = new Logger();
    private Logger() {}
    public static Logger getInstance() { return instance; }
    
    public void log(String message) {
        System.out.println(message);
    }
}

// Использование
Logger.getInstance().log("Hello!");

Когда использовать:

  • Когда сталкиваетесь с типовой проблемой
  • Когда код становится сложным и запутанным
  • Когда нужно сделать код понятнее для других разработчиков

🟡 Middle Level

Уровни паттернов

| Уровень | Примеры | Где применяется | | —————- | ————————————– | ———————- | | Идиомы языка | Try-with-resources, Optional, Streams | Синтаксис Java | | GoF паттерны | Strategy, Decorator, Observer, Factory | Взаимодействие классов | GoF (Gang of Four — «банда четырёх»: Gamma, Helm, Johnson, Vlissides, авторы книги «Design Patterns», 1994). | Enterprise | Repository, Unit of Work, Data Mapper | Работа с данными | | Cloud/Distributed | Circuit Breaker, Saga, CQRS | Микросервисы |

Основные категории

1. Порождающие (Creational)

  • Помогают создавать объекты
  • Примеры: Singleton, Factory, Builder
  • Зачем: Скрыть процесс создания объектов

2. Структурные (Structural)

  • Помогают компоновать классы и объекты
  • Примеры: Adapter, Decorator, Proxy
  • Зачем: Сделать совместимыми разные интерфейсы

3. Поведенческие (Behavioral)

  • Описывают взаимодействие между объектами
  • Примеры: Strategy, Observer, Chain of Responsibility
  • Зачем: Управлять алгоритмами и передачей данных

Паттерны в Modern Java

Многие классические паттерны упростились с приходом Java 8-21:

// ❌ Классическая Strategy (много классов)
public interface PaymentStrategy { void pay(); }
public class CreditCardStrategy implements PaymentStrategy { ... }
public class PayPalStrategy implements PaymentStrategy { ... }

// ✅ Modern Java (лямбды)
public void processPayment(UnaryOperator<BigDecimal> paymentStrategy) {
    BigDecimal result = paymentStrategy.apply(amount);
}

// Использование
processPayment(amount -> amount.subtract(discount));

Типичные ошибки

  1. Золотой молоток (Golden Hammer)
    // ❌ Использовать паттерн везде, где знаем
    // Создание Abstract Factory для одного объекта
       
    // ✅ Использовать когда реально нужно
    // Простой if-else лучше ненужной абстракции
    
  2. Overengineering
    • Слишком много уровней абстракции
    • Сложно читать и отлаживать
    • Stack trace на 50 уровней
  3. Карго-культ
    • Слепое копирование без понимания
    • Реализация Cloneable вместо конструктора копирования

Когда НЕ использовать паттерны

  1. Простые CRUD-приложения — прямой код читаемее абстракций
  2. Прототипы на выходные — скорость важнее архитектуры
  3. Когда паттерн не решает вашу проблему — не подгоняйте задачу под паттерн

🔴 Senior Level

Паттерн как архитектурный контракт

На глубоком уровне паттерн определяет:

  1. Разделение ответственности (Separation of Concerns)
    • Какие объекты за что отвечают
    • Четкие границы между компонентами
  2. Интерфейсы взаимодействия
    • Минимизация зависимостей (Loose Coupling)
    • Контракты между модулями
  3. Точки расширения
    • Где система может быть дополнена без изменения существующего кода
    • Open/Closed Principle на практике

Эволюция паттернов в Modern Java (8-21+)

Strategy / Command → Лямбды:

// Было: 10+ классов
// Стало: Function<T, R>, Consumer<T>, Predicate<T>

Template Method → Композиция:

// Вместо наследования — передача лямбд в конструктор
public class Processor {
    private final Function<Data, Result> transformer;
    
    public Processor(Function<Data, Result> transformer) {
        this.transformer = transformer;
    }
}

Singleton / Prototype → DI-контейнер:

// Spring управет жизненным циклом
@Component  // Singleton scope по умолчанию
public class UserService { }

@Scope("prototype")
public class OrderProcessor { }

Proxy → Spring AOP:

// @Transactional, @Cacheable — всё это Proxy
// JDK Dynamic Proxy или CGLIB под капотом
@Transactional
public void processOrder() { }

Архитектурные Trade-offs

Что выигрываем:

  • ✅ Гибкость и расширяемость
  • ✅ Тестируемость
  • ✅ Понятность для знакомых с паттерном
  • ✅ Слабая связанность

Что теряем:

  • ❌ Увеличение количества классов
  • ❌ Уровни косвенности (indirection)
  • ❌ Сложность отладки
  • ❌ Накладные расходы на производительность

Megamorphic Calls и JIT

// Паттерны влияют на производительность JVM!

// Monomorphic call (1 реализация) → инлайнинг
PaymentStrategy strategy = new CreditCard();
strategy.pay();  // JIT встраивает код → 0 накладных расходов

// Bimorphic call (2 реализации) → всё ещё быстро
// Megamorphic call (>2 реализаций) → косвенный вызов через vtable
// → Измеримая просадка по CPU в Hot-path!

Оптимизация:

// Для критичных путей: enum вместо интерфейса
public enum PaymentType {
    CREDIT_CARD { 
        @Override public void pay() { /* код */ }
    },
    PAYPAL { 
        @Override public void pay() { /* код */ }
    };
    public abstract void pay();
}
// → JIT лучше оптимизирует enum switch

Паттерны в распределённых системах

Cloud-Native паттерны:

Circuit Breaker → отказоустойчивость
Saga → распределённые транзакции
Sidecar → инфраструктура рядом с сервисом
CQRS → разделение чтения и записи
Event Sourcing → журнал событий вместо состояния

Взаимосвязь паттернов:

Abstract Factory (Creational)
  → возвращает Proxy (Structural)
    → который оборачивает бизнес-логику
      → управляемую через Strategy (Behavioral)

Chain of Responsibility (Behavioral)
  → собирается через Builder (Creational)

Facade (Structural)
  → упрощает интерфейс к подсистеме
    → управляемой через Strategy (Behavioral)

Принцип достаточности

Лучший паттерн — тот, что решает задачу минимальным кодом:

// ❌ Overengineering
public interface MessageValidator { boolean validate(Message m); }
public class EmailValidator implements MessageValidator { ... }
public class PhoneValidator implements MessageValidator { ... }
public class ValidatorFactory { ... }
public class ValidationStrategy { ... }

// ✅ Достаточно
public boolean isValid(Message m) {
    return m.getType() == EMAIL 
        ? emailRegex.matcher(m.getContent()).matches()
        : phoneRegex.matcher(m.getContent()).matches();
}

// Паттерн нужен ТОЛЬКО если:
// - Планируется расширение (новые типы валидации)
// - Сложная логика в каждой ветке
// - Нужно тестировать отдельно

Production Experience

Реальный сценарий #1: Overengineering убил читаемость

  • Проект: 15 паттернов на 10,000 строк кода
  • Проблема: новый разработчик тратит 2 недели на онбординг
  • Решение: упростили до 5 основных паттернов
  • Результат: онбординг 3 дня, меньше багов

Реальный сценарий #2: Pattern misuse в Spring

  • Ручной Singleton вместо @Component
  • Проблема: невозможно замокать в тестах
  • Решение: делегировали Spring IoC
  • Результат: тестируемость + гибкость

Best Practices

  1. Знайте паттерны, но не используйте без необходимости
  2. Modern Java упрощает многие паттерны (лямбды, Records)
  3. DI-контейнер заменяет ручные Singleton/Prototype/Factory
  4. Композиция > Наследование — гибче и менее хрупко
  5. Простота — если if-else достаточно, не усложняйте
  6. Документируйте — почему выбрали этот паттерн
  7. Проверяйте — не нарушает ли паттерн SOLID
  8. Думайте о JIT — мегаморфные вызовы влияют на CPU

Резюме для Senior

  • Паттерны — инструмент борьбы со сложностью, не цель
  • Modern Java делает многие паттерны “невидимыми” (лямбды, Records)
  • DI-контейнер — ваш Singleton/Factory/Prototype менеджер
  • Trade-offs: гибкость vs читаемость, расширяемость vs сложность
  • JIT оптимизации: monomorphic > megamorphic calls
  • Принцип достаточности: лучший паттерн — минимальный код
  • Распределённые системы — новые паттерны (Saga, Circuit Breaker)
  • Паттерны должны реагировать на реальную потребность, а не теоретическую

🎯 Шпаргалка для интервью

Обязательно знать:

  • Паттерны — проверенные решения типовых проблем, экономят 2-5 часов на задачу
  • Три категории: Creational (создание), Structural (композиция), Behavioral (взаимодействие)
  • GoF (Gang of Four) — Gamma, Helm, Johnson, Vlissides, авторы книги 1994 года
  • Modern Java (8-21) упрощает многие паттерны: лямбды заменяют Strategy, DI заменяет Singleton
  • Принцип достаточности: лучший паттерн — минимальный код, если if-else достаточно — не усложняйте
  • Overengineering убивает читаемость: 15 паттернов на 10K строк — это антипаттерн
  • Композиция предпочтительнее наследования — гибче и менее хрупко
  • Megamorphic calls (>2 реализаций) влияют на JIT оптимизации и CPU в hot-path

Частые уточняющие вопросы:

  • Когда НЕ использовать паттерны? — Простые CRUD, прототипы, паттерн не решает проблему
  • Чем идиома отличается от паттерна? — Идиома специфична языку (try-with-resources), паттерн универсален
  • Какие cloud-native паттерны вы знаете? — Circuit Breaker, Saga, CQRS, Event Sourcing, Sidecar
  • Что такое Golden Hammer? — Использование любимого паттерна везде, где только можно

Красные флаги (НЕ говорить):

  • “Я использую паттерны везде, где знаю” — это overengineering
  • “Singleton решает все проблемы” — Singleton считается антипаттерном во многих случаях
  • “Паттерны всегда улучшают производительность” — часто наоборот, добавляют overhead
  • “Я не использую паттерны, пишу проще” — незнание базовых архитектурных решений

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

  • [[2. Какие категории паттернов существуют]] — подробная классификация
  • [[16. Какие антипаттерны вы знаете]] — плохие решения, которые вредят коду
  • [[7. В чём разница между Factory Method и Abstract Factory]] — порождающие паттерны
  • [[10. Когда использовать Strategy]] — поведенческие паттерны на практике
  • [[12. В чём преимущество Decorator перед наследованием]] — композиция vs наследование