1. Definition

The Strategy pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows the algorithm to vary independently from the clients that use it.

2. Problem Statement

Consider an application that implements a specific operation in multiple ways. Embedding these algorithms directly into the application’s classes can make them hard to maintain, especially when the number of methods grows or they need to be reused across different parts of the application.

3. Solution

The Strategy pattern suggests defining a set of algorithms, encapsulating each one in a separate class, and making their objects interchangeable. This way, the class containing the behavior (the context) will have a reference to a strategy object and delegate the execution to this object instead of executing the behavior directly.

4. Real-World Use Cases

1. Sorting algorithms in libraries where multiple methods (like QuickSort, MergeSort) can be easily switched.

2. Different compression algorithms for file archiving tools.

5. Implementation Steps

1. Define the strategy interface common to all supported algorithms.

2. Implement concrete strategy classes for all variants of the algorithm.

3. The context class should maintain a reference to a strategy object and use it to execute the algorithm.

6. Implementation in Java

// Strategy Interface
interface PaymentStrategy {
    void pay(int amount);
}

// Concrete Strategy 1
class CreditCardPayment implements PaymentStrategy {
    private String cardNumber;

    public CreditCardPayment(String cardNumber) {
        this.cardNumber = cardNumber;
    }

    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using Credit Card ending with " + cardNumber);
    }
}

// Concrete Strategy 2
class PayPalPayment implements PaymentStrategy {
    private String email;

    public PayPalPayment(String email) {
        this.email = email;
    }

    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using PayPal with email " + email);
    }
}

// Context
class ShoppingCart {
    private PaymentStrategy paymentStrategy;

    public ShoppingCart(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    public void checkout(int amount) {
        paymentStrategy.pay(amount);
    }
}

// Client Code
public class Client {
    public static void main(String[] args) {
        ShoppingCart cart1 = new ShoppingCart(new CreditCardPayment("1234"));
        cart1.checkout(100);

        ShoppingCart cart2 = new ShoppingCart(new PayPalPayment("user@example.com"));
        cart2.checkout(150);
    }
}

Output:

Paid 100 using Credit Card ending with 1234
Paid 150 using PayPal with email user@example.com

Explanation:

The CreditCardPayment and PayPalPayment classes implement the PaymentStrategy interface, defining different ways to process payment. The ShoppingCart class (the context) has a reference to a PaymentStrategy object. Instead of processing payments directly, it delegates the work to the strategy object. This makes it easy to switch between different payment methods.

7. When to use?

1. When you have multiple ways to perform an operation and want to switch between these methods at runtime.

2. To isolate the complex, varying code from the rest of the application.

3. To make the application easier to extend and maintain when new methods are required.