Паттерны GOF

Порождающие паттерны (Creational Patterns)

1. Singleton (Одиночка)

Назначение: Гарантирует единственный экземпляр класса в приложении и предоставляет глобальный доступ к нему.

Когда использовать: Подключения к БД, логгеры, кэши, настройки конфигурации.

Ключевые особенности:

  • Приватный конструктор
  • Статический метод получения экземпляра
  • Ленивая инициализация (lazy loading)
public class DatabaseConnection {
    private static volatile DatabaseConnection instance;
    private Connection connection;
    
    private DatabaseConnection() {
        // Инициализация соединения
    }
    
    public static DatabaseConnection getInstance() {
        if (instance == null) {
            synchronized (DatabaseConnection.class) {
                if (instance == null) {
                    instance = new DatabaseConnection();
                }
            }
        }
        return instance;
    }
}

Плюсы: Контролируемый доступ, экономия памяти Минусы: Сложность тестирования, нарушение принципа единственной ответственности

2. Factory Method (Фабричный метод)

Назначение: Создание объектов без указания конкретного класса, делегируя решение подклассам.

Когда использовать: Когда заранее неизвестно, какие объекты нужно создавать, или когда создание объекта сложное.

abstract class PaymentProcessor {
    abstract Payment createPayment(String type);
    
    public void processPayment(String type, double amount) {
        Payment payment = createPayment(type);
        payment.pay(amount);
    }
}

class CreditCardProcessor extends PaymentProcessor {
    @Override
    Payment createPayment(String type) {
        return new CreditCardPayment();
    }
}

Применение в Java: Calendar.getInstance(), NumberFormat.getInstance()

3. Builder (Строитель)

Назначение: Пошаговое создание сложных объектов с множеством параметров.

Когда использовать: Много параметров конструктора, необязательные параметры, сложная логика создания.

public class HttpRequest {
    private String url;
    private String method;
    private Map<String, String> headers;
    private String body;
    
    private HttpRequest(Builder builder) {
        this.url = builder.url;
        this.method = builder.method;
        this.headers = builder.headers;
        this.body = builder.body;
    }
    
    public static class Builder {
        private String url;
        private String method = "GET";
        private Map<String, String> headers = new HashMap<>();
        private String body;
        
        public Builder url(String url) {
            this.url = url;
            return this;
        }
        
        public Builder method(String method) {
            this.method = method;
            return this;
        }
        
        public Builder header(String key, String value) {
            headers.put(key, value);
            return this;
        }
        
        public HttpRequest build() {
            return new HttpRequest(this);
        }
    }
}

Применение: StringBuilder, Stream API, Lombok @Builder

Структурные паттерны (Structural Patterns)

4. Adapter (Адаптер)

Назначение: Позволяет объектам с несовместимыми интерфейсами работать вместе.

Когда использовать: Интеграция legacy кода, работа с внешними API, несовместимые интерфейсы.

// Существующий интерфейс
interface MediaPlayer {
    void play(String filename);
}

// Внешняя библиотека
class AdvancedMediaPlayer {
    void playVlc(String filename) { /* ... */ }
    void playMp4(String filename) { /* ... */ }
}

// Адаптер
class MediaAdapter implements MediaPlayer {
    private AdvancedMediaPlayer advancedPlayer;
    
    public MediaAdapter(String audioType) {
        advancedPlayer = new AdvancedMediaPlayer();
    }
    
    @Override
    public void play(String filename) {
        if (filename.endsWith(".vlc")) {
            advancedPlayer.playVlc(filename);
        } else if (filename.endsWith(".mp4")) {
            advancedPlayer.playMp4(filename);
        }
    }
}

5. Decorator (Декоратор)

Назначение: Динамическое добавление функциональности объектам без изменения их структуры.

Когда использовать: Добавление поведения во время выполнения, альтернатива наследованию.

interface Coffee {
    double getCost();
    String getDescription();
}

class SimpleCoffee implements Coffee {
    @Override
    public double getCost() { return 1.0; }
    
    @Override
    public String getDescription() { return "Simple coffee"; }
}

abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;
    
    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }
}

class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }
    
    @Override
    public double getCost() {
        return decoratedCoffee.getCost() + 0.5;
    }
    
    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", milk";
    }
}

Применение в Java: InputStream, OutputStream, Collections.synchronizedList()

6. Facade (Фасад)

Назначение: Предоставляет упрощенный интерфейс к сложной подсистеме.

Когда использовать: Сложная система с множеством компонентов, нужен простой интерфейс.

class OrderFacade {
    private InventoryService inventoryService;
    private PaymentService paymentService;
    private ShippingService shippingService;
    private NotificationService notificationService;
    
    public boolean placeOrder(Order order) {
        if (!inventoryService.isAvailable(order.getItems())) {
            return false;
        }
        
        if (!paymentService.processPayment(order.getPayment())) {
            return false;
        }
        
        shippingService.arrangeShipping(order);
        notificationService.sendConfirmation(order);
        
        return true;
    }
}

Поведенческие паттерны (Behavioral Patterns)

7. Observer (Наблюдатель)

Назначение: Определяет зависимость один-ко-многим между объектами, автоматически уведомляя зависимые объекты об изменениях.

Когда использовать: Event-driven архитектура, MVC, pub/sub системы.

interface Observer {
    void update(String event);
}

class EventPublisher {
    private List<Observer> observers = new ArrayList<>();
    
    public void addObserver(Observer observer) {
        observers.add(observer);
    }
    
    public void notifyObservers(String event) {
        observers.forEach(observer -> observer.update(event));
    }
}

class EmailNotificationService implements Observer {
    @Override
    public void update(String event) {
        System.out.println("Sending email for event: " + event);
    }
}

Применение в Java: java.util.Observable, Event listeners в GUI

8. Strategy (Стратегия)

Назначение: Определяет семейство алгоритмов, инкапсулирует каждый и делает их взаимозаменяемыми.

Когда использовать: Множество способов выполнения задачи, выбор алгоритма во время выполнения.

interface PaymentStrategy {
    void pay(double amount);
}

class CreditCardPayment implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("Paid " + amount + " via credit card");
    }
}

class PayPalPayment implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("Paid " + amount + " via PayPal");
    }
}

class PaymentContext {
    private PaymentStrategy strategy;
    
    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void executePayment(double amount) {
        strategy.pay(amount);
    }
}

9. Command (Команда)

Назначение: Инкапсулирует запрос как объект, позволяя параметризовать клиентов с различными запросами.

Когда использовать: Undo/Redo операции, очереди команд, макрокоманды.

interface Command {
    void execute();
    void undo();
}

class TransferCommand implements Command {
    private Account from, to;
    private double amount;
    
    public TransferCommand(Account from, Account to, double amount) {
        this.from = from;
        this.to = to;
        this.amount = amount;
    }
    
    @Override
    public void execute() {
        from.withdraw(amount);
        to.deposit(amount);
    }
    
    @Override
    public void undo() {
        to.withdraw(amount);
        from.deposit(amount);
    }
}

class BankingSystem {
    private Stack<Command> commandHistory = new Stack<>();
    
    public void executeCommand(Command command) {
        command.execute();
        commandHistory.push(command);
    }
    
    public void undoLastCommand() {
        if (!commandHistory.isEmpty()) {
            commandHistory.pop().undo();
        }
    }
}

10. Template Method (Шаблонный метод)

Назначение: Определяет скелет алгоритма в базовом классе, позволяя подклассам переопределять отдельные шаги.

Когда использовать: Общий алгоритм с вариативными шагами, избежание дублирования кода.

abstract class DataProcessor {
    // Шаблонный метод
    public final void processData() {
        loadData();
        validateData();
        processDataImpl();
        saveData();
    }
    
    protected abstract void loadData();
    protected abstract void processDataImpl();
    protected abstract void saveData();
    
    protected void validateData() {
        // Общая логика валидации
        System.out.println("Validating data...");
    }
}

class CSVDataProcessor extends DataProcessor {
    @Override
    protected void loadData() {
        System.out.println("Loading CSV data");
    }
    
    @Override
    protected void processDataImpl() {
        System.out.println("Processing CSV data");
    }
    
    @Override
    protected void saveData() {
        System.out.println("Saving CSV data");
    }
}

Ключевые принципы для собеседования

SOLID принципы в паттернах:

  • Single Responsibility: Каждый класс имеет одну причину для изменения
  • Open/Closed: Открыт для расширения, закрыт для модификации (Strategy, Decorator)
  • Liskov Substitution: Подклассы должны заменять базовые классы (Template Method)
  • Interface Segregation: Клиенты не должны зависеть от неиспользуемых методов
  • Dependency Inversion: Зависимость от абстракций, а не от конкретных классов

Практические советы:

  1. Выбор паттерна: Понимайте проблему, которую решает каждый паттерн
  2. Производительность: Учитывайте overhead некоторых паттернов (Decorator, Observer)
  3. Тестируемость: Паттерны должны упрощать тестирование, а не усложнять
  4. Читаемость: Код должен быть понятен другим разработчикам
  5. Избегайте переусложнения: Не используйте паттерны там, где они не нужны

Часто задаваемые вопросы:

  • Отличие Strategy от Command: Strategy меняет алгоритм, Command инкапсулирует действие
  • Adapter vs Facade: Adapter для совместимости интерфейсов, Facade для упрощения
  • Singleton vs Static: Singleton может реализовывать интерфейсы, lazy loading
  • Observer vs Pub/Sub: Observer прямая связь, Pub/Sub через посредника
  • Factory vs Builder: Factory для простых объектов, Builder для сложных с множеством параметров

Порождающие паттерны

Что такое порождающие паттерны?

Порождающие паттерны решают проблемы создания объектов. Они скрывают сложность создания объектов от клиентского кода и делают систему независимой от того, как объекты создаются, компонуются и представляются.

Основные проблемы, которые решают:

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

1. Singleton (Одиночка)

Суть паттерна

Singleton гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к этому экземпляру.

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

  • Подключения к базе данных: Один пул соединений на всё приложение
  • Логгеры: Единый механизм логирования
  • Кэши: Один экземпляр кэша для всего приложения
  • Конфигурация: Настройки приложения должны быть едиными

Ключевые элементы

  • Приватный конструктор - запрещает создание экземпляров извне
  • Статическое поле - хранит единственный экземпляр
  • Статический метод - предоставляет доступ к экземпляру
  • Ленивая инициализация - создание экземпляра только при первом обращении
public class DatabaseConnectionPool {
    private static volatile DatabaseConnectionPool instance;
    private final List<Connection> connections;
    
    private DatabaseConnectionPool() {
        connections = new ArrayList<>();
        // Инициализация пула соединений
    }
    
    public static DatabaseConnectionPool getInstance() {
        if (instance == null) {
            synchronized (DatabaseConnectionPool.class) {
                if (instance == null) {
                    instance = new DatabaseConnectionPool();
                }
            }
        }
        return instance;
    }
    
    public Connection getConnection() {
        // Логика получения соединения
        return connections.get(0);
    }
}

Важные детали реализации

Double-checked locking - оптимизация для многопоточности:

  • Первая проверка без синхронизации (быстрая)
  • Вторая проверка внутри synchronized блока (безопасная)
  • volatile обеспечивает видимость изменений между потоками

Альтернативные реализации:

// Enum-based Singleton (рекомендуется)
public enum ConfigurationManager {
    INSTANCE;
    
    public void loadConfig() { /* ... */ }
}

// Initialization-on-demand holder
public class LazyHolder {
    private static class Holder {
        private static final LazyHolder INSTANCE = new LazyHolder();
    }
    
    public static LazyHolder getInstance() {
        return Holder.INSTANCE;
    }
}

Проблемы и решения

  • Тестирование: Сложно мокировать → используйте DI контейнеры
  • Сериализация: Может нарушить единственность → переопределите readResolve()
  • Reflection: Может создать новый экземпляр → добавьте защиту в конструктор

2. Factory Method (Фабричный метод)

Суть паттерна

Factory Method определяет интерфейс для создания объектов, но позволяет подклассам решать, какой класс инстанцировать.

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

  • Неизвестно заранее, какие объекты нужно создавать
  • Сложная логика создания объектов
  • Семейство связанных объектов с общим интерфейсом
  • Расширяемость: легко добавлять новые типы объектов

Ключевые элементы

  • Абстрактный создатель - определяет фабричный метод
  • Конкретные создатели - реализуют фабричный метод
  • Продукт - общий интерфейс создаваемых объектов
  • Конкретные продукты - различные реализации продукта
// Продукт - общий интерфейс
interface PaymentProcessor {
    void processPayment(double amount);
    String getPaymentMethod();
}

// Конкретные продукты
class CreditCardProcessor implements PaymentProcessor {
    public void processPayment(double amount) {
        System.out.println("Processing $" + amount + " via Credit Card");
    }
    
    public String getPaymentMethod() { return "Credit Card"; }
}

class PayPalProcessor implements PaymentProcessor {
    public void processPayment(double amount) {
        System.out.println("Processing $" + amount + " via PayPal");
    }
    
    public String getPaymentMethod() { return "PayPal"; }
}

// Абстрактный создатель
abstract class PaymentFactory {
    // Фабричный метод
    protected abstract PaymentProcessor createProcessor();
    
    // Бизнес-логика использует фабричный метод
    public void executePayment(double amount) {
        PaymentProcessor processor = createProcessor();
        processor.processPayment(amount);
    }
}

// Конкретные создатели
class CreditCardFactory extends PaymentFactory {
    protected PaymentProcessor createProcessor() {
        return new CreditCardProcessor();
    }
}

class PayPalFactory extends PaymentFactory {
    protected PaymentProcessor createProcessor() {
        return new PayPalProcessor();
    }
}

Использование в Java API

  • Calendar.getInstance() - создает календарь в зависимости от локали
  • NumberFormat.getInstance() - форматтер чисел
  • Collections.synchronizedList() - создает потокобезопасную обёртку

Преимущества

  • Слабая связанность: клиент не знает конкретные классы
  • Расширяемость: легко добавлять новые типы продуктов
  • Единое место создания: вся логика создания в одном месте

3. Abstract Factory (Абстрактная фабрика)

Суть паттерна

Abstract Factory предоставляет интерфейс для создания семейств связанных объектов без указания их конкретных классов.

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

  • Семейство связанных объектов должно использоваться вместе
  • Система должна быть независима от способа создания объектов
  • Различные конфигурации продуктов (например, UI для разных ОС)

Ключевые элементы

  • Абстрактная фабрика - интерфейс для создания семейства продуктов
  • Конкретные фабрики - реализуют создание конкретного семейства
  • Абстрактные продукты - интерфейсы для каждого типа продукта
  • Конкретные продукты - реализации для каждого семейства
// Абстрактные продукты
interface Database {
    void connect();
}

interface Cache {
    void store(String key, Object value);
}

// Конкретные продукты для MySQL
class MySQLDatabase implements Database {
    public void connect() { System.out.println("Connecting to MySQL"); }
}

