Strategy Pattern—simplify decision-making and enhance flexibility in your code!

What is Strategy pattern?

Following is a diagram that captures how a strategy pattern looks like:\

When to use Strategy Pattern?

The strategy pattern is a design pattern in software engineering that enables an object to change its behavior dynamically by switching out parts of its algorithm at runtime. It’s particularly useful in scenarios where multiple algorithms might be used interchangeably to accomplish a task, depending on the situation. Here are some scenarios where and when to use the strategy pattern:

  1. When you have multiple algorithms for performing a task, and the choice of algorithm depends on the context of execution. For example, different sorting algorithms might be preferable depending on the size and distribution of the dataset.
  2. When parts of your application involve complex decision-making algorithms that might vary or evolve independently of the rest of the system. Using the strategy pattern can help isolate these algorithms, making the system easier to maintain and adapt.
  3. When you need to replace multiple conditional branches in your code that select different code paths based on the state or type of data. This helps in adhering to the Open/Closed Principle, part of SOLID principles, which suggests that classes should be open for extension but closed for modification.
  4. When new behaviors need to be added frequently and the existing code should not be modified. The strategy pattern allows you to introduce new strategies without altering the context.
  5. When you want to decouple the implementation details of an operation from the class that uses it. This is beneficial for reducing dependencies and for improving testability through mocking.
  6. When the application needs to change its behavior dynamically at runtime depending on user actions or input. For example, changing the behavior of a graphical object in a user interface depending on user preferences or actions.
  7. When creating simulations or tests that require swapping out complex behavior with more predictable, controlled, or simplified behavior. It allows for easier testing by injecting mock strategies that have predictable outcomes.

Why is strategy pattern used?

  1. Flexibility to Switch Algorithms: One of the primary uses of the Strategy pattern is to enable the dynamic switching of algorithms under varying conditions during runtime. For instance, different sorting algorithms might be more efficient depending on the size and distribution of the data set.
  2. Promotes Open/Closed Principle: The Strategy pattern adheres to the Open/Closed Principle, one of the core principles of SOLID design. This principle states that software entities (like classes, modules, functions, etc.) should be open for extension but closed for modification. By using the Strategy pattern, you can keep your class closed for changes (by not modifying existing code) and still extend its behavior by adding new strategies.
  3. Decoupling Algorithm Implementation: The pattern helps in decoupling the algorithm implementation from the code that uses the algorithm. This means that the client code can operate independently of the specific algorithms being used, leading to easier maintenance and scalability.
  4. Avoids Conditional Statements: Using the Strategy pattern can eliminate the need for conditional statements when selecting desired behavior. This simplifies code and reduces the likelihood of errors during modification.
  5. Ease of Testing: Each strategy can be tested independently from the clients and other strategies. This isolation helps in unit testing and debugging as you can focus on one strategy at a time without worrying about the dependencies on others.
  6. Better Code Organization: The Strategy pattern helps in organizing the code by segregating different behaviors into separate classes. This improves modularity and makes the system easier to understand, extend, or modify.
  7. Dynamic Behavior Assignment: It provides a mechanism to assign behavior dynamically to an object. This is more flexible and adaptable than inheritance which only allows static behavior assignment through subclassing.

Overall, the Strategy pattern is highly useful for scenarios where multiple versions of an algorithm exist, or if algorithmic behavior needs to vary dynamically. It offers a clean architecture by separating the selection of an algorithm from the algorithm’s implementation, which helps manage complexity and enhance maintainability.

How to implement Strategy pattern (in Java)

Lets say we have the following problem statement:

We have to create an application that processes payments, where the payment method (e.g., credit card, PayPal, or debit card) can change dynamically. First we will see how the code would be written without strategy pattern and then with strategy pattern.

Without using strategy pattern

in this pattern we directly create a payment processor class which uses if else statements to process the payment logic.

Challenge: The main challenge with this code is that everytime a new payment method is added, the Payment processor class will need to be updated.

public class PaymentProcessor {

    public void pay(int amount, String paymentType) {
        if (paymentType.equals("CreditCard")) {
            System.out.println("Paying " + amount + " using Credit Card");
            // Credit card payment logic
        } else if (paymentType.equals("PayPal")) {
            System.out.println("Paying " + amount + " using PayPal");
            // PayPal payment logic
        } else if (paymentType.equals("DebitCard")) {
            System.out.println("Paying " + amount + " using Debit Card");
            // Debit card payment logic
        } else {
            System.out.println("Payment method not supported.");
        }
    }
}

Usage of the above code in the main method:

public class PaymentDemo {
    public static void main(String[] args) {
        PaymentProcessor processor = new PaymentProcessor();
        processor.pay(100, "CreditCard");
        processor.pay(200, "PayPal");
        processor.pay(150, "DebitCard");
    }
}

Updated class structure using the Strategy Pattern

We first create the strategy interface and then create concrete objects out of them. Create another context class which will use the strategy concrete classes to execute. This context class can also be replaced by composite class when where is a need that more than one strategy need to be applied. In our example, we would be using only one strategy

public interface PaymentStrategy {
    void pay(int amount);
}

public class CreditCardPayment implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("Paying " + amount + " using Credit Card");
    }
}

public class PayPalPayment implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("Paying " + amount + " using PayPal");
    }
}

public class DebitCardPayment implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("Paying " + amount + " using Debit Card");
    }
}

Here is the context class:

public class PaymentContext {
    private PaymentStrategy paymentStrategy;

    public PaymentContext(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }

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

    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }
}

Hers the main class to use the strategy pattern

public class PaymentDemo {
    public static void main(String[] args) {
        PaymentContext context = new PaymentContext(new CreditCardPayment());
        context.executePayment(100);

        context.setPaymentStrategy(new PayPalPayment());
        context.executePayment(200);

        context.setPaymentStrategy(new DebitCardPayment());
        context.executePayment(150);
    }
}

Note: Did you notice that for every new payment method that we add, we just have to create a concrete class extending the strategy interface and use it directly in the PaymentDemo class.

See more in