Strategy Pattern
Strategy Pattern
The Strategy pattern is one of the most frequently used behavioural patterns in professional Java. Its core idea is simple: define a family of algorithms, encapsulate each one behind a common interface, and make them interchangeable at runtime — without modifying the clients that use them. The pattern directly addresses the Open/Closed Principle: code is open for extension (add a new algorithm) but closed for modification (existing code does not change).
The Problem Without Strategy
Imagine a payment module that supports credit cards, PayPal and bank transfers. Without Strategy, the typical first draft puts all the logic in one class with a chain of if/else or a switch:
Every new payment method forces a change inside PaymentProcessor, it is impossible to test algorithms in isolation, and the class grows without bound. Strategy solves all three problems.
Structure of the Pattern
Three roles make up the pattern:
- Strategy interface — the contract every algorithm must satisfy.
- Concrete strategies — one class per algorithm, each implementing the interface.
- Context — holds a reference to a strategy, delegates work to it, and can swap strategies at runtime.
Canonical Java Example — Payment Processing
Usage is expressive and does not involve any conditional branching inside PaymentContext:
Lambdas as Strategies (Java 8+)
Notice the @FunctionalInterface annotation on PaymentStrategy — it has exactly one abstract method. That makes any lambda expression a valid concrete strategy, eliminating the need for a separate class for simple algorithms:
Method references work equally well when a pre-existing method already has the right signature:
cardNumber), owns mutable state, or needs to be unit-tested independently by name.
Real-World Example — Sorting with Comparator
You already use Strategy every day. java.util.Comparator is a Strategy interface. Collections.sort() and List.sort() are the Context:
The stream pipeline (the Context) is unchanged; only the Comparator (the Strategy) differs. The JDK designers applied the pattern so you never need to modify List to add a new sort order.
Strategy vs. Similar Patterns
- Template Method — fixes the skeleton in a base class and lets subclasses fill in steps via inheritance. Strategy delegates the whole algorithm via composition. Prefer composition over inheritance.
- State — looks identical in code (interface + context with a reference), but State transitions itself; Strategy is swapped by the caller. If the object decides which strategy to use next, it is State.
- Command — encapsulates a request as an object, often supporting undo. Strategy encapsulates an algorithm; the two are often combined (a Command chooses among Strategies).
Trade-offs and When Not to Use It
The pattern is powerful but not cost-free:
- Clients must be aware of strategies. The caller needs to know which strategy to inject. If selection logic is complex, move it to a factory or a configuration layer so the client stays clean.
- Overkill for two options. If a boolean flag between two simple branches is stable, a Strategy hierarchy adds indirection without benefit. Apply it when you anticipate a growing family of algorithms or when you need runtime swapping.
- Interface explosion. Each distinct algorithm family needs its own interface. Keep granularity appropriate — one interface per axis of variation.
switch expression may be clearer.
Summary
The Strategy pattern encapsulates interchangeable algorithms behind a shared interface, letting you swap behaviour at runtime without modifying the Context. In modern Java, @FunctionalInterface turns any Strategy into a target for lambdas and method references, reducing boilerplate dramatically. You see it throughout the JDK — Comparator, Predicate, Runnable, and ExecutorService are all Strategy in action. Apply it when you have a family of algorithms that varies independently from the code that uses them.