class RedisCache implements Cache {
    public void store(String key, Object value) { 
        System.out.println("Storing in Redis: " + key); 
    }
}

// Конкретные продукты для PostgreSQL
class PostgreSQLDatabase implements Database {
    public void connect() { System.out.println("Connecting to PostgreSQL"); }
}

class MemcachedCache implements Cache {
    public void store(String key, Object value) { 
        System.out.println("Storing in Memcached: " + key); 
    }
}

// Абстрактная фабрика
interface InfrastructureFactory {
    Database createDatabase();
    Cache createCache();
}

// Конкретные фабрики
class MySQLInfrastructureFactory implements InfrastructureFactory {
    public Database createDatabase() { return new MySQLDatabase(); }
    public Cache createCache() { return new RedisCache(); }
}

class PostgreSQLInfrastructureFactory implements InfrastructureFactory {
    public Database createDatabase() { return new PostgreSQLDatabase(); }
    public Cache createCache() { return new MemcachedCache(); }
}

Отличие от Factory Method

  • Factory Method: создает один тип объектов
  • Abstract Factory: создает семейство связанных объектов

4. Builder (Строитель)

Суть паттерна

Builder позволяет создавать сложные объекты пошагово, конструируя их из частей.

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

  • Много параметров конструктора (больше 4-5)
  • Необязательные параметры с значениями по умолчанию
  • Сложная логика создания объекта
  • Неизменяемые объекты (immutable objects)

Ключевые элементы

  • Строитель - определяет шаги конструирования
  • Конкретный строитель - реализует шаги и хранит результат
  • Директор - управляет процессом конструирования (опционально)
  • Продукт - создаваемый сложный объект
public class HttpRequest {
    private final String url;
    private final String method;
    private final Map<String, String> headers;
    private final String body;
    private final int timeout;
    
    // Приватный конструктор - объект создается только через Builder
    private HttpRequest(Builder builder) {
        this.url = builder.url;
        this.method = builder.method;
        this.headers = new HashMap<>(builder.headers);
        this.body = builder.body;
        this.timeout = builder.timeout;
    }
    
    // Вложенный класс Builder
    public static class Builder {
        private String url;
        private String method = "GET";
        private Map<String, String> headers = new HashMap<>();
        private String body;
        private int timeout = 30000;
        
        public Builder url(String url) {
            this.url = url;
            return this;
        }
        
        public Builder method(String method) {
            this.method = method;
            return this;
        }
        
        public Builder header(String key, String value) {
            headers.put(key, value);
            return this;
        }
        
        public Builder body(String body) {
            this.body = body;
            return this;
        }
        
        public Builder timeout(int timeout) {
            this.timeout = timeout;
            return this;
        }
        
        public HttpRequest build() {
            if (url == null) {
                throw new IllegalStateException("URL is required");
            }
            return new HttpRequest(this);
        }
    }
}

// Использование
HttpRequest request = new HttpRequest.Builder()
    .url("https://api.example.com/users")
    .method("POST")
    .header("Content-Type", "application/json")
    .header("Authorization", "Bearer token123")
    .body("{\"name\":\"John\"}")
    .timeout(5000)
    .build();

Преимущества Builder

  • Читаемость: код самодокументируется
  • Гибкость: можно создавать объекты с разными комбинациями параметров
  • Валидация: проверки в методе build()
  • Неизменяемость: объект создается полностью сформированным

Инструменты для Builder

Lombok @Builder - автоматически генерирует Builder:

@Builder
@Data
public class User {
    private String name;
    private String email;
    private int age;
    private List<String> roles;
}

// Использование
User user = User.builder()
    .name("John")
    .email("john@example.com")
    .age(30)
    .roles(Arrays.asList("USER", "ADMIN"))
    .build();

5. Prototype (Прототип)

Суть паттерна

Prototype позволяет создавать новые объекты путем клонирования существующих экземпляров.

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

  • Создание объекта дорого (сложные вычисления, обращения к БД)
  • Объекты мало отличаются друг от друга
  • Конфигурация через примеры вместо конструирования с нуля
public class DatabaseConnection implements Cloneable {
    private String host;
    private int port;
    private String database;
    private Properties settings;
    
    // Дорогая инициализация
    private void initializeConnection() {
        // Сложная логика подключения
        System.out.println("Expensive connection initialization");
    }
    
    @Override
    public DatabaseConnection clone() {
        try {
            DatabaseConnection cloned = (DatabaseConnection) super.clone();
            // Глубокое клонирование для изменяемых полей
            cloned.settings = (Properties) settings.clone();
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException("Clone not supported", e);
        }
    }
}

Важные моменты

  • Поверхностное vs глубокое клонирование: учитывайте изменяемые поля
  • Интерфейс Cloneable: маркерный интерфейс для разрешения клонирования
  • Альтернативы: copy constructors, статические методы копирования

Сравнение порождающих паттернов

Паттерн Что решает Когда использовать
Singleton Контроль количества экземпляров Один экземпляр на приложение
Factory Method Создание объектов через наследование Неизвестен точный тип создаваемого объекта
Abstract Factory Создание семейств объектов Нужно создавать связанные объекты
Builder Создание сложных объектов пошагово Много параметров, сложная конструкция
Prototype Создание через клонирование Дорогое создание, похожие объекты

Практические советы для собеседования

Частые вопросы

  1. "Чем отличается Singleton от статического класса?"

    • Singleton может реализовывать интерфейсы
    • Ленивая инициализация
    • Может быть передан как параметр
  2. "Когда использовать Builder вместо конструктора?"

    • Больше 4-5 параметров
    • Много необязательных параметров
    • Нужна валидация перед созданием
  3. "Проблемы Singleton в многопоточности?"

    • Double-checked locking
    • Enum-based реализация
    • Initialization-on-demand holder

Применение в реальных проектах

  • Spring: ApplicationContext как Singleton
  • Hibernate: SessionFactory через Builder
  • HTTP клиенты: Request/Response builders
  • Конфигурация: Properties как Singleton
  • Пулы соединений: Database connection pools

Порождающие паттерны в Spring

Как Spring использует порождающие паттерны

Spring Framework активно применяет порождающие паттерны для управления жизненным циклом объектов, их созданием и конфигурированием. Понимание этих паттернов критически важно для Senior разработчика, так как это основа архитектуры Spring.

Основные преимущества использования Spring:

  • Inversion of Control (IoC) - Spring берет на себя создание и управление объектами
  • Dependency Injection (DI) - автоматическое внедрение зависимостей
  • Конфигурация через аннотации или XML
  • Управление жизненным циклом объектов

1. Singleton в Spring

Как Spring реализует Singleton

По умолчанию все бины в Spring являются Singleton-ами. Это означает, что Spring создает только один экземпляр каждого бина на весь ApplicationContext.

Scope бинов в Spring

@Component
@Scope("singleton") // По умолчанию
public class UserService {
    // Этот объект будет создан только один раз
}

@Component
@Scope("prototype") // Новый экземпляр каждый раз
public class UserRequest {
    // Каждый раз создается новый объект
}

@Component
@Scope("request") // Один экземпляр на HTTP запрос
public class RequestProcessor {
    // Новый объект для каждого HTTP запроса
}

Ключевые особенности Singleton в Spring

Thread Safety: Spring Singleton бины должны быть потокобезопасными, так как один экземпляр используется всеми потоками.

@Service
public class CounterService {
    private int count = 0; // ОПАСНО! Не потокобезопасно
    
    public synchronized void increment() { // Синхронизация
        count++;
    }
    
    // Лучше использовать AtomicInteger
    private final AtomicInteger atomicCount = new AtomicInteger(0);
    
    public void safeIncrement() {
        atomicCount.incrementAndGet();
    }
}

Ленивая инициализация - бины создаются при первом обращении:

@Component
@Lazy // Создается только при первом использовании
public class ExpensiveService {
    public ExpensiveService() {
        System.out.println("Дорогая инициализация");
    }
}

ApplicationContext как Singleton

ApplicationContext сам является Singleton-ом в рамках JVM. Он управляет всеми бинами и их жизненным циклом:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Application.class, args);
        
        // Получаем один и тот же экземпляр
        UserService service1 = context.getBean(UserService.class);
        UserService service2 = context.getBean(UserService.class);
        
        System.out.println(service1 == service2); // true
    }
}

2. Factory Method в Spring

@Configuration и @Bean как Factory Method

@Configuration классы в Spring работают как фабрики, а @Bean методы - как фабричные методы, создающие экземпляры объектов.

@Configuration
public class DatabaseConfig {
    
    // Фабричный метод для создания DataSource
    @Bean
    @Primary
    public DataSource primaryDataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/primary");
        dataSource.setUsername("user");
        dataSource.setPassword("password");
        dataSource.setMaximumPoolSize(20);
        return dataSource;
    }
    
    // Альтернативный DataSource для отчетов
    @Bean("reportsDataSource")
    public DataSource reportsDataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/reports");
        dataSource.setUsername("reports_user");
        dataSource.setPassword("reports_pass");
        dataSource.setMaximumPoolSize(5);
        return dataSource;
    }
}

FactoryBean интерфейс

FactoryBean - специальный интерфейс для создания сложных объектов:

@Component
public class ConnectionPoolFactory implements FactoryBean<ConnectionPool> {
    
    @Override
    public ConnectionPool getObject() throws Exception {
        // Сложная логика создания пула соединений
        ConnectionPool pool = new ConnectionPool();
        pool.setMinSize(5);
        pool.setMaxSize(50);
        pool.initialize();
        return pool;
    }
    
    @Override
    public Class<?> getObjectType() {
        return ConnectionPool.class;
    }
    
    @Override
    public boolean isSingleton() {
        return true; // Создаем Singleton
    }
}

Conditional Factory Methods

Условное создание бинов в зависимости от профиля или наличия других бинов:

@Configuration
public class PaymentConfig {
    
    @Bean
    @ConditionalOnProperty(name = "payment.provider", havingValue = "paypal")
    public PaymentService paypalPaymentService() {
        return new PayPalPaymentService();
    }
    
    @Bean
    @ConditionalOnProperty(name = "payment.provider", havingValue = "stripe")
    public PaymentService stripePaymentService() {
        return new StripePaymentService();
    }
    
    @Bean
    @ConditionalOnMissingBean(PaymentService.class)
    public PaymentService defaultPaymentService() {
        return new DefaultPaymentService();
    }
}

3. Abstract Factory в Spring

Profiles как Abstract Factory

Spring Profiles позволяют создавать различные наборы (семейства) бинов для разных окружений:

@Configuration
@Profile("development")
public class DevelopmentConfig {
    
    @Bean
    public EmailService emailService() {
        return new MockEmailService(); // Заглушка для разработки
    }
    
    @Bean
    public PaymentService paymentService() {
        return new MockPaymentService(); // Заглушка для тестов
    }
}

@Configuration
@Profile("production")
public class ProductionConfig {
    
    @Bean
    public EmailService emailService() {
        return new SmtpEmailService(); // Реальная отправка email
    }
    
    @Bean
    public PaymentService paymentService() {
        return new StripePaymentService(); // Реальная оплата
    }
}

Qualifier для семейств бинов

@Qualifier помогает группировать связанные бины:

@Service
public class NotificationService {
    
    private final EmailService emailService;
    private final SmsService smsService;
    
    public NotificationService(
        @Qualifier("production") EmailService emailService,
        @Qualifier("production") SmsService smsService) {
        this.emailService = emailService;
        this.smsService = smsService;
    }
}

@Configuration
public class NotificationConfig {
    
    @Bean
    @Qualifier("production")
    public EmailService productionEmailService() {
        return new SmtpEmailService();
    }
    
    @Bean
    @Qualifier("production")
    public SmsService productionSmsService() {
        return new TwilioSmsService();
    }
}

Auto-Configuration как Abstract Factory

Spring Boot Auto-Configuration работает как Abstract Factory, создавая наборы бинов в зависимости от classpath:

@Configuration
@ConditionalOnClass(DataSource.class)
@EnableConfigurationProperties(DatabaseProperties.class)
public class DatabaseAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public DataSource dataSource(DatabaseProperties properties) {
        return DataSourceBuilder.create()
            .url(properties.getUrl())
            .username(properties.getUsername())
            .password(properties.getPassword())
            .build();
    }
    
    @Bean
    @ConditionalOnBean(DataSource.class)
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

4. Builder в Spring

@ConfigurationProperties как Builder

@ConfigurationProperties работает как Builder для создания объектов конфигурации:

@ConfigurationProperties(prefix = "app.database")
@Data
@Builder
public class DatabaseProperties {
    private String url;
    private String username;
    private String password;
    private int maxConnections;
    private Duration connectionTimeout;
    private boolean ssl;
}

# application.yml
app:
  database:
    url: jdbc:mysql://localhost:3306/mydb
    username: user
    password: password
    max-connections: 20
    connection-timeout: 30s
    ssl: true

RestTemplate Builder

RestTemplateBuilder - встроенный Builder в Spring Boot:

@Configuration
public class RestTemplateConfig {
    
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder
            .rootUri("https://api.example.com")
            .connectTimeout(Duration.ofSeconds(5))
            .readTimeout(Duration.ofSeconds(30))
            .defaultHeader("User-Agent", "MyApp/1.0")
            .errorHandler(new CustomErrorHandler())
            .build();
    }
}

WebClient Builder

WebClient.Builder для реактивного HTTP клиента:

@Configuration
public class WebClientConfig {
    
    @Bean
    public WebClient webClient(WebClient.Builder builder) {
        return builder
            .baseUrl("https://api.example.com")
            .defaultHeader("Content-Type", "application/json")
            .defaultHeader("Accept", "application/json")
            .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(1024 * 1024))
            .build();
    }
}

Spring Data JPA Query Builder

Spring Data JPA использует Builder для построения запросов:

@Repository
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
    
    // Метод использует внутренний Builder для построения запроса
    List<User> findByNameAndAgeGreaterThan(String name, Integer age);
}

// Использование Specification как Builder
public class UserSpecs {
    
    public static Specification<User> hasName(String name) {
        return (root, query, cb) -> cb.equal(root.get("name"), name);
    }
    
    public static Specification<User> hasAgeGreaterThan(Integer age) {
        return (root, query, cb) -> cb.greaterThan(root.get("age"), age);
    }
}

// Использование
List<User> users = userRepository.findAll(
    Specification.where(UserSpecs.hasName("John"))
        .and(UserSpecs.hasAgeGreaterThan(25))
);

5. Prototype в Spring Security

SecurityContext и Authentication

Spring Security использует Prototype для создания контекстов безопасности:

@Component
@Scope("prototype") // Новый экземпляр для каждого запроса
public class SecurityContextHolder {
    
    private Authentication authentication;
    
    public void setAuthentication(Authentication auth) {
        this.authentication = auth;
    }
}

Request-scoped бины как Prototype

