Strategy Pattern: Behavioral Injection

The Strategy Pattern (or Policy Pattern) enables a class to select an algorithm's behavior at runtime. It defines a family of algorithms, encapsulates each one, and makes them interchangeable, allowing the algorithm to vary independently from the clients that use it.

I. The Lambda Evolution

In modern Java, creating a full class hierarchy for every strategy is often overkill. Using Functional Interfaces and Lambdas, we can inject behavior directly into the context.

Concrete Example: Functional Tax Calculator

public class CheckoutService {
    // Strategy defined as a functional interface
    private ToDoubleBiFunction<Double, String> taxStrategy;

    public void setTaxStrategy(ToDoubleBiFunction<Double, String> strategy) {
        this.taxStrategy = strategy;
    }

    public double calculateTotal(double amount, String country) {
        double tax = taxStrategy.applyAsDouble(amount, country);
        return amount + tax;
    }
}

// Usage
service.setTaxStrategy((amt, c) -> c.equals("EU") ? amt * 0.21 : amt * 0.05);

II. The Enum Strategy Pattern

For a fixed set of algorithms, an Enum can act as both the registry and the implementation provider. This is highly performant and type-safe.

public enum DiscountPolicy {
    RETAIL {
        @Override public double apply(double price) { return price; }
    },
    VIP {
        @Override public double apply(double price) { return price * 0.80; }
    },
    LIQUIDATION {
        @Override public double apply(double price) { return price * 0.50; }
    };

    public abstract double apply(double price);
}

III. Strategy vs. State

IV. Technical Integrity

  1. Avoid Strategy Proliferation: Do not create a strategy for every minor if statement. The pattern is intended for substantial behavioral variations.
  2. Performance: Functional dispatch via lambdas is highly optimized by the JVM (often inlined), but the indirection does have a minor cost compared to a static if/else. Use it when flexibility is required, not as a default for all logic.
  3. Context Passing: A strategy often needs data from the Context. Prefer passing only the required data (as in the TaxCalculator above) rather than passing the whole Context object to avoid tight coupling.

See Also: