82. Strategy Pattern
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable; in modern Java, functional interfaces and lambdas make Strategy trivial โ Comparator and Predicate are canonical examples.
Explanation
The Strategy pattern separates what to do from who decides to do it: the context class delegates a behaviour to a strategy object. The strategy is a single-method interface โ in Java 8+, this is just a functional interface, and strategies are lambdas or method references.
Classic examples in the JDK: Comparator<T> is a strategy for ordering; Predicate<T> is a strategy for filtering; Runnable is a strategy for a task; Comparator.comparing(...) composes strategies. The pattern appears in every sort, filter, and higher-order function call.
Before Java 8, Strategies required anonymous classes (verbose). Lambdas and functional interfaces make this pattern nearly free โ just pass a lambda where a functional interface is expected.
Example
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;
public class StrategyPattern {
// Pre-lambda style: explicit strategy interface
@FunctionalInterface
interface DiscountStrategy {
double apply(double price);
}
static class ShoppingCart {
private double total;
ShoppingCart(double total) { this.total = total; }
double checkout(DiscountStrategy strategy) {
return strategy.apply(total);
}
}
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart(100.0);
// Strategies as lambdas โ no class needed!
DiscountStrategy noDiscount = price -> price;
DiscountStrategy tenPercent = price -> price * 0.90;
DiscountStrategy flatFive = price -> price - 5;
DiscountStrategy loyaltyBonus = price -> price * 0.80;
System.out.printf("No discount: $%.2f%n", cart.checkout(noDiscount));
System.out.printf("10%% off: $%.2f%n", cart.checkout(tenPercent));
System.out.printf("$5 off: $%.2f%n", cart.checkout(flatFive));
System.out.printf("Loyalty (20%%): $%.2f%n", cart.checkout(loyaltyBonus));
// JDK Strategy: Comparator
List<String> words = Arrays.asList("banana", "apple", "cherry", "kiwi");
words.sort(Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder()));
System.out.println("Sorted by length: " + words);
// JDK Strategy: Predicate composition
Predicate<String> longWord = s -> s.length() >= 6;
Predicate<String> startsA = s -> s.startsWith("a");
List<String> filtered = words.stream().filter(longWord.or(startsA)).toList();
System.out.println("Long or starts-a: " + filtered);
// Runtime strategy selection
String mode = "loyalty";
DiscountStrategy selected = switch (mode) {
case "ten" -> tenPercent;
case "flat" -> flatFive;
case "loyalty" -> loyaltyBonus;
default -> noDiscount;
};
System.out.printf("Mode '%s': $%.2f%n", mode, cart.checkout(selected));
}
}Sources
- https://refactoring.guru/design-patterns/strategy โ Strategy pattern reference
- https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Comparator.html โ Comparator as Strategy