Request-scoped бины создаются заново для каждого HTTP запроса:

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
    
    private String requestId;
    private LocalDateTime startTime;
    
    @PostConstruct
    public void init() {
        this.requestId = UUID.randomUUID().toString();
        this.startTime = LocalDateTime.now();
    }
}

Интеграция паттернов в Spring Boot

Application Events как Observer + Factory

ApplicationEvent система Spring комбинирует несколько паттернов:

// Событие (создается через Factory)
public class UserRegisteredEvent extends ApplicationEvent {
    private final User user;
    
    public UserRegisteredEvent(Object source, User user) {
        super(source);
        this.user = user;
    }
}

// Создатель событий (Factory)
@Service
public class UserService {
    
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    public void registerUser(User user) {
        // Логика регистрации
        userRepository.save(user);
        
        // Создание и публикация события
        UserRegisteredEvent event = new UserRegisteredEvent(this, user);
        eventPublisher.publishEvent(event);
    }
}

// Обработчик событий (Observer)
@Component
public class EmailNotificationHandler {
    
    @EventListener
    public void handleUserRegistered(UserRegisteredEvent event) {
        User user = event.getUser();
        emailService.sendWelcomeEmail(user.getEmail());
    }
}

Caching как Singleton + Proxy

Spring Cache использует Singleton для кэшей и Proxy для перехвата вызовов:

@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager manager = new CaffeineCacheManager();
        manager.setCaffeine(Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(Duration.ofMinutes(10))
        );
        return manager;
    }
}

@Service
public class UserService {
    
    @Cacheable("users") // Proxy перехватывает вызов
    public User findById(Long id) {
        // Дорогая операция - вызывается только если нет в кэше
        return userRepository.findById(id).orElse(null);
    }
}

Практические советы для собеседования

Ключевые вопросы и ответы

1. "Чем отличается Singleton в Spring от классического Singleton?"

  • Spring Singleton: один экземпляр на ApplicationContext
  • Классический Singleton: один экземпляр на JVM
  • Spring управляет жизненным циклом
  • Возможность настройки через конфигурацию

2. "Как Spring решает проблему циклических зависимостей?"

  • Использует Proxy для создания "пустых" объектов
  • Трехуровневый кэш бинов
  • Field injection vs Constructor injection

3. "Когда использовать @Component vs @Bean?"

  • @Component: для ваших классов, автоматическое сканирование
  • @Bean: для внешних библиотек, сложная логика создания

Инструменты и аннотации

Создание бинов:

  • @Component, @Service, @Repository, @Controller
  • @Configuration + @Bean
  • @ComponentScan для автоматического поиска

Управление жизненным циклом:

  • @PostConstruct, @PreDestroy
  • @Scope (singleton, prototype, request, session)
  • @Lazy для ленивой инициализации

Условное создание:

  • @ConditionalOnProperty
  • @ConditionalOnClass
  • @ConditionalOnMissingBean
  • @Profile

Лучшие практики

1. Prefer Constructor Injection:

@Service
public class UserService {
    private final UserRepository userRepository;
    private final EmailService emailService;
    
    // Конструктор инъекция - обязательные зависимости
    public UserService(UserRepository userRepository, EmailService emailService) {
        this.userRepository = userRepository;
        this.emailService = emailService;
    }
}

2. Use @ConfigurationProperties для сложной конфигурации:

@ConfigurationProperties(prefix = "app.security")
@Validated
public class SecurityProperties {
    @NotNull
    private String secretKey;
    
    @Min(300)
    private int tokenExpirationSeconds;
}

3. Avoid циклических зависимостей:

// Плохо - циклическая зависимость
@Service
public class UserService {
    @Autowired
    private OrderService orderService; // OrderService тоже зависит от UserService
}

// Хорошо - использование событий
@Service
public class UserService {
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    public void updateUser(User user) {
        userRepository.save(user);
        eventPublisher.publishEvent(new UserUpdatedEvent(user));
    }
}

Производительность и мониторинг

Spring Boot Actuator для мониторинга бинов:

management:
  endpoints:
    web:
      exposure:
        include: beans, configprops, env

Endpoint /actuator/beans покажет все созданные бины и их зависимости - полезно для понимания того, как Spring применяет порождающие паттерны в вашем приложении.

Структурные паттерны

Что такое структурные паттерны?

Структурные паттерны решают проблемы композиции классов и объектов. Они показывают, как из простых объектов и классов строить более крупные структуры, сохраняя при этом гибкость и эффективность.

Основные проблемы, которые решают:

  • Как совместить несовместимые интерфейсы
  • Как добавить функциональность без изменения кода
  • Как упростить работу со сложными системами
  • Как эффективно управлять большим количеством объектов

Ключевое отличие от других паттернов: структурные паттерны фокусируются на отношениях между объектами, а не на их создании или поведении.


1. Adapter (Адаптер)

Суть паттерна

Adapter позволяет объектам с несовместимыми интерфейсами работать вместе. Это как переходник между различными стандартами - он "переводит" вызовы одного интерфейса в вызовы другого.

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

  • Legacy код: Нужно интегрировать старую систему с новой
  • Внешние библиотеки: API третьих сторон не подходит под ваш интерфейс
  • Разные форматы данных: JSON API нужно адаптировать под XML
  • Миграция систем: Постепенный переход с одной технологии на другую

Ключевые элементы

  • Target - интерфейс, который ожидает клиент
  • Adaptee - существующий класс с несовместимым интерфейсом
  • Adapter - класс, который связывает Target и Adaptee
  • Client - код, который использует Target интерфейс
// Target - то, что ожидает наш код
interface PaymentProcessor {
    PaymentResult processPayment(double amount, String currency);
}

// Adaptee - внешняя библиотека с другим интерфейсом
class ThirdPartyPaymentGateway {
    public boolean makePayment(int amountInCents, String currencyCode, Map<String, String> metadata) {
        System.out.println("Processing " + amountInCents + " cents in " + currencyCode);
        return true; // Упрощенная логика
    }
}

// Adapter - переводит вызовы
class PaymentGatewayAdapter implements PaymentProcessor {
    private final ThirdPartyPaymentGateway gateway;
    
    public PaymentGatewayAdapter(ThirdPartyPaymentGateway gateway) {
        this.gateway = gateway;
    }
    
    @Override
    public PaymentResult processPayment(double amount, String currency) {
        // Конвертируем доллары в центы
        int amountInCents = (int) (amount * 100);
        
        // Подготавливаем метаданные
        Map<String, String> metadata = new HashMap<>();
        metadata.put("source", "web-app");
        
        // Вызываем адаптируемый метод
        boolean success = gateway.makePayment(amountInCents, currency, metadata);
        
        return new PaymentResult(success, success ? "SUCCESS" : "FAILED");
    }
}

// Использование
PaymentProcessor processor = new PaymentGatewayAdapter(new ThirdPartyPaymentGateway());
PaymentResult result = processor.processPayment(99.99, "USD");

Применение в Java

  • Collections.list() - адаптирует Enumeration к List
  • Arrays.asList() - адаптирует массив к List
  • InputStreamReader - адаптирует InputStream к Reader

Практические примеры в backend

// Адаптер для логирования
class Slf4jToLog4jAdapter implements Logger {
    private final org.apache.log4j.Logger log4jLogger;
    
    public void info(String message) {
        log4jLogger.info(message);
    }
}

// Адаптер для кэширования
class RedisCacheAdapter implements CacheManager {
    private final JedisPool jedisPool;
    
    public void put(String key, Object value) {
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.set(key, serialize(value));
        }
    }
}

2. Decorator (Декоратор)

Суть паттерна

Decorator позволяет динамически добавлять объектам новую функциональность, оборачивая их в полезные "обертки". Это альтернатива наследованию для расширения поведения.

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

  • Добавление функций во время выполнения: Логирование, кэширование, валидация
  • Избежание "взрыва классов": Вместо создания множества подклассов
  • Цепочка обработчиков: Middleware в web-приложениях
  • Прозрачное расширение: Клиент не знает о декораторах

Ключевые элементы

  • Component - общий интерфейс для объектов и декораторов
  • ConcreteComponent - основной объект, к которому добавляется функциональность
  • Decorator - базовый класс для всех декораторов
  • ConcreteDecorator - конкретные декораторы с дополнительной функциональностью
// Component - общий интерфейс
interface DataSource {
    void writeData(String data);
    String readData();
}

// ConcreteComponent - базовая функциональность
class FileDataSource implements DataSource {
    private String filename;
    private String data = "";
    
    public FileDataSource(String filename) {
        this.filename = filename;
    }
    
    @Override
    public void writeData(String data) {
        this.data = data;
        System.out.println("Writing to file: " + filename);
    }
    
    @Override
    public String readData() {
        System.out.println("Reading from file: " + filename);
        return data;
    }
}

// Базовый декоратор
abstract class DataSourceDecorator implements DataSource {
    protected DataSource wrappee;
    
    public DataSourceDecorator(DataSource source) {
        this.wrappee = source;
    }
    
    @Override
    public void writeData(String data) {
        wrappee.writeData(data);
    }
    
    @Override
    public String readData() {
        return wrappee.readData();
    }
}

// Конкретные декораторы
class EncryptionDecorator extends DataSourceDecorator {
    public EncryptionDecorator(DataSource source) {
        super(source);
    }
    
    @Override
    public void writeData(String data) {
        super.writeData(encrypt(data));
    }
    
    @Override
    public String readData() {
        return decrypt(super.readData());
    }
    
    private String encrypt(String data) {
        return "encrypted(" + data + ")";
    }
    
    private String decrypt(String data) {
        return data.replace("encrypted(", "").replace(")", "");
    }
}

class CompressionDecorator extends DataSourceDecorator {
    public CompressionDecorator(DataSource source) {
        super(source);
    }
    
    @Override
    public void writeData(String data) {
        super.writeData(compress(data));
    }
    
    @Override
    public String readData() {
        return decompress(super.readData());
    }
    
    private String compress(String data) {
        return "compressed(" + data + ")";
    }
    
    private String decompress(String data) {
        return data.replace("compressed(", "").replace(")", "");
    }
}

// Использование - можно комбинировать декораторы
DataSource source = new FileDataSource("data.txt");
source = new CompressionDecorator(source);
source = new EncryptionDecorator(source);

source.writeData("Hello World!"); // Будет и сжато, и зашифровано

Применение в Java

  • InputStream/OutputStream: BufferedInputStream, GZIPInputStream
  • Collections: Collections.synchronizedList(), Collections.unmodifiableList()
  • Servlet Filters: Цепочка фильтров в web-приложениях

Spring примеры

// Декоратор для методов сервиса
@Component
public class CachingServiceDecorator implements UserService {
    private final UserService userService;
    private final CacheManager cacheManager;
    
    @Override
    public User findById(Long id) {
        return cacheManager.get(id, () -> userService.findById(id));
    }
}

// AOP как декоратор
@Aspect
@Component
public class LoggingAspect {
    
    @Around("@annotation(Loggable)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed(); // Вызов оригинального метода
        long executionTime = System.currentTimeMillis() - start;
        logger.info("Method {} executed in {} ms", joinPoint.getSignature().getName(), executionTime);
        return result;
    }
}

3. Facade (Фасад)

Суть паттерна

Facade предоставляет простой интерфейс к сложной подсистеме. Это как пульт управления, который скрывает сложность множества компонентов за простыми кнопками.

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

  • Сложная подсистема: Много взаимосвязанных классов
  • Упрощение API: Клиентам нужен простой интерфейс
  • Слой абстракции: Скрыть детали реализации
  • Интеграция с legacy системами: Обернуть старый код в современный интерфейс

Ключевые элементы

  • Facade - упрощенный интерфейс к подсистеме
  • Subsystem classes - множество классов сложной подсистемы
  • Client - использует только Facade, не знает о внутренних классах
// Сложная подсистема - множество сервисов
class InventoryService {
    public boolean checkAvailability(String productId, int quantity) {
        System.out.println("Checking inventory for " + productId);
        return true;
    }
    
    public void reserveItems(String productId, int quantity) {
        System.out.println("Reserving " + quantity + " of " + productId);
    }
}

class PaymentService {
    public boolean processPayment(String cardNumber, double amount) {
        System.out.println("Processing payment: $" + amount);
        return true;
    }
    
    public void refund(String transactionId) {
        System.out.println("Refunding transaction: " + transactionId);
    }
}

class ShippingService {
    public String scheduleDelivery(String address, List<String> items) {
        System.out.println("Scheduling delivery to " + address);
        return "TRACK123";
    }
}

class NotificationService {
    public void sendOrderConfirmation(String email, String orderId) {
        System.out.println("Sending confirmation to " + email);
    }
    
    public void sendShippingNotification(String email, String trackingId) {
        System.out.println("Sending shipping notification with tracking: " + trackingId);
    }
}

// Facade - простой интерфейс ко всей подсистеме
class OrderFacade {
    private final InventoryService inventoryService;
    private final PaymentService paymentService;
    private final ShippingService shippingService;
    private final NotificationService notificationService;
    
    public OrderFacade() {
        this.inventoryService = new InventoryService();
        this.paymentService = new PaymentService();
        this.shippingService = new ShippingService();
        this.notificationService = new NotificationService();
    }
    
    // Простой метод, который координирует всю сложность
    public OrderResult placeOrder(OrderRequest request) {
        try {
            // 1. Проверяем наличие товара
            if (!inventoryService.checkAvailability(request.getProductId(), request.getQuantity())) {
                return OrderResult.failed("Product not available");
            }
            
            // 2. Резервируем товар
            inventoryService.reserveItems(request.getProductId(), request.getQuantity());
            
            // 3. Обрабатываем платеж
            if (!paymentService.processPayment(request.getCardNumber(), request.getAmount())) {
                return OrderResult.failed("Payment failed");
            }
            
            // 4. Планируем доставку
            String trackingId = shippingService.scheduleDelivery(
                request.getShippingAddress(), 
                List.of(request.getProductId())
            );
            
            // 5. Отправляем уведомления
            String orderId = "ORDER-" + System.currentTimeMillis();
            notificationService.sendOrderConfirmation(request.getEmail(), orderId);
            notificationService.sendShippingNotification(request.getEmail(), trackingId);
            
            return OrderResult.success(orderId, trackingId);
            
        } catch (Exception e) {
            return OrderResult.failed("Order processing failed: " + e.getMessage());
        }
    }
}

// Клиент использует только простой интерфейс
OrderFacade orderFacade = new OrderFacade();
OrderRequest request = new OrderRequest("PRODUCT-123", 2, "john@email.com", "123 Main St", "4111111111111111", 99.99);
OrderResult result = orderFacade.placeOrder(request);

Применение в реальных проектах

// Spring Boot Actuator как Facade
@RestController
public class HealthFacade {
    private final DatabaseHealthIndicator dbHealth;
    private final RedisHealthIndicator redisHealth;
    private final QueueHealthIndicator queueHealth;
    
    @GetMapping("/health")
    public HealthStatus getOverallHealth() {
        // Фасад объединяет проверки всех подсистем
        return HealthStatus.builder()
            .database(dbHealth.check())
            .cache(redisHealth.check())
            .queue(queueHealth.check())
            .build();
    }
}

