Strategy Pattern
BeginnerDefines a family of algorithms, encapsulates each one, and makes them interchangeable at runtime without changing the client.
Overview
Strategy eliminates if-else or switch chains by encapsulating each algorithm variant behind a common interface. The Context class holds a reference to a Strategy interface and delegates the algorithm to the current strategy. Strategies are interchangeable — the Context does not know which concrete strategy it is using. This follows the Open/Closed Principle: adding a new algorithm requires only a new Strategy class, not modifying the Context. Java lambda expressions make single-method Strategy interfaces trivially implementable without explicit classes.
Strategy Implementation (Replace if-else)
Replace a growing if-else chain for payment processing with a Strategy interface. Each payment method becomes a ConcreteStrategy. The PaymentContext delegates to whichever strategy is currently set.
// Strategy interface
public interface PaymentStrategy {
boolean pay(double amount);
String methodName();
}
// Concrete Strategies
public class CreditCardStrategy implements PaymentStrategy {
private final String cardNumber;
private final String cvv;
public CreditCardStrategy(String cardNumber, String cvv) {
this.cardNumber = cardNumber;
this.cvv = cvv;
}
@Override
public boolean pay(double amount) {
System.out.printf("Paid ₹%.2f via Credit Card ending %s%n",
amount, cardNumber.substring(cardNumber.length() - 4));
return true;
}
@Override public String methodName() { return "CREDIT_CARD"; }
}
public class UpiStrategy implements PaymentStrategy {
private final String upiId;
public UpiStrategy(String upiId) { this.upiId = upiId; }
@Override
public boolean pay(double amount) {
System.out.printf("Paid ₹%.2f via UPI ID: %s%n", amount, upiId);
return true;
}
@Override public String methodName() { return "UPI"; }
}
public class WalletStrategy implements PaymentStrategy {
private double balance;
public WalletStrategy(double balance) { this.balance = balance; }
@Override
public boolean pay(double amount) {
if (balance < amount) {
System.out.println("Insufficient wallet balance");
return false;
}
balance -= amount;
System.out.printf("Paid ₹%.2f via Wallet. Remaining: ₹%.2f%n", amount, balance);
return true;
}
@Override public String methodName() { return "WALLET"; }
}
// Context
public class CheckoutContext {
private PaymentStrategy strategy;
public void setStrategy(PaymentStrategy strategy) {
this.strategy = Objects.requireNonNull(strategy);
}
public boolean checkout(double amount) {
if (strategy == null) throw new IllegalStateException("No payment strategy set");
System.out.println("Attempting payment via: " + strategy.methodName());
return strategy.pay(amount);
}
}
// Usage — strategy swapped at runtime
CheckoutContext ctx = new CheckoutContext();
ctx.setStrategy(new UpiStrategy("akshay@okaxis"));
ctx.checkout(999.0);
ctx.setStrategy(new WalletStrategy(500.0));
ctx.checkout(999.0); // fails — insufficient balance
ctx.setStrategy(new CreditCardStrategy("4111111111111234", "123"));
ctx.checkout(999.0); // succeedsStrategy with Java Lambdas
When the Strategy interface has a single abstract method (@FunctionalInterface), Java lambdas replace concrete strategy classes. This dramatically reduces boilerplate for simple strategies.
@FunctionalInterface
public interface SortStrategy {
void sort(int[] array);
}
public class Sorter {
private SortStrategy strategy;
public Sorter(SortStrategy strategy) { this.strategy = strategy; }
public void sort(int[] array) { strategy.sort(array); }
}
// Lambda strategies — no concrete class needed
Sorter bubbleSorter = new Sorter(array -> {
// bubble sort implementation
for (int i = 0; i < array.length - 1; i++)
for (int j = 0; j < array.length - 1 - i; j++)
if (array[j] > array[j+1]) { int t = array[j]; array[j] = array[j+1]; array[j+1] = t; }
});
// Method references as strategies
Sorter javaSorter = new Sorter(Arrays::sort); // java.util.Arrays.sort as strategy
// Java Comparator IS a Strategy (functional interface for sort comparison)
List<Course> courses = getCourses();
courses.sort(Comparator.comparing(Course::getRating).reversed()
.thenComparing(Course::getTitle)); // composed strategiesKey Points to Remember
- 1Strategy replaces conditional branches with polymorphism — each branch becomes a class.
- 2The Context delegates to the Strategy — it does not know or care which strategy is active.
- 3Prefer @FunctionalInterface strategies; pass lambdas instead of creating concrete classes.
- 4Java Comparator is the canonical Strategy — Comparator.comparing() composes strategies.
- 5Strategy (behavioral) vs Bridge (structural): Strategy swaps algorithms; Bridge separates abstraction from implementation.
Interview Questions
Sign in to ask AriaHow does Strategy pattern replace large if-else chains?
What is the relationship between Strategy pattern and Java Comparator?
How does Strategy differ from the Template Method pattern?
Can you implement a discount calculator using Strategy? Show the code.
How would you select a Strategy at runtime based on user input?
Ask Aria about Strategy Pattern
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.