21. Strategy
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it. The client selects the appropriate strategy at runtime without modifying the context.
Intent and Motivation
Intent: Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
Motivation: A navigation app supports walking, driving, and transit routes — each uses a different algorithm. Hard-coding if (mode == DRIVING) inside Navigator violates Open/Closed Principle and grows unwieldy. Strategy extracts each algorithm into its own class (DrivingStrategy, WalkingStrategy) implementing a RouteStrategy interface. The Navigator context holds a reference to the current strategy and delegates buildRoute() to it. Switching strategies at runtime is a single setStrategy() call.
Strategy is one of the most practical patterns for eliminating conditional complexity and enabling plugin-like extensibility.
Structure (UML-like)
┌──────────────┐ uses ┌──────────────┐
│ Context │ ────────────► │ Strategy │ (interface)
├──────────────┤ ├──────────────┤
│ - strategy │ │ + execute() │
│ + setStrategy│ └──────▲───────┘
│ + doSomething│ │ implements
└──────────────┘ ┌───────┴──────────────┐
│ │
┌──────┴──────┐ ┌───────┴──────┐
│ ConcreteSt. │ │ ConcreteSt. │
│ A │ │ B │
└─────────────┘ └──────────────┘
Participants:
- Strategy — common interface for all supported algorithms.
- ConcreteStrategy — implements a specific algorithm.
- Context — maintains a reference to a Strategy; delegates algorithm execution to it.
- Client — creates and configures the Context with a ConcreteStrategy.
Java Example
import java.util.*;
// Strategy
interface SortStrategy {
void sort(List<Integer> list);
}
class BubbleSort implements SortStrategy {
public void sort(List<Integer> list) {
System.out.println("Sorting with bubble sort");
Collections.sort(list); // simplified
}
}
class QuickSort implements SortStrategy {
public void sort(List<Integer> list) {
System.out.println("Sorting with quick sort");
list.sort(Integer::compareTo);
}
}
// Context
class Sorter {
private SortStrategy strategy;
Sorter(SortStrategy strategy) { this.strategy = strategy; }
void setStrategy(SortStrategy strategy) { this.strategy = strategy; }
void sort(List<Integer> list) { strategy.sort(list); }
}
// Usage
List<Integer> data = new ArrayList<>(List.of(5, 3, 8, 1));
Sorter sorter = new Sorter(new BubbleSort());
sorter.sort(data);
sorter.setStrategy(new QuickSort());
sorter.sort(data);
JavaScript Example
const paymentStrategies = {
creditCard: {
pay(amount) {
console.log(`Paid $${amount} via Credit Card`);
}
},
paypal: {
pay(amount) {
console.log(`Paid $${amount} via PayPal`);
}
},
crypto: {
pay(amount) {
console.log(`Paid $${amount} via Crypto`);
}
}
};
class Checkout {
constructor(strategy) {
this.strategy = strategy;
}
setPaymentMethod(method) {
this.strategy = paymentStrategies[method];
if (!this.strategy) throw new Error(`Unknown method: ${method}`);
}
processPayment(amount) {
this.strategy.pay(amount);
}
}
const checkout = new Checkout(paymentStrategies.creditCard);
checkout.processPayment(99.99);
checkout.setPaymentMethod('paypal');
checkout.processPayment(49.99);
Real-World Use Cases
| Framework / System | Usage |
|---|---|
Java Comparator |
Sorting strategy injected into Collections.sort(). |
Java ThreadPoolExecutor |
Rejection policies (Abort, CallerRuns, Discard) are strategies. |
Spring @Qualifier |
Selects among multiple strategy bean implementations. |
| Express middleware | Authentication strategies (JWT, OAuth, API key) swapped per route. |
| Compression libraries | gzip, bzip2, lz4 algorithms as interchangeable strategies. |
| Machine learning | Optimizer strategies (SGD, Adam, RMSprop) plugged into training loops. |
Pros and Cons
| Pros | Cons |
|---|---|
| Algorithms can be swapped at runtime | Clients must be aware of available strategies |
| Eliminates conditional statements for algorithm selection | Increased number of objects (one per strategy) |
| Open/Closed — add strategies without modifying context | Overkill when only one algorithm will ever exist |
| Each strategy is independently testable | Context must expose setStrategy() — clients control selection |
| Strategies can be shared across multiple contexts | Strategy selection logic can itself become complex |
When to Use vs When NOT to Use
Use when:
- Many related classes differ only in their behavior (algorithm).
- You need different variants of an algorithm.
- An algorithm uses data that clients should not know about.
- A class defines many behaviors appearing as conditional statements.
Do NOT use when:
- Only one algorithm exists and is unlikely to change.
- Algorithm selection is simple and stable (a single function or method suffices).
- Strategies differ only in configuration, not algorithm (use parameters instead).
- The overhead of strategy objects outweighs the flexibility benefit.
Common Mistakes
- Confusing Strategy with State — Strategy is chosen externally; State transitions internally based on context conditions.
- Context knowing concrete strategy classes — depend on the Strategy interface only.
- Strategy with state — strategies should be stateless when possible; stateful strategies complicate sharing.
- Too many strategies for minor variations — use parameters or configuration for small differences.
- Not using dependency injection — manually
new-ing strategies in context couples to concrete classes.
Related Patterns
- State — similar structure; State changes autonomously, Strategy is client-selected.
- Command — commands encapsulate actions; strategies encapsulate algorithms.
- Template Method — defines algorithm skeleton in a base class; Strategy delegates entire algorithm to a separate object.
- Bridge — Strategy varies behavior; Bridge varies implementation platform.
- Factory Method — can create the appropriate strategy based on configuration.