// Service Layer как Facade для Repository слоя
@Service
@Transactional
public class UserManagementFacade {
    private final UserRepository userRepository;
    private final RoleRepository roleRepository;
    private final AuditService auditService;
    private final EmailService emailService;
    
    public void createUserWithRoles(CreateUserRequest request) {
        // Фасад координирует работу нескольких компонентов
        User user = userRepository.save(new User(request.getName(), request.getEmail()));
        
        request.getRoles().forEach(roleName -> {
            Role role = roleRepository.findByName(roleName);
            user.addRole(role);
        });
        
        auditService.logUserCreation(user);
        emailService.sendWelcomeEmail(user.getEmail());
    }
}

4. Proxy (Заместитель)

Суть паттерна

Proxy предоставляет объект-заместитель, который контролирует доступ к другому объекту. Proxy может добавлять дополнительную логику: ленивую загрузку, кэширование, контроль доступа, логирование.

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

  • Ленивая инициализация: Создание дорогих объектов по требованию
  • Контроль доступа: Проверка прав перед выполнением операций
  • Кэширование: Сохранение результатов дорогих операций
  • Логирование: Отслеживание обращений к объекту
  • Удаленные объекты: Работа с объектами через сеть

Типы Proxy

  • Virtual Proxy - ленивая загрузка
  • Protection Proxy - контроль доступа
  • Remote Proxy - работа с удаленными объектами
  • Caching Proxy - кэширование результатов
// Subject - общий интерфейс для RealSubject и Proxy
interface DatabaseService {
    List<User> getAllUsers();
    User getUserById(Long id);
}

// RealSubject - реальный объект, который выполняет работу
class DatabaseServiceImpl implements DatabaseService {
    
    @Override
    public List<User> getAllUsers() {
        // Имитируем дорогую операцию БД
        try {
            Thread.sleep(2000); // 2 секунды
            System.out.println("Loading all users from database...");
            return Arrays.asList(
                new User(1L, "John"),
                new User(2L, "Jane")
            );
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    
    @Override
    public User getUserById(Long id) {
        try {
            Thread.sleep(1000); // 1 секунда
            System.out.println("Loading user " + id + " from database...");
            return new User(id, "User" + id);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

// Proxy - добавляет кэширование и ленивую загрузку
class CachingDatabaseProxy implements DatabaseService {
    private DatabaseService realService;
    private Map<String, Object> cache = new ConcurrentHashMap<>();
    private static final int CACHE_TTL_SECONDS = 300; // 5 минут
    
    // Ленивая инициализация реального сервиса
    private DatabaseService getRealService() {
        if (realService == null) {
            System.out.println("Initializing real database service...");
            realService = new DatabaseServiceImpl();
        }
        return realService;
    }
    
    @Override
    public List<User> getAllUsers() {
        String cacheKey = "all_users";
        
        // Проверяем кэш
        CachedResult<List<User>> cached = (CachedResult<List<User>>) cache.get(cacheKey);
        if (cached != null && !cached.isExpired()) {
            System.out.println("Returning cached users");
            return cached.getData();
        }
        
        // Загружаем из БД
        List<User> users = getRealService().getAllUsers();
        
        // Сохраняем в кэш
        cache.put(cacheKey, new CachedResult<>(users, CACHE_TTL_SECONDS));
        
        return users;
    }
    
    @Override
    public User getUserById(Long id) {
        String cacheKey = "user_" + id;
        
        CachedResult<User> cached = (CachedResult<User>) cache.get(cacheKey);
        if (cached != null && !cached.isExpired()) {
            System.out.println("Returning cached user " + id);
            return cached.getData();
        }
        
        User user = getRealService().getUserById(id);
        cache.put(cacheKey, new CachedResult<>(user, CACHE_TTL_SECONDS));
        
        return user;
    }
}

// Вспомогательный класс для кэширования
class CachedResult<T> {
    private final T data;
    private final long timestamp;
    private final int ttlSeconds;
    
    public CachedResult(T data, int ttlSeconds) {
        this.data = data;
        this.ttlSeconds = ttlSeconds;
        this.timestamp = System.currentTimeMillis();
    }
    
    public boolean isExpired() {
        return System.currentTimeMillis() - timestamp > ttlSeconds * 1000;
    }
    
    public T getData() { return data; }
}

Применение в Java и Spring

// Spring AOP как Dynamic Proxy
@Service
public class UserServiceImpl implements UserService {
    
    @Cacheable("users")
    @PreAuthorize("hasRole('ADMIN')")
    public User findById(Long id) {
        return userRepository.findById(id);
    }
}

// JPA Lazy Loading как Virtual Proxy
@Entity
public class Order {
    @OneToMany(fetch = FetchType.LAZY) // Proxy для ленивой загрузки
    private List<OrderItem> items;
}

// HTTP Client как Remote Proxy
@FeignClient(name = "user-service")
public interface UserServiceProxy {
    @GetMapping("/users/{id}")
    User getUserById(@PathVariable Long id);
}

5. Composite (Компоновщик)

Суть паттерна

Composite позволяет сгруппировать объекты в древовидную структуру и работать с ней так, как будто это единичный объект. Клиенты одинаково обращаются как к отдельным объектам, так и к их композициям.

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

  • Иерархические структуры: Файловые системы, организационные структуры
  • Рекурсивные операции: Операции должны применяться ко всему дереву
  • Единообразная обработка: Листья и ветки обрабатываются одинаково
// Component - общий интерфейс для листьев и композитов
interface FileSystemComponent {
    String getName();
    long getSize();
    void display(String indent);
}

// Leaf - конечный элемент (файл)
class File implements FileSystemComponent {
    private String name;
    private long size;
    
    public File(String name, long size) {
        this.name = name;
        this.size = size;
    }
    
    @Override
    public String getName() { return name; }
    
    @Override
    public long getSize() { return size; }
    
    @Override
    public void display(String indent) {
        System.out.println(indent + "📄 " + name + " (" + size + " bytes)");
    }
}

// Composite - составной элемент (папка)
class Directory implements FileSystemComponent {
    private String name;
    private List<FileSystemComponent> children = new ArrayList<>();
    
    public Directory(String name) {
        this.name = name;
    }
    
    public void add(FileSystemComponent component) {
        children.add(component);
    }
    
    public void remove(FileSystemComponent component) {
        children.remove(component);
    }
    
    @Override
    public String getName() { return name; }
    
    @Override
    public long getSize() {
        // Рекурсивно вычисляем размер всех дочерних элементов
        return children.stream()
            .mapToLong(FileSystemComponent::getSize)
            .sum();
    }
    
    @Override
    public void display(String indent) {
        System.out.println(indent + "📁 " + name + "/ (" + getSize() + " bytes total)");
        // Рекурсивно отображаем все дочерние элементы
        children.forEach(child -> child.display(indent + "  "));
    }
}

// Использование - одинаково работаем с файлами и папками
Directory root = new Directory("root");
Directory documents = new Directory("documents");
Directory photos = new Directory("photos");

documents.add(new File("report.pdf", 1024));
documents.add(new File("presentation.pptx", 2048));

photos.add(new File("vacation1.jpg", 512));
photos.add(new File("vacation2.jpg", 768));

root.add(documents);
root.add(photos);
root.add(new File("readme.txt", 256));

root.display(""); // Выводит всю иерархию
System.out.println("Total size: " + root.getSize() + " bytes");

Применение в реальных проектах

// Меню приложения
interface MenuComponent {
    void execute();
    String getTitle();
}

class MenuItem implements MenuComponent {
    private String title;
    private Runnable action;
    
    public void execute() { action.run(); }
}

class Menu implements MenuComponent {
    private String title;
    private List<MenuComponent> items = new ArrayList<>();
    
    public void execute() {
        items.forEach(MenuComponent::execute);
    }
}

// Валидация форм
interface Validator {
    ValidationResult validate(Object value);
}

class CompositeValidator implements Validator {
    private List<Validator> validators = new ArrayList<>();
    
    public ValidationResult validate(Object value) {
        return validators.stream()
            .map(v -> v.validate(value))
            .reduce(ValidationResult.success(), ValidationResult::combine);
    }
}

6. Bridge (Мост)

Суть паттерна

Bridge разделяет абстракцию и её реализацию, позволяя им изменяться независимо. Это полезно, когда у вас есть несколько способов реализации и несколько вариантов использования.

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

  • Множественные реализации: Разные способы выполнения одной задачи
  • Избежание постоянного связывания: Абстракция и реализация должны выбираться во время выполнения
  • Расширяемость: Легко добавлять новые абстракции и реализации
// Implementor - интерфейс для конкретных реализаций
interface MessageSender {
    void sendMessage(String message, String recipient);
}

// ConcreteImplementor - конкретные реализации
class EmailSender implements MessageSender {
    @Override
    public void sendMessage(String message, String recipient) {
        System.out.println("Email to " + recipient + ": " + message);
    }
}

class SmsSender implements MessageSender {
    @Override
    public void sendMessage(String message, String recipient) {
        System.out.println("SMS to " + recipient + ": " + message);
    }
}

class SlackSender implements MessageSender {
    @Override
    public void sendMessage(String message, String recipient) {
        System.out.println("Slack to " + recipient + ": " + message);
    }
}

// Abstraction - базовый класс для различных типов уведомлений
abstract class Notification {
    protected MessageSender messageSender;
    
    public Notification(MessageSender messageSender) {
        this.messageSender = messageSender;
    }
    
    public abstract void notify(String message);
}

// Refined Abstraction - конкретные типы уведомлений
class SimpleNotification extends Notification {
    private String recipient;
    
    public SimpleNotification(String recipient, MessageSender messageSender) {
        super(messageSender);
        this.recipient = recipient;
    }
    
    @Override
    public void notify(String message) {
        messageSender.sendMessage(message, recipient);
    }
}

class UrgentNotification extends Notification {
    private String recipient;
    
    public UrgentNotification(String recipient, MessageSender messageSender) {
        super(messageSender);
        this.recipient = recipient;
    }
    
    @Override
    public void notify(String message) {
        messageSender.sendMessage("URGENT: " + message, recipient);
        messageSender.sendMessage("This is a follow-up for urgent message", recipient);
    }
}

// Использование - можем комбинировать любые абстракции с любыми реализациями
Notification emailNotification = new SimpleNotification("john@example.com", new EmailSender());
Notification urgentSms = new UrgentNotification("+1234567890", new SmsSender());
Notification slackAlert = new UrgentNotification("@john", new SlackSender());

emailNotification.notify("Your order has been shipped");
urgentSms.notify("Server is down");
slackAlert.notify("Deploy completed");

Сравнение структурных паттернов

Паттерн Назначение Ключевая особенность
Adapter Совместимость интерфейсов Конвертирует один интерфейс в другой
Decorator Добавление функций Расширяет поведение без наследования
Facade Упрощение интерфейса Скрывает сложность подсистемы
Proxy Контроль доступа Управляет доступом к объекту
Composite Древовидные структуры Единообразная работа с частью и целым
Bridge Разделение абстракции Независимое изменение абстракции и реализации

Практические советы для собеседования

Частые вопросы

1. "Чем отличается Adapter от Facade?"

  • Adapter: Делает совместимыми два интерфейса
  • Facade: Упрощает сложный интерфейс

2. "Когда использовать Decorator вместо наследования?"

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

3. "Чем отличается Proxy от Decorator?"

  • Proxy: Контролирует доступ к объекту (тот же интерфейс)
  • Decorator: Расширяет функциональность объекта

Структурные паттерны в Spring

Как Spring использует структурные паттерны

Spring Framework широко применяет структурные паттерны для создания гибкой, расширяемой архитектуры. Понимание этих паттернов критически важно для Senior разработчика, так как они лежат в основе многих ключевых механизмов Spring.

Основные преимущества Spring подхода:

  • Прозрачность: Паттерны работают "за кулисами", не усложняя код
  • Конфигурируемость: Поведение настраивается через аннотации и конфигурацию
  • Интеграция: Паттерны работают совместно, усиливая друг друга
  • AOP интеграция: Aspect-Oriented Programming как основа многих паттернов

1. Proxy в Spring

Spring AOP как основа Proxy паттерна

Spring AOP (Aspect-Oriented Programming) - это фундаментальный механизм Spring, который реализует Proxy паттерн для добавления cross-cutting concerns (сквозной функциональности) к бизнес-логике.

Как работает Spring AOP Proxy

Spring создает прокси-объекты двумя способами:

  • JDK Dynamic Proxy - для интерфейсов
  • CGLIB Proxy - для классов

Ключевая особенность: Клиентский код не знает, что работает с прокси, а не с оригинальным объектом.

@Service
@Transactional // Spring создаст прокси для управления транзакциями
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Cacheable("users") // Еще один прокси для кэширования
    @PreAuthorize("hasRole('ADMIN')") // Прокси для безопасности
    public User findById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    
    @Retryable(maxAttempts = 3) // Прокси для retry логики
    public void updateUser(User user) {
        userRepository.save(user);
    }
}

Детальное объяснение работы

Когда Spring видит аннотации вроде @Transactional, он:

  1. Создает прокси-класс, который имплементирует тот же интерфейс
  2. Перехватывает вызовы методов в прокси
  3. Выполняет дополнительную логику (открытие транзакции)
  4. Вызывает оригинальный метод
  5. Выполняет завершающую логику (коммит/роллбек транзакции)

Практический пример создания кастомного аспекта

@Aspect
@Component
public class PerformanceMonitoringAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitoringAspect.class);
    
    // Pointcut - определяет, где применяется аспект
    @Around("@annotation(MonitorPerformance)")
    public Object monitorExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        String methodName = joinPoint.getSignature().getName();
        
        try {
            // Вызов оригинального метода
            Object result = joinPoint.proceed();
            
            long executionTime = System.currentTimeMillis() - startTime;
            logger.info("Method {} executed in {} ms", methodName, executionTime);
            
            return result;
        } catch (Exception e) {
            logger.error("Method {} failed after {} ms", methodName, System.currentTimeMillis() - startTime);
            throw e;
        }
    }
}

// Кастомная аннотация
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MonitorPerformance {
}

// Использование
@Service
public class OrderService {
    
    @MonitorPerformance // Spring создаст прокси для мониторинга
    public Order processOrder(OrderRequest request) {
        // Бизнес-логика
        return new Order();
    }
}

Spring Boot Actuator как Proxy

Spring Boot Actuator использует прокси для мониторинга приложения:

@RestController
public class HealthController {
    
    @Autowired
    private HealthIndicator dbHealthIndicator;
    
