Simple Explanation for Strategy Pattern
Situation: You’re Building a Payment System
You’ve got a PaymentService
that supports credit card payments.
So you write:
public void pay(String method) {
if (method.equals("creditcard")) {
// handle credit card logic
}
}
Then… Feature Creep Happens
- Add PayPal
- Add UPI
- Add Crypto (because your PM is now a Bitcoin bro)
- Add Apple Pay, Wallets, NetBanking…
So now your method becomes:
if (method.equals("creditcard")) {
// credit card logic
} else if (method.equals("paypal")) {
// paypal logic
} else if ...
You’re drowning in if-else
hell. Every time a new payment method is added:
- You’re touching old code
- You risk breaking existing logic
- You can’t test strategies separately
- You’ve violated Open/Closed Principle (again)
What’s the Real Problem?
- Your code is tightly coupled to how each payment works.
- Adding new strategies means changing existing code.
- Testing or reusing individual strategies? Painful.
- The
PaymentService
is now responsible for too many things.
How Strategy Pattern Saves You
Strategy says: “Don’t embed logic for every case inside one class. Delegate the behavior to separate interchangeable strategy classes.”
You:
- Define a
PaymentStrategy
interface - Create one class per payment method:
CreditCardPayment
,PayPalPayment
,UPIPayment
, etc. - Your
PaymentService
doesn’t care how the payment is processed - it just calls.pay()
on the selected strategy.
Now:
- Adding a new payment method? Just add a new class.
- No touching old code.
- Each strategy is isolated, testable, replaceable.
Implementation
// Strategy interface
interface PaymentStrategy {
void pay(int amount);
}
// Concrete strategies
class CreditCardPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println("Paid " + amount + " using Credit Card");
}
}
class PayPalPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println("Paid " + amount + " using PayPal");
}
}
Context class that uses the strategy:
class PaymentService {
private PaymentStrategy strategy;
public PaymentService(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void processPayment(int amount) {
strategy.pay(amount);
}
}
Usage:
PaymentService service = new PaymentService(new CreditCardPayment());
service.processPayment(500); // Paid ₹500 using Credit Card
Want to switch to PayPal? No problem:
service = new PaymentService(new PayPalPayment());
service.processPayment(800); // Paid ₹800 using PayPal
When to Use Strategy?
- When you have multiple ways to perform a task (e.g., sorting, payment, compression).
- When those ways can change dynamically at runtime.
- When you want to avoid long if-else or switch blocks.
- When you want to separate business rules from workflow.
Benefits
- Open/Closed Principle: Add new strategies without modifying existing code
- Single Responsibility: Each strategy handles only one implementation approach
- Testability: Test each strategy in isolation
- Flexibility: Swap strategies at runtime based on conditions
- Maintainability: Cleaner code without complex conditionals
Real-World Examples
- Payment processing (as shown above)
- Different compression algorithms
- Multiple authentication methods
- Various notification strategies (email, SMS, push)
- Different sorting algorithms
Drawbacks
- Increases number of classes in your codebase
- May be overkill for simple conditional logic
- Clients must be aware of different strategies
The Strategy pattern is your friend whenever you find yourself writing a growing chain of conditionals to handle different ways of doing the same thing.