All topics
library
intermediate

Strategy Pattern with Functional Interfaces

Replace class-based strategy implementations with lambdas and functional interfaces for cleaner code.

Strategy means the behaviour is a parameter. Since Java 8 the parameter is usually a lambda.

Strategy = GPS navigation modes. The destination (context) stays the same, but you can switch between 'fastest route,' 'shortest route,' or 'avoid tolls' (strategies) without changing the map app.

Key Concepts

1
    // the old shape: an interface and a class per strategy
    interface Discount { double apply(double amount); }
    class TenPercent implements Discount {
      public double apply(double a) { return a * 0.9; }
    }
2
    // the same thing today
    Discount tenPercent = a -> a * 0.9;
    Discount none       = a -> a;
3
What it replaces: a switch that grows forever.
    double price(String tier, double amount) {
      switch (tier) {
        case "gold":   return amount * 0.8;
        case "silver": return amount * 0.9;
        default:       return amount;
      }
    }
4
Every new tier edits this method. A dispatch table does not:
5
    static final Map<String, Discount> TIERS = Map.of(
      "gold",   a -> a * 0.8,
      "silver", a -> a * 0.9
    );
6
    double price(String tier, double amount) {
      return TIERS.getOrDefault(tier, a -> a).apply(amount);
    }
7
Adding a tier is adding a map entry — nothing existing is touched, which is what the open/closed principle asks for.
8
Often you need no interface of your own.
    Predicate<T>            a test
    Function<T, R>          a transformation
    Comparator<T>           an ordering
    Supplier<T>             a factory
    Consumer<T>             a side effect
    UnaryOperator<T>        T to T, like the discount above
9
The JDK is full of it.
    list.sort(Comparator.comparing(Order::total));
    list.stream().filter(o -> o.total() > 100);
    map.computeIfAbsent(k, key -> load(key));
10
Where a class is still better. A strategy that needs state, several methods, a name in a stack trace, or its own tests. A lambda has no name, so a failure inside one reports as lambda$price$0.
lambda$price$0
11
And in Spring, injecting a Map<String, Discount> of named beans gives you the dispatch table with the strategies as testable components.
Map<String, Discount>
12
What the interviewer is probing.1. "How has Java 8 changed the Strategy pattern?" Probing: whether you can connect a pattern to the language. Stalls: "It has not." Moves up: a strategy with one method is a lambda, so the class per strategy disappears and a Map of lambdas replaces the switch.
13
2. "How does a dispatch map satisfy the open/closed principle?" Probing: the principle applied concretely. Stalls: "It is cleaner." Moves up: adding a strategy is adding a map entry; no existing method is edited, which is what open for extension and closed for modification means.
14
3. "When is a class still better than a lambda?" Probing: the limits. Stalls: "Never." Moves up: when the strategy needs state, several methods, its own tests, or a name — a lambda appears in stack traces as lambda$method$0.
15
4. "Which JDK interfaces are strategies you already use?" Probing: recognition. Stalls: "None." Moves up: Comparator, Predicate, Function and Supplier — every sort, filter and map call is the pattern.