Question 10 · Section 2

When to Use Strategy?

4. Auto-registry in Spring via List 5. Context Object for data passing 6. Avoid in hot path (>5 implementations — HotSpot threshold) 7. Template Method for parts of an algorithm...

Language versions: English Russian Ukrainian

Junior Level

Strategy is a pattern that allows selecting different algorithms for the same task.

Simple analogy: GPS navigator. The goal is the same (get from A to B), but routes differ: by car, on foot, by public transport.

Example:

// Strategy interface
interface PaymentStrategy {
    void pay(BigDecimal amount);
}

// Concrete strategies
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");
    }
}

// Usage
PaymentStrategy strategy = new CreditCardStrategy();
strategy.pay(new BigDecimal("100"));

// Easy to swap
strategy = new PayPalStrategy();
strategy.pay(new BigDecimal("200"));

When to use:

  • Multiple ways to do the same thing
  • Need to choose algorithm at runtime
  • Lots of if-else or switch

Middle Level

Problem: Growing switch

// Bad: adding a new type = modifying the method
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;
        // Each new type -> method modification!
    }
}

Solution: Strategy

// Good: adding a new type = new class
interface PaymentStrategy {
    void pay(BigDecimal amount);
}

class CreditCardPayment implements PaymentStrategy {
    public void pay(BigDecimal amount) { /* logic */ }
}

class PayPalPayment implements PaymentStrategy {
    public void pay(BigDecimal amount) { /* logic */ }
}

// Strategy selection
Map<PaymentType, PaymentStrategy> strategies = Map.of(
    CREDIT_CARD, new CreditCardPayment(),
    PAYPAL, new PayPalPayment(),
    CRYPTO, new CryptoPayment()
);

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

Modern Java: Lambdas

// For simple strategies, no classes needed!
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

// For a limited set of strategies
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);
}

// Usage
BigDecimal cost = ShippingStrategy.AIR.calculate(weight);

Spring: Auto-registry Strategy

// Automatic registration of all strategies
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")); }
}

// Automatic 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);
    }
}

// Add a new strategy = create a new @Component!

Typical Mistakes

  1. Strategy for simple if-else
    // Overengineering
    interface CompareStrategy { int compare(int a, int b); }
    class AscendingCompare implements CompareStrategy { ... }
    class DescendingCompare implements CompareStrategy { ... }
    
    // Lambda is sufficient
    Comparator<Integer> asc = Integer::compare;
    Comparator<Integer> desc = (a, b) -> Integer.compare(b, a);
    
  2. Fat interface
    // Too many methods
    interface Strategy {
        void init();
        void process();
        void cleanup();
        String getName();
        int getPriority();
    }
    
    // Split into separate strategies
    

Senior Level

Strategy vs State vs Template Method

Strategy vs State:

// Strategy: chosen externally
order.setPaymentStrategy(new CreditCardPayment());  // Client chooses

// State: changed internally
order.setState(PaidState.INSTANCE);  // Object changes state itself
order.process();  // Behavior depends on state

Strategy vs Template Method:

// Template Method: inheritance, parts of algorithm
abstract class AbstractReport {
    public void generate() {
        loadData();      // Overridden
        formatData();    // Overridden
        saveReport();    // Common code
    }
}

// Strategy: composition, entire algorithm
interface ReportGenerator { Report generate(); }
class PdfReportGenerator implements ReportGenerator { ... }
class HtmlReportGenerator implements ReportGenerator { ... }

JIT and Polymorphism

// Monomorphic call (1 implementation) -> inlining
PaymentStrategy strategy = new CreditCardPayment();
strategy.pay(amount);  // JIT inlines code -> 0 overhead

// Bimorphic call (2 implementations) -> inline cache
// JIT still optimizes

// Megamorphic call (>5 implementations — HotSpot threshold) -> vtable lookup
interface PaymentStrategy { void pay(); }
class A implements PaymentStrategy { ... }
class B implements PaymentStrategy { ... }
class C implements PaymentStrategy { ... }
class D implements PaymentStrategy { ... }  // >2!

