The Strategy pattern is a powerful behavioral design pattern that allows you to define a family of algorithms, encapsulate each one, and make them interchangeable. This pattern lets the algorithm vary independently from clients that use it, promoting flexibility and reusability in your code.

The Strategy pattern suggests that we extract all independent algorithms into distinct classes, each implementing a common interface. Instead of embedding multiple algorithms within a single class, the main class should contain a reference to a strategy object. This reference is typically set through the constructor or a setter method. By doing so, the pattern enables clients to easily switch between different algorithm implementations at runtime. This design promotes flexibility and decoupling, allowing the main class to delegate algorithm execution to the strategy object without being tightly bound to any specific implementation.

Structure

The Strategy pattern consists of the following key components:

  • Strategy: is an interface which is common to all supported algorithms
  • Concrete Strategy: are implementations of Strategy interface implementing respective algorithms
  • Context: uses the algorithms defined by different Strategy implementations and maintains a reference to the Strategy interface

When to use the Strategy Pattern?

  • When you want to introduce new strategies/algorithms in the future without breaking existing code (Open/Closed Principle)
  • When you want to switch algorithms at runtime and want to get rid of conditional statements

Implementation Example

The code below demonstrates the Strategy design pattern for a simple e-commerce payment system in Java. It defines a PaymentStrategy interface with a pay method, which is implemented by two concrete strategies: CreditCardPayment and PayPalPayment. The ShoppingCart class acts as the context, holding a reference to the current payment strategy and allowing it to be changed at runtime. In the Solution class, a shopping cart is created and used to process payments with different strategies. The client code can easily switch between payment methods by setting a new strategy on the cart. This structure allows for flexible and interchangeable payment processing, adhering to the Open/Closed Principle by enabling easy addition of new payment methods without modifying existing code.

// Client class
public class Solution {

    public static void main(String[] args) {

        ShoppingCart cart = new ShoppingCart();

        cart.setPaymentStrategy(new PayPalPayment("johndoe@example.com"));
        cart.checkout(100);
        
        cart.setPaymentStrategy(new CreditCardPayment("John Doe", "1234567890123456"));
        cart.checkout(200);
    }
}

// Strategy interface
interface PaymentStrategy {

    void pay(int amount);
}

// Concrete strategies
class CreditCardPayment implements PaymentStrategy {

    private String name;
    private String cardNumber;

    public CreditCardPayment(String name, String cardNumber) {

        this.name = name;
        this.cardNumber = cardNumber;
    }

    public void pay(int amount) {

        System.out.println("Payment of " + amount + " accepted using Credit Card!");
    }
}

class PayPalPayment implements PaymentStrategy {

    private String email;

    public PayPalPayment(String email) {

        this.email = email;
    }

    public void pay(int amount) {

        System.out.println("Payment of " + amount + " accepted using PayPal!");
    }
}

// Context
class ShoppingCart {

    private PaymentStrategy paymentStrategy;

    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {

        this.paymentStrategy = paymentStrategy;
    }

    public void checkout(int amount) {

        paymentStrategy.pay(amount);
    }
}

The output of the following program will be:

Payment of 100 accepted using PayPal!
Payment of 200 accepted using Credit Card!