    @GetMapping("/actuator/health")
    public ResponseEntity<?> health() {
        // Actuator создает прокси для каждого HealthIndicator
        // и агрегирует результаты
        return ResponseEntity.ok(healthService.getOverallHealth());
    }
}

2. Decorator в Spring

Spring Security как Decorator

Spring Security широко использует Decorator для добавления слоев безопасности к существующим компонентам без изменения их кода.

Filter Chain как цепочка декораторов

SecurityFilterChain - это классический пример Decorator паттерна:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            // Каждый метод добавляет новый декоратор
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                .loginPage("/login")
                .defaultSuccessUrl("/dashboard")
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .csrf(csrf -> csrf.disable())
            .build();
    }
}

Объяснение работы Security Filters

Каждый фильтр - это декоратор, который:

  1. Получает запрос от предыдущего фильтра
  2. Выполняет свою специфическую логику (аутентификация, авторизация, CSRF защита)
  3. Передает запрос дальше по цепочке или прерывает выполнение

Method Security как Decorator

@Service
public class BankingService {
    
    // Декоратор для проверки прав доступа
    @PreAuthorize("hasRole('ADMIN') or #accountId == authentication.principal.accountId")
    public Account getAccount(Long accountId) {
        return accountRepository.findById(accountId);
    }
    
    // Декоратор для post-обработки результата
    @PostAuthorize("returnObject.owner == authentication.principal.username")
    public Account findAccountByNumber(String accountNumber) {
        return accountRepository.findByNumber(accountNumber);
    }
    
    // Декоратор для фильтрации коллекций
    @PostFilter("filterObject.isPublic() or filterObject.owner == authentication.principal.username")
    public List<Document> getAllDocuments() {
        return documentRepository.findAll();
    }
}

Spring Cache как Decorator

Spring Cache добавляет кэширование к методам прозрачно:

@Service
@CacheConfig(cacheNames = "users")
public class UserService {
    
    @Cacheable(key = "#id") // Декоратор добавляет кэширование
    public User findById(Long id) {
        System.out.println("Loading user from database: " + id);
        return userRepository.findById(id).orElse(null);
    }
    
    @CachePut(key = "#user.id") // Декоратор обновляет кэш
    public User updateUser(User user) {
        return userRepository.save(user);
    }
    
    @CacheEvict(key = "#id") // Декоратор удаляет из кэша
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
    
    @CacheEvict(allEntries = true) // Очищает весь кэш
    public void clearCache() {
        // Метод может быть пустым
    }
}

HTTP Client Interceptors как Decorator

RestTemplate и WebClient используют interceptors как декораторы:

@Configuration
public class HttpClientConfig {
    
    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        
        // Добавляем декораторы (interceptors)
        List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
        
        // Декоратор для логирования
        interceptors.add(new LoggingInterceptor());
        
        // Декоратор для аутентификации
        interceptors.add(new AuthenticationInterceptor());
        
        // Декоратор для retry логики
        interceptors.add(new RetryInterceptor());
        
        restTemplate.setInterceptors(interceptors);
        return restTemplate;
    }
}

// Пример декоратора для логирования
public class LoggingInterceptor implements ClientHttpRequestInterceptor {
    
    @Override
    public ClientHttpResponse intercept(
            HttpRequest request, 
            byte[] body, 
            ClientHttpRequestExecution execution) throws IOException {
        
        // Логика до выполнения запроса
        logger.info("Request: {} {}", request.getMethod(), request.getURI());
        
        // Вызов следующего декоратора/оригинального запроса
        ClientHttpResponse response = execution.execute(request, body);
        
        // Логика после выполнения запроса
        logger.info("Response: {}", response.getStatusCode());
        
        return response;
    }
}

3. Adapter в Spring

Spring Data JPA как Adapter

Spring Data JPA служит адаптером между объектно-ориентированным Java кодом и реляционными базами данных:

// Ваш доменный интерфейс
public interface UserService {
    List<User> findActiveUsers();
    User findByEmail(String email);
}

// Spring Data создает адаптер автоматически
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    // Spring переводит имя метода в SQL запрос
    List<User> findByActiveTrue();
    
    // Кастомный запрос через аннотацию
    @Query("SELECT u FROM User u WHERE u.email = :email AND u.active = true")
    Optional<User> findActiveUserByEmail(@Param("email") String email);
    
    // Native SQL запрос
    @Query(value = "SELECT * FROM users WHERE created_date > :date", nativeQuery = true)
    List<User> findUsersCreatedAfter(@Param("date") LocalDate date);
}

// Реализация сервиса использует адаптер
@Service
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserRepository userRepository; // Это адаптер к БД
    
    @Override
    public List<User> findActiveUsers() {
        return userRepository.findByActiveTrue();
    }
    
    @Override
    public User findByEmail(String email) {
        return userRepository.findActiveUserByEmail(email).orElse(null);
    }
}

Message Converters как Adapter

Spring MVC использует адаптеры для конвертации данных между различными форматами:

@RestController
public class UserController {
    
    // Spring автоматически использует адаптеры для конвертации
    @PostMapping("/users")
    public ResponseEntity<User> createUser(@RequestBody CreateUserRequest request) {
        // MappingJackson2HttpMessageConverter адаптирует JSON в объект
        User user = userService.createUser(request);
        
        // Тот же конвертер адаптирует объект обратно в JSON
        return ResponseEntity.ok(user);
    }
    
    @GetMapping("/users/export")
    public ResponseEntity<byte[]> exportUsers() {
        List<User> users = userService.getAllUsers();
        
        // Кастомный адаптер для конвертации в CSV
        byte[] csvData = csvConverter.convertToCSV(users);
        
        return ResponseEntity.ok()
            .header("Content-Type", "text/csv")
            .header("Content-Disposition", "attachment; filename=users.csv")
            .body(csvData);
    }
}

// Кастомный адаптер для CSV
@Component
public class CsvMessageConverter implements HttpMessageConverter<Object> {
    
    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return MediaType.parseMediaType("text/csv").isCompatibleWith(mediaType);
    }
    
    @Override
    public void write(Object object, MediaType contentType, HttpOutputMessage outputMessage) 
            throws IOException {
        // Адаптируем объект в CSV формат
        String csv = convertObjectToCsv(object);
        outputMessage.getBody().write(csv.getBytes());
    }
}

Property Sources как Adapter

Spring Boot использует адаптеры для работы с различными источниками конфигурации:

@Configuration
@PropertySource("classpath:custom.properties")
public class DatabaseConfig {
    
    // Environment адаптирует различные источники конфигурации
    @Autowired
    private Environment env;
    
    @Bean
    public DataSource dataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        
        // Адаптер ищет свойства в различных источниках:
        // 1. System properties
        // 2. Environment variables  
        // 3. application.yml/properties
        // 4. @PropertySource файлы
        dataSource.setJdbcUrl(env.getProperty("database.url"));
        dataSource.setUsername(env.getProperty("database.username"));
        dataSource.setPassword(env.getProperty("database.password"));
        
        return dataSource;
    }
}

// Альтернативный подход с @ConfigurationProperties
@ConfigurationProperties(prefix = "database")
@Data
public class DatabaseProperties {
    private String url;
    private String username;
    private String password;
    private int maxPoolSize = 10;
}

Integration Adapters

Spring Integration предоставляет множество адаптеров для интеграции с внешними системами:

@Configuration
@EnableIntegration
public class IntegrationConfig {
    
    // Адаптер для работы с файлами
    @Bean
    public MessageSource<File> fileAdapter() {
        FileReadingMessageSource source = new FileReadingMessageSource();
        source.setDirectory(new File("/input"));
        source.setFilter(new SimplePatternFileListFilter("*.csv"));
        return source;
    }
    
    // Адаптер для отправки email
    @Bean
    public MessageHandler emailAdapter() {
        MailSendingMessageHandler handler = new MailSendingMessageHandler(mailSender);
        handler.setRequiresReply(false);
        return handler;
    }
    
    // Адаптер для работы с JMS
    @Bean
    public MessageHandler jmsAdapter() {
        JmsSendingMessageHandler handler = new JmsSendingMessageHandler(jmsTemplate);
        handler.setDestinationName("orders.queue");
        return handler;
    }
}

4. Facade в Spring

Spring Boot Auto-Configuration как Facade

Spring Boot Auto-Configuration - это мощный фасад, который скрывает сложность конфигурирования многих компонентов:

// Пользователь видит только простой код
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// За кулисами Spring Boot Auto-Configuration работает как фасад
@Configuration
@ConditionalOnClass(DataSource.class)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public DataSource dataSource(DataSourceProperties properties) {
        // Фасад скрывает сложность создания DataSource
        return DataSourceBuilder.create()
            .driverClassName(properties.getDriverClassName())
            .url(properties.getUrl())
            .username(properties.getUsername())
            .password(properties.getPassword())
            .build();
    }
    
    @Bean
    @ConditionalOnBean(DataSource.class)
    @ConditionalOnMissingBean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    
    @Bean
    @ConditionalOnProperty(name = "spring.jpa.enabled", havingValue = "true", matchIfMissing = true)
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
        // Сложная конфигурация JPA скрыта за фасадом
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setDataSource(dataSource);
        factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        return factory;
    }
}

Service Layer как Facade

Service слой в Spring приложениях часто работает как фасад к сложной бизнес-логике:

@Service
@Transactional
public class OrderProcessingFacade {
    
    // Фасад координирует работу множества компонентов
    private final OrderRepository orderRepository;
    private final InventoryService inventoryService;
    private final PaymentService paymentService;
    private final ShippingService shippingService;
    private final NotificationService notificationService;
    private final AuditService auditService;
    
    // Простой метод скрывает сложную бизнес-логику
    public OrderResult processOrder(OrderRequest request) {
        try {
            // 1. Валидация заказа
            validateOrder(request);
            
            // 2. Проверка наличия товара
            if (!inventoryService.reserveItems(request.getItems())) {
                return OrderResult.failed("Items not available");
            }
            
            // 3. Обработка платежа
            PaymentResult payment = paymentService.processPayment(request.getPayment());
            if (!payment.isSuccessful()) {
                inventoryService.releaseItems(request.getItems());
                return OrderResult.failed("Payment failed");
            }
            
            // 4. Создание заказа
            Order order = orderRepository.save(new Order(request));
            
            // 5. Планирование доставки
            ShippingInfo shipping = shippingService.scheduleDelivery(order);
            
            // 6. Отправка уведомлений
            notificationService.sendOrderConfirmation(order);
            
            // 7. Аудит
            auditService.logOrderCreated(order);
            
            return OrderResult.success(order.getId(), shipping.getTrackingNumber());
            
        } catch (Exception e) {
            // Фасад обрабатывает все исключения
            auditService.logOrderError(request, e);
            return OrderResult.failed("Order processing failed");
        }
    }
    
    // Вспомогательные методы скрыты от клиента
    private void validateOrder(OrderRequest request) {
        if (request.getItems().isEmpty()) {
            throw new IllegalArgumentException("Order must contain items");
        }
        // Дополнительная валидация...
    }
}

Spring MVC как Facade

DispatcherServlet работает как фасад ко всей MVC архитектуре:

// Простой контроллер
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping("/{id}")
    public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        return ResponseEntity.ok(userMapper.toDto(user));
    }
}

// За кулисами DispatcherServlet координирует:
// 1. HandlerMapping - поиск подходящего контроллера
// 2. HandlerAdapter - адаптация вызова метода
// 3. ViewResolver - разрешение представлений
// 4. MessageConverter - конвертация данных
// 5. ExceptionHandler - обработка исключений

5. Composite в Spring

Spring Expression Language (SpEL) как Composite

SpEL использует Composite для построения сложных выражений из простых компонентов:

@Service
public class SecurityService {
    
    // Простые выражения можно комбинировать в сложные
    @PreAuthorize("hasRole('ADMIN') and #user.department == authentication.principal.department")
    public void updateUser(User user) {
        userRepository.save(user);
    }
    
    @PreAuthorize("hasRole('MANAGER') or (#user.createdBy == authentication.principal.id and hasRole('USER'))")
    public void deleteUser(User user) {
        userRepository.delete(user);
    }
    
    // Сложные выражения с методами
    @PreAuthorize("@securityService.canAccessDocument(#documentId, authentication.principal.id)")
    public Document getDocument(Long documentId) {
        return documentRepository.findById(documentId);
    }
    
    public boolean canAccessDocument(Long documentId, Long userId) {
        // Сложная логика проверки доступа
        Document doc = documentRepository.findById(documentId);
        User user = userRepository.findById(userId);
        
        return doc.isPublic() || 
               doc.getOwner().equals(user) || 
               user.hasRole("ADMIN") ||
               doc.getSharedUsers().contains(user);
    }
}

Validation как Composite

Bean Validation в Spring использует Composite для объединения валидаторов:

@Entity
public class User {
    
    @NotBlank(message = "Name is required")
    @Size(min = 2, max = 50, message = "Name must be between 2 and 50 characters")
    private String name;
    
    @NotBlank(message = "Email is required")
    @Email(message = "Email should be valid")
    private String email;
    
    @NotNull(message = "Age is required")
    @Min(value = 18, message = "Age should be at least 18")
    @Max(value = 120, message = "Age should be less than 120")
    private Integer age;
    
    // Композитная валидация
    @Valid
    @NotNull
    private Address address;
    
    // Кастомная композитная валидация
    @ValidUserProfile
    private String profileData;
}

