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.