Design Patterns Strategy
The Strategy pattern lets you define a family of algorithms, put each one in its own class, and swap them in and out at runtime. The object that uses the algorithm does not know the details inside it — it just calls a standard method and gets a result.
Think of a GPS app. You can choose to travel by car, bike, or on foot. The destination is the same. The route-calculation algorithm changes. The app does not rewrite itself — it just swaps the strategy.
What Problem Does It Solve?
Without Strategy, you often see large if-else or switch blocks that select behavior:
void sort(int[] data, String type) {
if (type.equals("bubble")) {
// bubble sort code
} else if (type.equals("quick")) {
// quick sort code
} else if (type.equals("merge")) {
// merge sort code
}
}
Every new sorting type forces you to open this method and add another branch. Tests become complicated. The method grows without limit. Strategy replaces this with clean, separate classes.
Core Roles in Strategy
Context
The Context holds a reference to a Strategy object. It delegates the work to that strategy. It does not perform the algorithm itself.
Strategy Interface
Defines the common method that all concrete strategies must implement. The Context talks to this interface, not to specific classes.
Concrete Strategy
Each concrete strategy implements the interface with a specific algorithm.
A Diagram in Plain Text
+------------------+ +-----------------------+
| Sorter | | <<Strategy>> |
| (Context) |------>| sort(int[] data) |
| | +-----------------------+
| strategy | /\
| setStrategy() | ________|________
| executeSort() | | | |
+------------------+ [Bubble] [Quick] [Merge]
Sort Sort Sort
The Sorter calls strategy.sort(data). Swap the strategy object and you get completely different behavior without changing the Sorter.
Code Example in Java
Step 1 — Define the Strategy Interface
interface SortStrategy {
void sort(int[] data);
}
Step 2 — Create Concrete Strategies
class BubbleSort implements SortStrategy {
public void sort(int[] data) {
System.out.println("Sorting with Bubble Sort");
// bubble sort logic here
}
}
class QuickSort implements SortStrategy {
public void sort(int[] data) {
System.out.println("Sorting with Quick Sort");
// quick sort logic here
}
}
class MergeSort implements SortStrategy {
public void sort(int[] data) {
System.out.println("Sorting with Merge Sort");
// merge sort logic here
}
}
Step 3 — Build the Context
class Sorter {
private SortStrategy strategy;
public Sorter(SortStrategy strategy) {
this.strategy = strategy;
}
public void setStrategy(SortStrategy strategy) {
this.strategy = strategy;
}
public void sort(int[] data) {
strategy.sort(data);
}
}
Step 4 — Use It
int[] data = {5, 2, 8, 1, 9};
Sorter sorter = new Sorter(new BubbleSort());
sorter.sort(data); // Bubble Sort runs
sorter.setStrategy(new QuickSort());
sorter.sort(data); // Quick Sort runs now
sorter.setStrategy(new MergeSort());
sorter.sort(data); // Merge Sort runs now
Real-World Uses
- Payment systems: One checkout page supports credit card, PayPal, UPI, and crypto. Each payment method is a separate strategy.
- Compression tools: A file archiver lets you choose ZIP, GZIP, or BZIP2. Each format is a strategy.
- Navigation apps: Fastest route, shortest route, avoid tolls — each routing algorithm is a strategy.
- Tax calculation: Different tax rules for different countries or product types are separate strategies.
Strategy with Lambda (Java 8+)
In modern Java, you can pass strategies as lambda expressions instead of creating full classes for simple cases:
// No separate class needed for simple strategies
Sorter sorter = new Sorter(data -> Arrays.sort(data));
sorter.sort(myArray);
// Swap with another lambda
sorter.setStrategy(data -> {
// custom reverse sort
Arrays.sort(data);
reverseArray(data);
});
Lambdas work when the strategy interface has exactly one method. The pattern stays the same — you are still injecting behavior from outside.
Strategy vs if-else: Side-by-Side
WITHOUT STRATEGY:
-----------------
class Discount {
double apply(String type, double price) {
if (type.equals("seasonal")) return price * 0.8;
if (type.equals("loyalty")) return price * 0.9;
if (type.equals("flash")) return price * 0.5;
return price;
}
}
// Every new discount type: open this class, add another branch
WITH STRATEGY:
--------------
interface DiscountStrategy {
double apply(double price);
}
class SeasonalDiscount implements DiscountStrategy {
public double apply(double price) { return price * 0.8; }
}
class LoyaltyDiscount implements DiscountStrategy {
public double apply(double price) { return price * 0.9; }
}
// Every new discount type: add one new class, touch nothing else
Common Mistakes to Avoid
Overusing Strategy for Simple Cases
If you have only two choices and they never change, a simple if-else is fine. Strategy adds classes. Only add that complexity when you have multiple interchangeable algorithms or expect new ones to arrive.
Leaking Context Data into the Strategy
Strategies receive data through the method call. Avoid passing the entire Context object into the strategy — that creates a hidden dependency and makes strategies hard to reuse.
Stateful Strategies
Keep strategies stateless when possible. A stateless strategy can be shared between many contexts. A stateful strategy must be a separate instance per context, which costs memory.
Strategy vs Template Method
Both patterns handle variable algorithms, but they work differently:
Strategy: - Algorithm lives in a separate class - Switched at runtime by replacing the object - Uses composition (has-a relationship) Template Method: - Algorithm skeleton in the base class - Variable steps overridden in subclasses - Uses inheritance (is-a relationship)
Choose Strategy when you want to switch behavior at runtime. Choose Template Method when the overall flow is fixed and only a few steps vary.
Benefits and Trade-offs
Benefits
- Add new algorithms without modifying existing code.
- Algorithms are isolated and easy to test individually.
- Replace behavior at runtime based on user input or configuration.
- Eliminates large conditional blocks.
Trade-offs
- Clients must know which strategies exist to select the right one.
- Small differences between strategies may not justify separate classes.
- More classes in the codebase increases initial reading effort.
Quick Summary
Strategy separates an algorithm from the object that uses it. Define a common interface, implement each variation in its own class, and inject the right one at runtime. The result is a system where new behaviors can be added by writing new classes, not by editing existing ones.