// Кастомный композитный валидатор
@Constraint(validatedBy = UserProfileValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidUserProfile {
    String message() default "Invalid user profile";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class UserProfileValidator implements ConstraintValidator<ValidUserProfile, String> {
    
    // Композитный валидатор может включать множество проверок
    private List<ProfileRule> rules = Arrays.asList(
        new LengthRule(),
        new ContentRule(),
        new FormatRule()
    );
    
    @Override
    public boolean isValid(String profile, ConstraintValidatorContext context) {
        return rules.stream().allMatch(rule -> rule.validate(profile));
    }
}

Spring Boot Configuration Properties как Composite

@ConfigurationProperties может создавать композитные структуры конфигурации:

@ConfigurationProperties(prefix = "app")
@Data
public class ApplicationProperties {
    
    // Композитные свойства
    private Database database = new Database();
    private Security security = new Security();
    private Monitoring monitoring = new Monitoring();
    
    @Data
    public static class Database {
        private String url;
        private String username;
        private String password;
        private Pool pool = new Pool();
        
        @Data
        public static class Pool {
            private int minSize = 5;
            private int maxSize = 20;
            private Duration maxWait = Duration.ofSeconds(30);
        }
    }
    
    @Data
    public static class Security {
        private Jwt jwt = new Jwt();
        private OAuth2 oauth2 = new OAuth2();
        
        @Data
        public static class Jwt {
            private String secret;
            private Duration expiration = Duration.ofHours(24);
        }
        
        @Data
        public static class OAuth2 {
            private String clientId;
            private String clientSecret;
            private List<String> scopes = new ArrayList<>();
        }
    }
    
    @Data
    public static class Monitoring {
        private boolean enabled = true;
        private Metrics metrics = new Metrics();
        private Tracing tracing = new Tracing();
        
        @Data
        public static class Metrics {
            private boolean enabled = true;
            private String endpoint = "/metrics";
        }
        
        @Data
        public static class Tracing {
            private boolean enabled = false;
            private double samplingRate = 0.1;
        }
    }
}

// Использование композитной конфигурации
@Configuration
@EnableConfigurationProperties(ApplicationProperties.class)
public class AppConfig {
    
    @Bean
    public DataSource dataSource(ApplicationProperties props) {
        Database dbProps = props.getDatabase();
        
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl(dbProps.getUrl());
        dataSource.setUsername(dbProps.getUsername());
        dataSource.setPassword(dbProps.getPassword());
        dataSource.setMinimumIdle(dbProps.getPool().getMinSize());
        dataSource.setMaximumPoolSize(dbProps.getPool().getMaxSize());
        
        return dataSource;
    }
}

Интеграция структурных паттернов в Spring

Spring Cloud как комбинация паттернов

Spring Cloud объединяет множество структурных паттернов для создания микросервисной архитектуры:

// Service Discovery как Proxy + Adapter
@SpringBootApplication
@EnableEurekaClient
public class UserServiceApplication {
    
    @Bean
    @LoadBalanced // Proxy для load balancing
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
    @Bean
    public WebClient.Builder webClientBuilder() {
        return WebClient.builder();
    }
}

// Circuit Breaker как Proxy + Decorator
@Service
public class ExternalApiService {
    
    @Autowired
    @LoadBalanced
    private RestTemplate restTemplate;
    
    @CircuitBreaker(name = "external-api", fallbackMethod = "fallbackResponse")
    @TimeLimiter(name = "external-api")
    @Retry(name = "external-api")
    public CompletableFuture<String> callExternalApi() {
        // Resilience4j создает несколько прокси:
        // 1. Circuit Breaker Proxy
        // 2. Time Limiter Proxy  
        // 3. Retry Proxy
        return CompletableFuture.supplyAsync(() -> 
            restTemplate.getForObject("http://external-service/api/data", String.class)
        );
    }
    
    public CompletableFuture<String> fallbackResponse(Exception ex) {
        return CompletableFuture.completedFuture("Fallback response");
    }
}

// API Gateway как Facade + Proxy
@RestController
public class ApiGatewayController {
    
    @Autowired
    private UserServiceClient userService;
    
    @Autowired
    private OrderServiceClient orderService;
    
    // Фасад объединяет несколько микросервисов
    @GetMapping("/api/user-profile/{userId}")
    public UserProfile getUserProfile(@PathVariable Long userId) {
        User user = userService.getUser(userId);
        List<Order> orders = orderService.getUserOrders(userId);
        
        return UserProfile.builder()
            .user(user)
            .recentOrders(orders.stream().limit(5).collect(toList()))
            .build();
    }
}

Spring Data как Multi-Pattern Framework

Spring Data комбинирует несколько структурных паттернов:

// Repository как Proxy + Adapter
@Repository
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
    
    // Spring Data создает прокси, который адаптирует методы к SQL
    List<User> findByActiveTrue();
    
    @Query("SELECT u FROM User u WHERE u.department.name = :departmentName")
    List<User> findByDepartment(@Param("departmentName") String departmentName);
}

// Specification как Composite
public class UserSpecifications {
    
    public static Specification<User> hasName(String name) {
        return (root, query, cb) -> cb.equal(root.get("name"), name);
    }
    
    public static Specification<User> isActive() {
        return (root, query, cb) -> cb.isTrue(root.get("active"));
    }
    
    public static Specification<User> inDepartment(String department) {
        return (root, query, cb) -> cb.equal(root.join("department").get("name"), department);
    }
}

// Композиция спецификаций
@Service
public class UserSearchService {
    
    @Autowired
    private UserRepository userRepository;
    
    public List<User> searchUsers(UserSearchCriteria criteria) {
        Specification<User> spec = Specification.where(null);
        
        if (criteria.getName() != null) {
            spec = spec.and(UserSpecifications.hasName(criteria.getName()));
        }
        
        if (criteria.isActiveOnly()) {
            spec = spec.and(UserSpecifications.isActive());
        }
        
        if (criteria.getDepartment() != null) {
            spec = spec.and(UserSpecifications.inDepartment(criteria.getDepartment()));
        }
        
        return userRepository.findAll(spec);
    }
}

Bridge паттерн в Spring

Spring Profiles как Bridge

Spring Profiles реализуют Bridge паттерн, разделяя абстракцию (бизнес-логика) от реализации (конфигурация окружения):

// Абстракция - интерфейс сервиса
public interface EmailService {
    void sendEmail(String to, String subject, String body);
}

// Реализация для разработки
@Service
@Profile("development")
public class MockEmailService implements EmailService {
    
    @Override
    public void sendEmail(String to, String subject, String body) {
        System.out.println("MOCK EMAIL - To: " + to + ", Subject: " + subject);
    }
}

// Реализация для продакшена
@Service
@Profile("production")
public class SmtpEmailService implements EmailService {
    
    @Value("${smtp.host}")
    private String smtpHost;
    
    @Value("${smtp.port}")
    private int smtpPort;
    
    @Override
    public void sendEmail(String to, String subject, String body) {
        // Реальная отправка через SMTP
        System.out.println("Sending real email via " + smtpHost + ":" + smtpPort);
    }
}

// Бизнес-логика не зависит от конкретной реализации
@Service
public class UserRegistrationService {
    
    private final EmailService emailService; // Абстракция
    
    public UserRegistrationService(EmailService emailService) {
        this.emailService = emailService;
    }
    
    public void registerUser(User user) {
        userRepository.save(user);
        
        // Используем абстракцию, не зная о конкретной реализации
        emailService.sendEmail(
            user.getEmail(),
            "Welcome!",
            "Thank you for registering"
        );
    }
}

Database Abstraction как Bridge

Spring Data JPA использует Bridge для разделения бизнес-логики от конкретной СУБД:

// Абстракция - бизнес-логика
@Service
public class ReportService {
    
    private final ReportRepository reportRepository;
    
    public List<SalesReport> generateSalesReport(LocalDate from, LocalDate to) {
        // Бизнес-логика не зависит от типа БД
        return reportRepository.findSalesBetweenDates(from, to);
    }
}

// Мост - Spring Data JPA
@Repository
public interface ReportRepository extends JpaRepository<SalesReport, Long> {
    
    @Query("SELECT new com.example.SalesReport(DATE(s.createdAt), SUM(s.amount)) " +
           "FROM Sale s WHERE s.createdAt BETWEEN :from AND :to GROUP BY DATE(s.createdAt)")
    List<SalesReport> findSalesBetweenDates(@Param("from") LocalDate from, @Param("to") LocalDate to);
}

# Реализация может меняться через конфигурацию
# application-mysql.yml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/myapp

# application-postgresql.yml  
spring:
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/myapp

Практические советы для собеседования

Ключевые вопросы и ответы

1. "Как Spring AOP реализует Proxy паттерн?"

  • JDK Dynamic Proxy для интерфейсов
  • CGLIB для классов
  • Interceptors для добавления cross-cutting concerns
  • @EnableAspectJAutoProxy для активации

2. "Чем отличается Spring Security Filter от обычного Decorator?"

  • Chain of Responsibility + Decorator
  • Каждый фильтр может прервать цепочку
  • Порядок выполнения критически важен
  • SecurityContext передается по цепочке

3. "Как Spring Boot Auto-Configuration работает как Facade?"

  • Скрывает сложность конфигурирования
  • @ConditionalOn* аннотации для условного создания бинов
  • spring.factories для автоматической загрузки
  • Starter modules как готовые фасады

Архитектурные паттерны в Spring

Layered Architecture:

@Controller    // Presentation Layer
@Service       // Business Layer  
@Repository    // Data Access Layer
@Configuration // Infrastructure Layer

Hexagonal Architecture с Spring:

// Порты (интерфейсы)
public interface UserRepository {
    User save(User user);
}

// Адаптеры (реализации)
@Repository
public class JpaUserRepository implements UserRepository {
    // Реализация через JPA
}

@Repository  
@Profile("test")
public class InMemoryUserRepository implements UserRepository {
    // Реализация в памяти для тестов
}

Performance и Monitoring

Spring Boot Actuator endpoints:

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,beans,configprops
  endpoint:
    health:
      show-details: always

Полезные endpoints для понимания паттернов:

  • /actuator/beans - показывает все созданные прокси
  • /actuator/configprops - композитные конфигурации
  • /actuator/mappings - фасады контроллеров
  • /actuator/metrics - декораторы для мониторинга

Лучшие практики

1. Избегайте self-invocation в AOP:

@Service
public class UserService {
    
    @Transactional
    public void updateUser(User user) {
        userRepository.save(user);
        
        // Это НЕ СРАБОТАЕТ - прокси не перехватит self-call
        this.sendNotification(user);
    }
    
    @Async
    public void sendNotification(User user) {
        emailService.send(user.getEmail(), "Updated");
    }
}

// Правильное решение
@Service  
public class UserService {
    
    @Autowired
    private UserService self; // Внедряем прокси
    
    @Transactional
    public void updateUser(User user) {
        userRepository.save(user);
        self.sendNotification(user); // Используем прокси
    }
}

2. Правильная конфигурация кэширования:

@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager manager = new CaffeineCacheManager();
        manager.setCaffeine(Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(Duration.ofMinutes(10))
            .recordStats() // Для мониторинга
        );
        return manager;
    }
}

3. Эффективное использование Profiles:

@Configuration
public class DatabaseConfig {
    
    @Bean
    @Profile("!test")
    public DataSource productionDataSource() {
        return new HikariDataSource();
    }
    
    @Bean
    @Profile("test")
    public DataSource testDataSource() {
        return new H2DataSource();
    }
}

Структурные паттерны в Spring Framework образуют основу гибкой, расширяемой архитектуры. Понимание их работы критически важно для Senior разработчика, так как они влияют на производительность, тестируемость и поддерживаемость приложения.

Поведенческие паттерны

Что такое поведенческие паттерны?

Поведенческие паттерны — это паттерны проектирования, которые определяют алгоритмы взаимодействия между объектами и распределение обязанностей между ними. Они помогают организовать коммуникацию между классами и объектами, делая систему более гибкой и расширяемой.


1. Observer (Наблюдатель)

Описание

Определяет зависимость "один ко многим" между объектами. При изменении состояния одного объекта все зависящие от него объекты уведомляются и обновляются автоматически.

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

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

Пример (Java)

// Интерфейс наблюдателя
interface Observer {
    void update(String message);
}

// Конкретный наблюдатель
class User implements Observer {
    private String name;
    
    public User(String name) { this.name = name; }
    
    @Override
    public void update(String message) {
        System.out.println(name + " получил: " + message);
    }
}

// Издатель (Subject)
class NewsAgency {
    private List<Observer> observers = new ArrayList<>();
    private String news;
    
    public void subscribe(Observer observer) {
        observers.add(observer);
    }
    
    public void unsubscribe(Observer observer) {
        observers.remove(observer);
    }
    
    public void setNews(String news) {
        this.news = news;
        notifyObservers();
    }
    
    private void notifyObservers() {
        observers.forEach(observer -> observer.update(news));
    }
}

Применение в Java

  • java.util.Observer (deprecated в Java 9)
  • Event Listeners в GUI
  • Spring Events (@EventListener)
  • Reactive Streams (RxJava, Project Reactor)

2. Strategy (Стратегия)

Описание

Определяет семейство алгоритмов, инкапсулирует каждый и делает их взаимозаменяемыми. Стратегия позволяет изменять алгоритм независимо от клиентов, которые его используют.

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

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

Пример (Java)

// Интерфейс стратегии
interface PaymentStrategy {
    void pay(int amount);
}

// Конкретные стратегии
class CreditCardPayment implements PaymentStrategy {
    private String cardNumber;
    
    public CreditCardPayment(String cardNumber) {
        this.cardNumber = cardNumber;
    }
    
    @Override
    public void pay(int amount) {
        System.out.println("Оплата " + amount + " картой " + cardNumber);
    }
}

class PayPalPayment implements PaymentStrategy {
    private String email;
    
    public PayPalPayment(String email) {
        this.email = email;
    }
    
    @Override
    public void pay(int amount) {
        System.out.println("Оплата " + amount + " через PayPal " + email);
    }
}

// Контекст
class ShoppingCart {
    private PaymentStrategy paymentStrategy;
    
    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }
    
    public void checkout(int amount) {
        paymentStrategy.pay(amount);
    }
}

Применение в Java

  • Comparator в Collections
  • ThreadPoolExecutor с различными политиками
  • Spring Security (AuthenticationProvider)

3. Command (Команда)

Описание

Инкапсулирует запрос как объект, позволяя параметризовать клиентов с различными запросами, ставить запросы в очередь и поддерживать отмену операций.

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

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

Пример (Java)

// Интерфейс команды
interface Command {
    void execute();
    void undo();
}

// Получатель команды
class Light {
    private boolean isOn = false;
    
    public void turnOn() {
        isOn = true;
        System.out.println("Свет включен");
    }
    
    public void turnOff() {
        isOn = false;
        System.out.println("Свет выключен");
    }
}

// Конкретная команда
class LightOnCommand implements Command {
    private Light light;
    
    public LightOnCommand(Light light) {
        this.light = light;
    }
    
    @Override
    public void execute() {
        light.turnOn();
    }
    
    @Override
    public void undo() {
        light.turnOff();
    }
}

// Инициатор
class RemoteControl {
    private Command command;
    
    public void setCommand(Command command) {
        this.command = command;
    }
    
    public void pressButton() {
        command.execute();
    }
    
    public void pressUndo() {
        command.undo();
    }
}

Применение в Java

  • Runnable и Callable интерфейсы
  • ActionListener в Swing
  • TransactionTemplate в Spring
  • Queue для асинхронной обработки

4. Template Method (Шаблонный метод)

Описание

Определяет скелет алгоритма в базовом классе, позволяя подклассам переопределять отдельные шаги алгоритма, не изменяя его структуру.

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

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

Пример (Java)

// Абстрактный класс с шаблонным методом
abstract class DataProcessor {
    
    // Шаблонный метод
    public final void process() {
        readData();
        processData();
        writeData();
    }
    
    // Конкретный метод
    private void readData() {
        System.out.println("Чтение данных...");
    }
    
    // Абстрактный метод - должен быть реализован в подклассах
    protected abstract void processData();
    
    // Хук-метод - может быть переопределен
    protected void writeData() {
        System.out.println("Запись данных...");
    }
}

// Конкретная реализация
class CSVProcessor extends DataProcessor {
    @Override
    protected void processData() {
        System.out.println("Обработка CSV данных");
    }
}

class XMLProcessor extends DataProcessor {
    @Override
    protected void processData() {
        System.out.println("Обработка XML данных");
    }
    
    @Override
    protected void writeData() {
        System.out.println("Запись XML данных в специальном формате");
    }
}

Применение в Java

  • AbstractList, AbstractSet в Collections
  • HttpServlet (service(), doGet(), doPost())
  • Spring шаблоны (JdbcTemplate, RestTemplate)

