The Strategy Design Pattern: Kill the If-Else Statements
Master the Strategy Pattern in Java. Learn how to replace complex if-else logic with interchangeable algorithms and follow the Open/Closed Principle.
Moshiour Rahman
Advertisement
The Problem: The If-Else Monster
Imagine you are building an E-Commerce checkout implementation. You need to handle payments.
public void pay(String type, int amount) {
if (type.equals("CREDIT_CARD")) {
// ... 20 lines of credit card logic ...
System.out.println("Paid with Card");
} else if (type.equals("PAYPAL")) {
// ... 20 lines of PayPal logic ...
System.out.println("Paid with PayPal");
} else if (type.equals("CRYPTO")) {
// ... 20 lines of Crypto logic ...
System.out.println("Paid with Crypto");
} else {
throw new IllegalArgumentException("Unknown method");
}
}
Why this is bad:
- Violation of Open/Closed Principle: Every time you add a new payment method (e.g., Apple Pay), you must modify the
payclass. This risks breaking existing code. - Hard to Read: The method grows indefinitely.
- Hard to Test: You have to test all logic paths in one giant method.
The Solution: The Strategy Pattern
The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it.
Real-Life Analogy: GPS Navigation 🗺️
When you use Google Maps, you select a destination. Then you choose a Strategy:
- 🚗 Driving: Optimized for roads, avoiding traffic.
- 🚶 Walking: Optimized for sidewalks and parks.
- 🚌 Transit: Optimized for bus/train schedules.
The Goal (A to B) is the same. The Strategy (How to get there) creates different paths.
Visualizing the Pattern

Implementation
1. The Interface
Define the common contract for all strategies.
public interface PaymentStrategy {
void pay(int amount);
}
2. Concrete Strategies
Implement the specific logic in separate classes.
public class CreditCardStrategy implements PaymentStrategy {
private String cardNumber;
public CreditCardStrategy(String cardNumber) {
this.cardNumber = cardNumber;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid with Credit Card: " + cardNumber);
}
}
public class PayPalStrategy implements PaymentStrategy {
private String email;
public PayPalStrategy(String email) {
this.email = email;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid using PayPal: " + email);
}
}
3. The Context
The class that uses the strategy. It doesn’t know which strategy it’s using, just that it fulfills the interface.
public class ShoppingCart {
// Rely on abstraction, not implementation
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout(int amount) {
if (paymentStrategy == null) {
throw new IllegalStateException("Payment method not selected");
}
paymentStrategy.pay(amount);
}
}
Usage
ShoppingCart cart = new ShoppingCart();
// User selects PayPal
cart.setPaymentStrategy(new PayPalStrategy("user@example.com"));
cart.checkout(100);
// User switches to Credit Card
cart.setPaymentStrategy(new CreditCardStrategy("1234-5678-9012-3456"));
cart.checkout(200);
In The Wild (Real World Examples)
1. java.util.Collections.sort()
When you sort a list, you provide a Comparator. The sorting algorithm (MergeSort/TimSort) is fixed, but the Comparison Strategy is customized by you.
// We pass a "Strategy" for comparing integers
Collections.sort(numbers, (a, b) -> b - a);
2. Spring Framework (Resource)
Spring uses different strategies to load resources depending on the prefix (classpath:, file:, http:).
Cheat Sheet
| Feature | Details |
|---|---|
| Category | Behavioral |
| Problem Solved | Massive switch/if-else statements, rigid algorithms |
| Key Principle | Open/Closed Principle |
| Signature Look | A class accepting an Interface in its setters or constructor (setAlgorithm(Algorithm a)) |
| Pros | Swappable logic at runtime, cleaner code, easier testing |
| Cons | Increases number of classes |
Tips to Remember 🧠
- “The Plug Adapter”: Think of it like plugging different distinct cartridges into a game console.
- Runtime Switching: If you need to change behavior while the app is running (e.g., Light Mode vs Dark Mode), use Strategy.
- Composition over Inheritance: Instead of
class Dog extends Animal, useclass Animal { MoveStrategy ms; }. This lets a Dog switch from “Walking” to “Swimming” without changing its class.
Advertisement
Moshiour Rahman
Software Architect & AI Engineer
Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.
Related Articles
The Template Method Pattern: The Recipe for Success
Master the Template Method Pattern in Java. Learn how to define the skeleton of an algorithm in a superclass but let subclasses override specific steps.
JavaThe State Design Pattern: Machines with Personality
Master the State Pattern in Java. Learn how to let an object alter its behavior when its internal state changes, replacing massive switch statements.
JavaThe Builder Design Pattern: The Cure for Constructor Headache
Master the Builder Pattern in Java. Learn how to construct complex objects step-by-step, avoid telescoping constructors, and write readable code.
Comments
Comments are powered by GitHub Discussions.
Configure Giscus at giscus.app to enable comments.