// JIT cannot inline -> indirect call
// -> ~5-10ns overhead per call
// -> In hot path (1M calls/sec) = 5-10ms loss

Hot-path optimization:

// Instead of interface -> 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 compiles to tableswitch -> O(1) -> fast!

Context Object Pattern

// Problem: strategy needs lots of data
interface PricingStrategy {
    BigDecimal calculate(Product p, Customer c, Cart cart,
                        Promotion promo, Locale locale);
}

// Solution: context object
public record PricingContext(
    Product product,
    Customer customer,
    Cart cart,
    Promotion promotion,
    Locale locale
) {}

interface PricingStrategy {
    BigDecimal calculate(PricingContext context);
}

// Adding new data doesn't break the interface!

Production Experience

Real scenario #1: Megamorphic slowdown

  • 10 DiscountStrategy implementations
  • Hot path: 100,000 calls/sec
  • Problem: virtual call overhead ~5-10ns (nanoseconds), not milliseconds
  • Solution: enum switch instead of interface
  • Result: 10x speedup

Real scenario #2: Spring auto-registry

  • 20 validation strategies
  • Manual Map registration -> errors
  • Solution: auto-registry via List injection
  • Result: new strategy = new @Component

Best Practices

  1. Lambdas for simple strategies
  2. Enum for a limited set
  3. Classes for complex logic
  4. Auto-registry in Spring via List
  5. Context Object for data passing
  6. Avoid in hot path (>5 implementations — HotSpot threshold)
  7. Template Method for parts of an algorithm
  8. Strategy for entire algorithm replacement

Senior Summary

  • Strategy = replacing the entire algorithm via composition
  • State = behavior change from within
  • Template Method = replacing parts via inheritance
  • JIT: megamorphic calls >5 implementations (HotSpot threshold) -> overhead
  • Hot path: enum switch > interface
  • Context Object for passing data to strategy
  • Spring auto-registry: List -> Map
  • Modern Java: lambdas replace simple strategies

Interview Cheat Sheet

Must know:

  • Strategy replaces growing switch/if-else via composition — each algorithm in a separate class
  • In Modern Java, lambdas replace simple strategies: Map<Enum, Function> instead of 10+ classes
  • Spring auto-registry: List injection -> automatic Map<String, Strategy>
  • JIT optimization: megamorphic calls (>5 implementations — HotSpot threshold) -> virtual call overhead ~5-10ns
  • For hot paths: enum switch is faster than interface (JIT compiles to tableswitch -> O(1))
  • Context Object Pattern: passing data to strategy via context object instead of 5+ parameters
  • Enum Strategy — for a limited set of algorithms with compact code

Common follow-up questions:

  • When is Strategy overengineering? — Simple if-else with 2-3 branches and uncomplicated logic
  • How does Strategy differ from State? — Strategy is chosen externally (client), State changes internally (object itself)
  • What is a megamorphic call? — Method call with >5 implementations (HotSpot threshold) -> JIT doesn’t inline -> overhead
  • How does Spring auto-register strategies? — List injection -> Stream.collect(toMap)

Red flags (DO NOT say):

  • “I use Strategy for 2 if-else branches” — overengineering, lambdas are enough
  • “Strategy doesn’t affect performance” — megamorphic calls >5 implementations have measurable overhead
  • “I don’t use Strategy, I write switch” — OCP violation, adding a new type = code modification
  • “All strategies must be in separate classes” — lambdas and enum replace simple strategies

Related topics:

  • [[13. Difference between State and Strategy]] — pattern comparison
  • [[01. What are design patterns]] — lambdas replace Strategy
  • [[02. What pattern categories exist]] — Behavioral patterns
  • [[16. What anti-patterns do you know]] — Golden Hammer
  • [[14. What Proxy types exist]] — alternative Structural approach