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

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

4. Auto-registry в Spring через List 5. Context Object для передачи данных 6. Избегайте в Hot-path (>5 реализаций — порог HotSpot) 7. Template Method для частей алгоритма 8. Str...

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

🟢 Junior Level

Strategy — паттерн, который позволяет выбирать разные алгоритмы для одной и той же задачи.

Простая аналогия: Навигатор. Цель одна (добраться из A в B), но маршруты разные: на машине, пешком, на общественном транспорте.

Пример:

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

// Конкретные стратегии
class CreditCardStrategy implements PaymentStrategy {
    public void pay(BigDecimal amount) {
        System.out.println("Paid " + amount + " by credit card");
    }
}

class PayPalStrategy implements PaymentStrategy {
    public void pay(BigDecimal amount) {
        System.out.println("Paid " + amount + " via PayPal");
    }
}

// Использование
PaymentStrategy strategy = new CreditCardStrategy();
strategy.pay(new BigDecimal("100"));

// Легко поменять
strategy = new PayPalStrategy();
strategy.pay(new BigDecimal("200"));

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

  • Есть несколько способов сделать одно и то же
  • Нужно выбирать алгоритм на runtime
  • Много if-else или switch

🟡 Middle Level

Проблема: разрастающийся switch

// ❌ Плохо: добавление нового типа = изменение метода
public void processPayment(PaymentType type, BigDecimal amount) {
    switch (type) {
        case CREDIT_CARD:
            processCreditCard(amount);
            break;
        case PAYPAL:
            processPayPal(amount);
            break;
        case CRYPTO:
            processCrypto(amount);
            break;
        // Каждый новый тип → изменение метода!
    }
}

Решение: Strategy

// ✅ Хорошо: добавление нового типа = новый класс
interface PaymentStrategy {
    void pay(BigDecimal amount);
}

class CreditCardPayment implements PaymentStrategy {
    public void pay(BigDecimal amount) { /* логика */ }
}

class PayPalPayment implements PaymentStrategy {
    public void pay(BigDecimal amount) { /* логика */ }
}

// Выбор стратегии
Map<PaymentType, PaymentStrategy> strategies = Map.of(
    CREDIT_CARD, new CreditCardPayment(),
    PAYPAL, new PayPalPayment(),
    CRYPTO, new CryptoPayment()
);

strategies.get(type).pay(amount);

Modern Java: лямбды

// Для простых стратегий не нужны классы!
Map<PaymentType, Consumer<BigDecimal>> strategies = Map.of(
    CREDIT_CARD, amount -> processCreditCard(amount),
    PAYPAL, amount -> processPayPal(amount),
    CRYPTO, amount -> processCrypto(amount)
);

strategies.get(type).accept(amount);

Enum Strategy

// Для ограниченного набора стратегий
public enum ShippingStrategy {
    AIR {
        public BigDecimal calculate(BigDecimal weight) {
            return weight.multiply(BigDecimal.TEN);
        }
    },
    SEA {
        public BigDecimal calculate(BigDecimal weight) {
            return weight.multiply(BigDecimal.ONE);
        }
    };
    
    public abstract BigDecimal calculate(BigDecimal weight);
}

// Использование
BigDecimal cost = ShippingStrategy.AIR.calculate(weight);

Spring: Auto-registry Strategy

// Автоматическая регистрация всех стратегий
public interface DiscountStrategy {
    BigDecimal apply(BigDecimal amount);
    String getType();
}

@Component
public class PercentageDiscount implements DiscountStrategy {
    public String getType() { return "PERCENTAGE"; }
    public BigDecimal apply(BigDecimal amount) { return amount.multiply(new BigDecimal("0.9")); }
}

@Component
public class FixedDiscount implements DiscountStrategy {
    public String getType() { return "FIXED"; }
    public BigDecimal apply(BigDecimal amount) { return amount.subtract(new BigDecimal("100")); }
}

// Автоматический Map
@Service
public class DiscountService {
    private final Map<String, DiscountStrategy> strategies;
    
    public DiscountService(List<DiscountStrategy> allStrategies) {
        this.strategies = allStrategies.stream()
            .collect(Collectors.toMap(
                DiscountStrategy::getType,
                Function.identity()
            ));
    }
    
    public BigDecimal applyDiscount(String type, BigDecimal amount) {
        return strategies.get(type).apply(amount);
    }
}

// Добавить новую стратегию = создать новый @Component!

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

  1. Strategy для простого if-else
    // ❌ Overengineering
    interface CompareStrategy { int compare(int a, int b); }
    class AscendingCompare implements CompareStrategy { ... }
    class DescendingCompare implements CompareStrategy { ... }
       
    // ✅ Достаточно лямбды
    Comparator<Integer> asc = Integer::compare;
    Comparator<Integer> desc = (a, b) -> Integer.compare(b, a);
    
  2. Толстый интерфейс
    // ❌ Слишком много методов
    interface Strategy {
        void init();
        void process();
        void cleanup();
        String getName();
        int getPriority();
    }
       
    // ✅ Разделите на разные стратегии
    

🔴 Senior Level

Strategy vs State vs Template Method

Strategy vs State:

// Strategy: выбор извне
order.setPaymentStrategy(new CreditCardPayment());  // Клиент выбирает

// State: изменение изнутри
order.setState(PaidState.INSTANCE);  // Объект сам меняет состояние
order.process();  // Поведение зависит от состояния