5. State (Состояние)

Описание

Позволяет объекту изменять свое поведение при изменении внутреннего состояния. Создается впечатление, что объект изменил свой класс.

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

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

Пример (Java)

// Интерфейс состояния
interface State {
    void handle(Context context);
}

// Конкретные состояния
class StartState implements State {
    @Override
    public void handle(Context context) {
        System.out.println("Игрок в состоянии START");
        context.setState(new PlayingState());
    }
}

class PlayingState implements State {
    @Override
    public void handle(Context context) {
        System.out.println("Игрок играет");
        context.setState(new EndState());
    }
}

class EndState implements State {
    @Override
    public void handle(Context context) {
        System.out.println("Игра окончена");
        context.setState(new StartState());
    }
}

// Контекст
class Context {
    private State state;
    
    public Context() {
        state = new StartState();
    }
    
    public void setState(State state) {
        this.state = state;
    }
    
    public void request() {
        state.handle(this);
    }
}

Применение в Java

  • TCP Connection states
  • Thread states в Java
  • Spring State Machine
  • Workflow engines

6. Chain of Responsibility (Цепочка обязанностей)

Описание

Позволяет передавать запросы последовательно по цепочке обработчиков. Каждый обработчик решает, может ли он обработать запрос и стоит ли передавать его дальше.

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

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

Пример (Java)

// Абстрактный обработчик
abstract class Handler {
    protected Handler nextHandler;
    
    public void setNext(Handler handler) {
        this.nextHandler = handler;
    }
    
    public abstract void handleRequest(Request request);
}

// Запрос
class Request {
    private String type;
    private String content;
    
    public Request(String type, String content) {
        this.type = type;
        this.content = content;
    }
    
    public String getType() { return type; }
    public String getContent() { return content; }
}

// Конкретные обработчики
class AuthHandler extends Handler {
    @Override
    public void handleRequest(Request request) {
        if (request.getType().equals("AUTH")) {
            System.out.println("Обработка аутентификации: " + request.getContent());
        } else if (nextHandler != null) {
            nextHandler.handleRequest(request);
        }
    }
}

class ValidationHandler extends Handler {
    @Override
    public void handleRequest(Request request) {
        if (request.getType().equals("VALIDATION")) {
            System.out.println("Валидация: " + request.getContent());
        } else if (nextHandler != null) {
            nextHandler.handleRequest(request);
        }
    }
}

Применение в Java

  • Servlet Filters в web-приложениях
  • Exception handling в некоторых фреймворках
  • Logging frameworks (Logback, Log4j)
  • Spring Security filter chain

7. Mediator (Посредник)

Описание

Определяет объект, который инкапсулирует взаимодействие множества объектов. Посредник обеспечивает слабую связанность, избавляя объекты от необходимости ссылаться друг на друга.

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

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

Пример (Java)

// Интерфейс посредника
interface ChatMediator {
    void sendMessage(String message, User user);
    void addUser(User user);
}

// Конкретный посредник
class ChatRoom implements ChatMediator {
    private List<User> users = new ArrayList<>();
    
    @Override
    public void addUser(User user) {
        users.add(user);
    }
    
    @Override
    public void sendMessage(String message, User user) {
        users.stream()
            .filter(u -> u != user)
            .forEach(u -> u.receive(message));
    }
}

// Абстрактный коллега
abstract class User {
    protected ChatMediator mediator;
    protected String name;
    
    public User(ChatMediator mediator, String name) {
        this.mediator = mediator;
        this.name = name;
    }
    
    public abstract void send(String message);
    public abstract void receive(String message);
}

// Конкретный коллега
class ChatUser extends User {
    public ChatUser(ChatMediator mediator, String name) {
        super(mediator, name);
    }
    
    @Override
    public void send(String message) {
        System.out.println(name + " отправляет: " + message);
        mediator.sendMessage(message, this);
    }
    
    @Override
    public void receive(String message) {
        System.out.println(name + " получает: " + message);
    }
}

Применение в Java

  • Spring ApplicationContext как посредник между beans
  • Message Brokers (RabbitMQ, Apache Kafka)
  • Event Bus patterns
  • MVC контроллеры

8. Visitor (Посетитель)

Описание

Позволяет определить новую операцию, не изменяя классы объектов, над которыми она оперирует. Разделяет алгоритм от структуры объектов.

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

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

Пример (Java)

// Интерфейс посетителя
interface Visitor {
    void visit(Book book);
    void visit(Movie movie);
}

// Интерфейс элемента
interface Element {
    void accept(Visitor visitor);
}

// Конкретные элементы
class Book implements Element {
    private String title;
    private double price;
    
    public Book(String title, double price) {
        this.title = title;
        this.price = price;
    }
    
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    
    public String getTitle() { return title; }
    public double getPrice() { return price; }
}

class Movie implements Element {
    private String title;
    private double price;
    
    public Movie(String title, double price) {
        this.title = title;
        this.price = price;
    }
    
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    
    public String getTitle() { return title; }
    public double getPrice() { return price; }
}

// Конкретный посетитель
class PriceCalculator implements Visitor {
    private double totalPrice = 0;
    
    @Override
    public void visit(Book book) {
        totalPrice += book.getPrice() * 0.9; // скидка на книги
    }
    
    @Override
    public void visit(Movie movie) {
        totalPrice += movie.getPrice() * 0.8; // скидка на фильмы
    }
    
    public double getTotalPrice() { return totalPrice; }
}

Применение в Java

  • Compiler design (AST traversal)
  • XML/JSON processing
  • File system operations
  • Static analysis tools

9. Iterator (Итератор)

Описание

Предоставляет способ последовательного доступа к элементам составного объекта, не раскрывая его внутреннего представления.

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

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

Пример (Java)

// Интерфейс итератора уже есть в Java Collections
import java.util.Iterator;

// Пользовательская коллекция
class BookCollection implements Iterable<Book> {
    private List<Book> books = new ArrayList<>();
    
    public void addBook(Book book) {
        books.add(book);
    }
    
    @Override
    public Iterator<Book> iterator() {
        return new BookIterator();
    }
    
    // Внутренний класс итератора
    private class BookIterator implements Iterator<Book> {
        private int currentIndex = 0;
        
        @Override
        public boolean hasNext() {
            return currentIndex < books.size();
        }
        
        @Override
        public Book next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return books.get(currentIndex++);
        }
    }
}

Применение в Java

  • Java Collections Framework (List, Set, Map)
  • Stream API
  • For-each loops
  • ResultSet в JDBC

10. Memento (Хранитель)

Описание

Позволяет сохранить и восстановить предыдущее состояние объекта, не нарушая принципы инкапсуляции.

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

  • Когда нужно сохранить состояние объекта для последующего восстановления
  • Когда прямое получение состояния нарушает инкапсуляцию
  • Когда нужна функция "отмены" (Undo)

Пример (Java)

// Memento - хранитель состояния
class TextMemento {
    private final String text;
    private final int cursorPosition;
    
    public TextMemento(String text, int cursorPosition) {
        this.text = text;
        this.cursorPosition = cursorPosition;
    }
    
    public String getText() { return text; }
    public int getCursorPosition() { return cursorPosition; }
}

// Originator - создатель состояния
class TextEditor {
    private String text;
    private int cursorPosition;
    
    public void setText(String text) {
        this.text = text;
    }
    
    public void setCursorPosition(int position) {
        this.cursorPosition = position;
    }
    
    public TextMemento createMemento() {
        return new TextMemento(text, cursorPosition);
    }
    
    public void restoreFromMemento(TextMemento memento) {
        this.text = memento.getText();
        this.cursorPosition = memento.getCursorPosition();
    }
    
    public void showState() {
        System.out.println("Текст: " + text + ", Позиция: " + cursorPosition);
    }
}

// Caretaker - опекун
class EditorHistory {
    private Stack<TextMemento> history = new Stack<>();
    
    public void save(TextEditor editor) {
        history.push(editor.createMemento());
    }
    
    public void undo(TextEditor editor) {
        if (!history.isEmpty()) {
            editor.restoreFromMemento(history.pop());
        }
    }
}

Применение в Java

  • Undo/Redo functionality
  • Database transactions
  • Game save states
  • Version control systems

Ключевые различия между паттернами

Паттерн Основная цель Когда использовать
Observer Уведомление о изменениях Один объект изменяется → много объектов реагируют
Strategy Взаимозаменяемые алгоритмы Множество способов выполнения одной задачи
Command Инкапсуляция запросов Нужна очередь, логирование или отмена операций
Template Method Скелет алгоритма Общий алгоритм с вариациями в шагах
State Изменение поведения Поведение зависит от состояния объекта
Chain of Responsibility Цепочка обработчиков Запрос может быть обработан разными способами
Mediator Слабая связанность Сложное взаимодействие между объектами
Visitor Новые операции Часто добавляются операции, структура стабильна
Iterator Последовательный доступ Нужен обход коллекции без раскрытия структуры
Memento Сохранение состояния Нужна функция отмены или снапшоты состояния

Советы для собеседования

  1. Знайте применение в Java: Изучите как паттерны используются в стандартной библиотеке и популярных фреймворках
  2. Понимайте проблемы: Сможете объяснить, какую конкретную проблему решает каждый паттерн
  3. Приводите примеры: Готовьте реальные примеры из своего опыта
  4. Знайте недостатки: Понимайте, когда паттерн может усложнить код
  5. Различайте похожие паттерны: Особенно State vs Strategy, Observer vs Mediator

Помните: Паттерны — это инструменты, а не цель. Используйте их для решения конкретных проблем, а не для демонстрации знаний.

Поведенческие паттерны в Spring Framework

Введение в поведенческие паттерны Spring

Spring Framework активно использует поведенческие паттерны для обеспечения слабой связанности, расширяемости и гибкости архитектуры. Понимание этих паттернов критически важно для Senior Java Backend разработчика, так как они лежат в основе многих Spring механизмов.


1. Observer Pattern в Spring Events

Что это такое?

Spring Events — это реализация паттерна Observer, которая позволяет компонентам приложения публиковать события и подписываться на них без прямых зависимостей.

Ключевые компоненты:

  • ApplicationEvent — базовый класс для всех событий
  • ApplicationEventPublisher — интерфейс для публикации событий
  • @EventListener — аннотация для обработчиков событий
  • ApplicationContext — выступает как посредник между издателями и подписчиками

Как работает:

// Пользовательское событие
public class UserRegisteredEvent extends ApplicationEvent {
    private final String email;
    
    public UserRegisteredEvent(Object source, String email) {
        super(source);
        this.email = email;
    }
    
    public String getEmail() { return email; }
}

// Издатель события
@Service
public class UserService {
    @Autowired
    private ApplicationEventPublisher publisher;
    
    public void registerUser(String email) {
        // Бизнес-логика регистрации
        System.out.println("Пользователь зарегистрирован: " + email);
        
        // Публикация события
        publisher.publishEvent(new UserRegisteredEvent(this, email));
    }
}

// Подписчики на событие
@Component
public class EmailNotificationService {
    @EventListener
    public void handleUserRegistered(UserRegisteredEvent event) {
        System.out.println("Отправка welcome email: " + event.getEmail());
    }
}

@Component
public class AnalyticsService {
    @EventListener
    @Async // Асинхронная обработка
    public void handleUserRegistered(UserRegisteredEvent event) {
        System.out.println("Отправка данных в аналитику: " + event.getEmail());
    }
}

Преимущества:

  • Слабая связанность — сервисы не знают друг о друге
  • Расширяемость — легко добавлять новые обработчики
  • Асинхронность — поддержка @Async для неблокирующей обработки
  • Условная обработка — можно добавлять условия через SpEL

Применение в реальных проектах:

  • Отправка уведомлений после бизнес-операций
  • Логирование и аудит
  • Кэширование и инвалидация
  • Интеграция с внешними системами

2. Template Method в Spring Templates

Что это такое?

Spring Templates — это реализация паттерна Template Method, которая предоставляет готовый скелет для выполнения типичных операций с различными технологиями.

Основные Template классы:

  • JdbcTemplate — для работы с базами данных
  • RestTemplate — для HTTP-вызовов
  • JmsTemplate — для работы с JMS
  • RedisTemplate — для работы с Redis

JdbcTemplate — детальный разбор:

@Repository
public class UserRepository {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    // Template Method скрывает:
    // 1. Получение Connection
    // 2. Создание PreparedStatement
    // 3. Обработку исключений
    // 4. Закрытие ресурсов
    
    public List<User> findAllUsers() {
        String sql = "SELECT id, name, email FROM users";
        
        return jdbcTemplate.query(sql, (rs, rowNum) -> {
            User user = new User();
            user.setId(rs.getLong("id"));
            user.setName(rs.getString("name"));
            user.setEmail(rs.getString("email"));
            return user;
        });
    }
    
    public void saveUser(User user) {
        String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
        jdbcTemplate.update(sql, user.getName(), user.getEmail());
    }
}

RestTemplate — пример использования:

@Service
public class ExternalApiService {
    @Autowired
    private RestTemplate restTemplate;
    
    public UserDto getUserFromApi(Long userId) {
        String url = "https://api.example.com/users/" + userId;
        
        // Template Method обрабатывает:
        // - Создание HTTP-запроса
        // - Сериализацию/десериализацию
        // - Обработку ошибок
        // - Закрытие соединений
        
        return restTemplate.getForObject(url, UserDto.class);
    }
}

Преимущества Template Method в Spring:

  • Убирает boilerplate код — нет необходимости в ручном управлении ресурсами
  • Единообразие — один и тот же API для разных технологий
  • Обработка исключений — автоматическая трансляция технологических исключений в Spring исключения
  • Конфигурируемость — можно настроить поведение через параметры

3. Strategy Pattern в Spring Security

Что это такое?

Spring Security использует паттерн Strategy для предоставления различных способов аутентификации и авторизации.

Ключевые Strategy интерфейсы:

  • AuthenticationProvider — стратегии аутентификации
  • UserDetailsService — стратегии загрузки пользователей
  • PasswordEncoder — стратегии кодирования паролей
  • AccessDecisionManager — стратегии принятия решений о доступе

Пример с AuthenticationProvider:

// Стратегия для аутентификации через базу данных
@Component
public class DatabaseAuthenticationProvider implements AuthenticationProvider {
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Override
    public Authentication authenticate(Authentication authentication) 
            throws AuthenticationException {
        
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();
        
        UserDetails user = userDetailsService.loadUserByUsername(username);
        
        if (passwordEncoder.matches(password, user.getPassword())) {
            return new UsernamePasswordAuthenticationToken(
                username, password, user.getAuthorities());
        }
        
        throw new BadCredentialsException("Invalid credentials");
    }
    
    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

// Стратегия для аутентификации через JWT
@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
    
    @Autowired
    private JwtTokenValidator jwtTokenValidator;
    