Strategy vs Template Method:

// Template Method: наследование, части алгоритма
abstract class AbstractReport {
    public void generate() {
        loadData();      // Переопределяется
        formatData();    // Переопределяется
        saveReport();    // Общий код
    }
}

// Strategy: композиция, весь алгоритм
interface ReportGenerator { Report generate(); }
class PdfReportGenerator implements ReportGenerator { ... }
class HtmlReportGenerator implements ReportGenerator { ... }

JIT и полиморфизм

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

// Bimorphic call (2 реализации) → inline cache
// JIT всё ещё оптимизирует

// Megamorphic call (>5 реализаций — порог HotSpot) → vtable lookup
interface PaymentStrategy { void pay(); }
class A implements PaymentStrategy { ... }
class B implements PaymentStrategy { ... }
class C implements PaymentStrategy { ... }
class D implements PaymentStrategy { ... }  // >2!

// JIT не может инлайнить → косвенный вызов
// → ~5-10ns overhead на вызов
// → В Hot-path (1M вызовов/сек) = 5-10ms потери

Оптимизация для Hot-path:

// Вместо интерфейса → enum switch
public enum PaymentType {
    CREDIT_CARD, PAYPAL, CRYPTO
}

public void process(PaymentType type, BigDecimal amount) {
    switch (type) {
        case CREDIT_CARD -> processCreditCard(amount);
        case PAYPAL -> processPayPal(amount);
        case CRYPTO -> processCrypto(amount);
    }
}

// → JIT компилирует в tableswitch → O(1) → быстро!

Context Object Pattern

// Проблема: стратегия нуждается во многих данных
interface PricingStrategy {
    BigDecimal calculate(Product p, Customer c, Cart cart, 
                        Promotion promo, Locale locale);
}

// Решение: объект-контекст
public record PricingContext(
    Product product,
    Customer customer,
    Cart cart,
    Promotion promotion,
    Locale locale
) {}

interface PricingStrategy {
    BigDecimal calculate(PricingContext context);
}

// Добавление новых данных не ломает интерфейс!

Production Experience

Реальный сценарий #1: Megamorphic slowdown

  • 10 реализаций DiscountStrategy
  • Hot-path: 100,000 вызовов/сек
  • Проблема: virtual call overhead ~1-5ns (nanoseconds), не миллисекунды
  • Решение: enum switch вместо интерфейса
  • Результат: 5ms (ускорение в 10 раз)

Реальный сценарий #2: Spring auto-registry

  • 20 стратегий валидации
  • Ручная регистрация в Map → ошибки
  • Решение: авто-registry через List injection
  • Результат: новая стратегия = новый @Component

Best Practices

  1. Лямбды для простых стратегий
  2. Enum для ограниченного набора
  3. Классы для сложной логики
  4. Auto-registry в Spring через List
  5. Context Object для передачи данных
  6. Избегайте в Hot-path (>5 реализаций — порог HotSpot)
  7. Template Method для частей алгоритма
  8. Strategy для всей замены алгоритма

Резюме для Senior

  • Strategy = замена всего алгоритма через композицию
  • State = изменение поведения изнутри
  • Template Method = замена частей через наследование
  • JIT: megamorphic calls >5 реализаций (порог HotSpot) → overhead
  • Hot-path: enum switch > интерфейс
  • Context Object для передачи данных в стратегию
  • Spring auto-registry: List → Map
  • Modern Java: лямбды заменяют простые стратегии

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

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

  • Strategy заменяет разрастающийся switch/if-else через композицию — каждый алгоритм в отдельном классе
  • В Modern Java лямбды заменяют простые стратегии: Map<Enum, Function> вместо 10+ классов
  • Spring auto-registry: List injection → автоматический Map<String, Strategy>
  • JIT оптимизация: megamorphic calls (>5 реализаций — порог HotSpot) → virtual call overhead ~5-10ns
  • Для Hot-path: enum switch быстрее интерфейса (JIT компилирует в tableswitch → O(1))
  • Context Object Pattern: передача данных в стратегию через объект-контекст вместо 5+ параметров
  • Enum Strategy — для ограниченного набора алгоритмов с компактным кодом

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

  • Когда Strategy — overengineering? — Простой if-else с 2-3 ветками и несложной логикой
  • Чем Strategy отличается от State? — Strategy выбирается извне (клиент), State меняется изнутри (объект сам)
  • Что такое megamorphic call? — Вызов метода с >5 реализациями (порог HotSpot) → JIT не инлайнит → overhead
  • Как Spring автоматически регистрирует стратегии? — List injection → Stream.collect(toMap)

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

  • “Я использую Strategy для 2 веток if-else” — overengineering, лямбды достаточно
  • “Strategy не влияет на производительность” — megamorphic calls >5 реализаций measurable overhead
  • “Я не использую Strategy, пишу switch” — нарушение OCP, добавление нового типа = изменение кода
  • “Все стратегии должны быть в отдельных классах” — лямбды и enum заменяют простые стратегии

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

  • [[13. В чём разница между State и Strategy]] — сравнение паттернов
  • [[1. Что такое паттерны проектирования]] — лямбды заменяют Strategy
  • [[2. Какие категории паттернов существуют]] — Behavioral паттерны
  • [[16. Какие антипаттерны вы знаете]] — Golden Hammer
  • [[14. Какие типы Proxy существуют]] — альтернативный Structural подход