    @Override
    public Authentication authenticate(Authentication authentication) 
            throws AuthenticationException {
        
        String token = (String) authentication.getCredentials();
        
        if (jwtTokenValidator.isValid(token)) {
            String username = jwtTokenValidator.getUsername(token);
            List<GrantedAuthority> authorities = jwtTokenValidator.getAuthorities(token);
            
            return new JwtAuthenticationToken(username, token, authorities);
        }
        
        throw new BadCredentialsException("Invalid JWT token");
    }
    
    @Override
    public boolean supports(Class<?> authentication) {
        return JwtAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

Конфигурация стратегий:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public AuthenticationManager authenticationManager(
            List<AuthenticationProvider> providers) {
        return new ProviderManager(providers);
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(); // Стратегия кодирования
    }
}

Преимущества Strategy в Spring Security:

  • Гибкость — можно комбинировать различные способы аутентификации
  • Расширяемость — легко добавлять новые стратегии
  • Переиспользование — стратегии можно использовать в разных контекстах
  • Тестируемость — каждую стратегию можно тестировать отдельно

4. Chain of Responsibility в Spring Security Filters

Что это такое?

Spring Security Filter Chain — это реализация паттерна Chain of Responsibility для обработки HTTP-запросов через цепочку фильтров безопасности.

Основные фильтры в цепочке:

  • SecurityContextPersistenceFilter — управление SecurityContext
  • UsernamePasswordAuthenticationFilter — аутентификация по логину/паролю
  • BasicAuthenticationFilter — HTTP Basic аутентификация
  • BearerTokenAuthenticationFilter — OAuth2/JWT аутентификация
  • AuthorizationFilter — авторизация доступа к ресурсам

Пример пользовательского фильтра:

@Component
public class CustomAuthenticationFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  FilterChain filterChain) 
            throws ServletException, IOException {
        
        String apiKey = request.getHeader("X-API-Key");
        
        if (apiKey != null && isValidApiKey(apiKey)) {
            // Создаем аутентификацию
            ApiKeyAuthenticationToken authentication = 
                new ApiKeyAuthenticationToken(apiKey);
            
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        
        // Передаем управление следующему фильтру в цепочке
        filterChain.doFilter(request, response);
    }
    
    private boolean isValidApiKey(String apiKey) {
        // Логика проверки API ключа
        return "valid-api-key".equals(apiKey);
    }
}

Конфигурация цепочки фильтров:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .addFilterBefore(new CustomAuthenticationFilter(), 
                           UsernamePasswordAuthenticationFilter.class)
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
        
        return http.build();
    }
}

Преимущества Chain of Responsibility:

  • Модульность — каждый фильтр отвечает за свою задачу
  • Гибкость — можно изменять порядок фильтров
  • Расширяемость — легко добавлять новые фильтры
  • Переиспользование — фильтры можно использовать в разных цепочках

5. Command Pattern в Spring MVC

Что это такое?

Spring MVC использует паттерн Command для обработки HTTP-запросов через контроллеры. Каждый метод контроллера представляет собой команду.

Ключевые компоненты:

  • @Controller/@RestController — обработчики команд
  • @RequestMapping — маппинг команд на URL
  • HandlerMapping — определение команды для запроса
  • HandlerAdapter — выполнение команды

Пример контроллера как Command:

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    // Команда для создания пользователя
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody CreateUserCommand command) {
        User user = userService.createUser(command.getName(), command.getEmail());
        return ResponseEntity.ok(user);
    }
    
    // Команда для обновления пользователя
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, 
                                         @RequestBody UpdateUserCommand command) {
        User user = userService.updateUser(id, command.getName(), command.getEmail());
        return ResponseEntity.ok(user);
    }
    
    // Команда для удаления пользователя
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}

// Command объекты для инкапсуляции параметров
public class CreateUserCommand {
    private String name;
    private String email;
    // геттеры и сеттеры
}

public class UpdateUserCommand {
    private String name;
    private String email;
    // геттеры и сеттеры
}

Дополнительные элементы Command Pattern:

// Interceptor для логирования команд
@Component
public class CommandLoggingInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        if (handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod) handler;
            System.out.println("Выполняется команда: " + 
                method.getMethod().getName());
        }
        
        return true;
    }
}

// Глобальный обработчик ошибок команд
@ControllerAdvice
public class CommandExceptionHandler {
    
    @ExceptionHandler(ValidationException.class)
    public ResponseEntity<ErrorResponse> handleValidationException(
            ValidationException ex) {
        
        ErrorResponse error = new ErrorResponse("VALIDATION_ERROR", ex.getMessage());
        return ResponseEntity.badRequest().body(error);
    }
}

Преимущества Command Pattern в Spring MVC:

  • Инкапсуляция — каждая операция представлена отдельной командой
  • Логирование — можно легко логировать выполнение команд
  • Валидация — команды можно валидировать с помощью Bean Validation
  • Тестируемость — каждую команду можно тестировать отдельно

6. Mediator Pattern в Spring ApplicationContext

Что это такое?

Spring ApplicationContext выступает как посредник (Mediator) между различными компонентами приложения, обеспечивая их взаимодействие без прямых зависимостей.

Функции ApplicationContext как Mediator:

  • Dependency Injection — внедрение зависимостей между компонентами
  • Event Publishing — публикация и маршрутизация событий
  • Resource Management — управление ресурсами приложения
  • Lifecycle Management — управление жизненным циклом компонентов

Пример взаимодействия через ApplicationContext:

// Компоненты не знают друг о друге напрямую
@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService; // Внедрено через ApplicationContext
    
    @Autowired
    private ApplicationEventPublisher eventPublisher; // Посредник для событий
    
    public void processOrder(Order order) {
        // Обработка заказа
        paymentService.processPayment(order.getPayment());
        
        // Публикация события через посредника
        eventPublisher.publishEvent(new OrderProcessedEvent(order));
    }
}

@Service
public class PaymentService {
    public void processPayment(Payment payment) {
        // Обработка платежа
        System.out.println("Платеж обработан: " + payment.getAmount());
    }
}

// Компоненты реагируют на события через посредника
@Component
public class EmailService {
    @EventListener
    public void handleOrderProcessed(OrderProcessedEvent event) {
        System.out.println("Отправка email для заказа: " + event.getOrder().getId());
    }
}

@Component
public class InventoryService {
    @EventListener
    public void handleOrderProcessed(OrderProcessedEvent event) {
        System.out.println("Обновление инвентаря для заказа: " + event.getOrder().getId());
    }
}

Конфигурация медиатора:

@Configuration
@EnableAsync
@EnableScheduling
public class ApplicationConfig {
    
    // ApplicationContext автоматически выступает как медиатор
    // для внедрения зависимостей
    
    @Bean
    public AsyncTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-");
        executor.initialize();
        return executor;
    }
}

Преимущества Mediator Pattern:

  • Слабая связанность — компоненты не зависят друг от друга напрямую
  • Централизованное управление — все взаимодействия проходят через ApplicationContext
  • Гибкость — легко изменять взаимодействия между компонентами
  • Тестируемость — можно легко создавать моки для тестирования

7. Visitor Pattern в Spring Expression Language (SpEL)

Что это такое?

Spring Expression Language использует паттерн Visitor для обхода и вычисления выражений в AST (Abstract Syntax Tree).

Применение SpEL в Spring:

  • @Value — для внедрения значений из конфигурации
  • @Conditional — для условной конфигурации
  • Security expressions — для правил безопасности
  • Cache expressions — для ключей кэша

Примеры использования SpEL:

@Component
public class ConfigurationComponent {
    
    // Простое значение из properties
    @Value("${app.name}")
    private String appName;
    
    // Выражение с условием
    @Value("#{${app.debug:false} ? 'DEBUG' : 'PROD'}")
    private String mode;
    
    // Вызов метода через SpEL
    @Value("#{T(java.lang.Math).random() * 100}")
    private double randomValue;
    
    // Работа с другими бинами
    @Value("#{userService.getCurrentUserName()}")
    private String currentUser;
}

// Использование в условной конфигурации
@Configuration
@ConditionalOnExpression("#{environment.getProperty('feature.enabled') == 'true'}")
public class FeatureConfiguration {
    
    @Bean
    public FeatureService featureService() {
        return new FeatureServiceImpl();
    }
}

// Использование в кэшировании
@Service
public class UserService {
    
    @Cacheable(value = "users", key = "#userId", condition = "#userId > 0")
    public User getUserById(Long userId) {
        return userRepository.findById(userId);
    }
    
    @CacheEvict(value = "users", key = "#user.id")
    public void updateUser(User user) {
        userRepository.save(user);
    }
}

Применение в Spring Security:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityMethodConfig {
    
    @PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
    public void updateUser(Long userId, User user) {
        // Метод доступен только админам или владельцу аккаунта
    }
    
    @PostAuthorize("returnObject.owner == authentication.principal.username")
    public Document getDocument(Long documentId) {
        // Возвращает документ только если текущий пользователь - владелец
        return documentRepository.findById(documentId);
    }
}

Преимущества Visitor Pattern в SpEL:

  • Гибкость — можно создавать сложные выражения
  • Переиспользование — выражения можно использовать в разных контекстах
  • Безопасность — SpEL предоставляет контролируемую среду выполнения
  • Интеграция — тесная интеграция с Spring контекстом

8. State Pattern в Spring State Machine

Что это такое?

Spring State Machine — это отдельный проект Spring для реализации конечных автоматов с использованием паттерна State.

Основные компоненты:

  • State — состояния системы
  • Event — события, которые могут вызвать переходы
  • Transition — переходы между состояниями
  • Action — действия, выполняемые при переходах

Пример конечного автомата для заказа:

// Определение состояний
public enum OrderStates {
    CREATED,
    PAID,
    SHIPPED,
    DELIVERED,
    CANCELLED
}

// Определение событий
public enum OrderEvents {
    PAY,
    SHIP,
    DELIVER,
    CANCEL
}

// Конфигурация автомата
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStates, OrderEvents> {
    
    @Override
    public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) 
            throws Exception {
        states
            .withStates()
                .initial(OrderStates.CREATED)
                .states(EnumSet.allOf(OrderStates.class))
                .end(OrderStates.DELIVERED)
                .end(OrderStates.CANCELLED);
    }
    
    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) 
            throws Exception {
        transitions
            .withExternal()
                .source(OrderStates.CREATED).target(OrderStates.PAID)
                .event(OrderEvents.PAY)
                .action(paymentAction())
            .and()
            .withExternal()
                .source(OrderStates.PAID).target(OrderStates.SHIPPED)
                .event(OrderEvents.SHIP)
                .action(shippingAction())
            .and()
            .withExternal()
                .source(OrderStates.SHIPPED).target(OrderStates.DELIVERED)
                .event(OrderEvents.DELIVER)
                .action(deliveryAction())
            .and()
            .withExternal()
                .source(OrderStates.CREATED).target(OrderStates.CANCELLED)
                .event(OrderEvents.CANCEL)
                .guard(orderCancellationGuard());
    }
    
    @Bean
    public Action<OrderStates, OrderEvents> paymentAction() {
        return context -> {
            System.out.println("Обработка платежа...");
            // Логика обработки платежа
        };
    }
    
    @Bean
    public Action<OrderStates, OrderEvents> shippingAction() {
        return context -> {
            System.out.println("Отправка заказа...");
            // Логика отправки
        };
    }
    
    @Bean
    public Guard<OrderStates, OrderEvents> orderCancellationGuard() {
        return context -> {
            // Условие для отмены заказа
            return true; // Можно отменить
        };
    }
}

Использование State Machine:

@Service
public class OrderService {
    
    @Autowired
    private StateMachine<OrderStates, OrderEvents> stateMachine;
    
    public void processOrder(Order order) {
        // Запуск автомата
        stateMachine.start();
        
        // Отправка событий
        stateMachine.sendEvent(OrderEvents.PAY);
        stateMachine.sendEvent(OrderEvents.SHIP);
        stateMachine.sendEvent(OrderEvents.DELIVER);
        
        // Получение текущего состояния
        OrderStates currentState = stateMachine.getState().getId();
        System.out.println("Текущее состояние: " + currentState);
    }
}

Преимущества State Pattern в Spring State Machine:

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

Сводная таблица применения паттернов в Spring

Паттерн Реализация в Spring Основное назначение Пример использования
Observer Spring Events Слабая связанность компонентов Отправка уведомлений после бизнес-операций
Template Method JdbcTemplate, RestTemplate Убирание boilerplate кода Работа с БД, HTTP-вызовы
Strategy Spring Security Взаимозаменяемые алгоритмы Различные способы аутентификации
Chain of Responsibility Security Filter Chain Поэтапная обработка запросов Фильтры безопасности
Command Spring MVC Controllers Инкапсуляция операций Обработка HTTP-запросов
Mediator ApplicationContext Центральное управление Dependency Injection, Event Publishing
Visitor SpEL Обход и вычисление выражений @Value, @Conditional, Security expressions
State Spring State Machine Управление состояниями Workflow, бизнес-процессы

Ключевые вопросы для собеседования

1. Spring Events vs JMS/RabbitMQ

Вопрос: "Когда использовать Spring Events, а когда внешние message brokers?"

Ответ:

  • Spring Events — для внутренних событий в рамках одного приложения
  • Message Brokers — для межсервисного взаимодействия, гарантии доставки, персистентности

2. Проблемы с Spring Events

Вопрос: "Какие проблемы могут возникнуть с Spring Events?"

Ответ:

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

3. Различия между Template классами

Вопрос: "В чем разница между JdbcTemplate и JPA?"

Ответ:

  • JdbcTemplate — низкоуровневый, полный контроль над SQL
  • JPA — высокоуровневый, ORM, автоматическая генерация SQL
  • Производительность — JdbcTemplate быстрее для простых операций
  • Сложность — JPA лучше для сложных доменных моделей

4. Безопасность SpEL

Вопрос: "Какие проблемы безопасности есть у SpEL?"

Ответ:

  • Code injection — если позволить пользователю вводить SpEL выражения
  • Доступ к системным классам — можно ограничить через SecurityManager
  • Производительность — компиляция выражений может быть медленной

5. Альтернативы Spring State Machine

Вопрос: "Когда НЕ стоит использовать Spring State Machine?"

Ответ:

  • Простые состояния — достаточно enum и if-else
  • Распределенные системы — лучше использовать Saga Pattern
  • Высокие нагрузки — может быть overhead
  • Легаси системы — сложно интегрировать

Практические советы

1. Производительность

  • @Async для Spring Events — не забывайте про асинхронность
  • Кэширование SpEL выражений — компилируйте один раз
  • Пул потоков для Template классов — настройте правильно

2. Тестирование

  • @MockBean для изоляции компонентов
  • @TestEventListener для тестирования событий
  • @TestConfiguration для тестовых конфигураций

3. Мониторинг

  • Actuator для мониторинга Spring компонентов
  • Micrometer для метрик
  • Логирование событий и переходов состояний

Помните: Spring паттерны — это не просто теория, а практические инструменты для создания maintainable и scalable приложений.