Strategy Pattern: Change Algorithm dynamically at Runtime

The strategy pattern is a behavioral design pattern that enables an object to dynamically change its algorithm or behavior at runtime. It encapsulates interchangeable algorithms within separate classes, allowing them to be selected and executed based on specific requirements.

By decoupling the implementation details from the client code, the strategy pattern promotes code flexibility, extensibility, and maintainability. It is commonly used when multiple algorithms or variations of a behavior need to be supported, providing a clean and structured way to manage different strategies without modifying the core logic of an application.

Real World Analogy

Let’s consider a real-world analogy of a payment system using the strategy pattern:

Imagine you are building a payment system for an online marketplace. The payment system supports multiple payment gateways such as PayPal, Stripe, and Braintree. Each payment gateway has its own API and integration requirements.

In this analogy, the payment system is the context, and the payment gateways are the strategies. The context interacts with the payment gateways using a common interface, allowing the system to switch between different payment gateways seamlessly.

The strategy pattern allows the payment system to encapsulate the different payment gateway implementations behind a common interface. The system can dynamically select and switch between payment gateways based on factors such as user preferences, availability, or specific business requirements.

Applicability

Here are some common scenarios where the strategy pattern can be applied:

  1. Algorithm Selection: When you have multiple algorithms or strategies to solve a particular problem, the strategy pattern allows you to encapsulate each algorithm into separate classes and dynamically select the appropriate strategy at runtime.
  2. Configuration or Feature Switching: The strategy pattern is useful when you need to switch between different configurations or enable/disable certain features in your application. Each configuration or feature can be represented by a strategy, and the context can switch between strategies based on the desired configuration.
  3. Integration with External Systems: When integrating with external systems or services that have different APIs or protocols, the strategy pattern helps in encapsulating the integration logic for each system separately. Each integration can be implemented as a separate strategy.
  4. Input/Output Processing: In scenarios where you need to process different types of input or output formats, such as parsing different file formats or generating various output formats, the strategy pattern can be applied. Each input/output format can be represented by a strategy, and the context can utilize the appropriate strategy based on the specific format requirements.

Applications of Strategy Pattern

The strategy design pattern finds real-world applications in various domains. Here are some examples of its practical use:

  1. Sorting Algorithms: Different sorting algorithms, such as bubble sort, merge sort, and quicksort, can be encapsulated as strategies. The context can select and utilize the appropriate sorting strategy based on factors like input size or desired performance.
  2. Payment Processing: Payment systems often involve multiple payment gateways or processors. Each payment gateway can be implemented as a separate strategy, allowing the system to dynamically switch between different payment strategies based on factors like availability, cost, or customer preference.
  3. Routing in Navigation Systems: Navigation systems employ various routing strategies, such as shortest distance, fastest route, or avoiding toll roads. Each routing strategy can be represented as a strategy, enabling the system to choose the appropriate strategy based on user preferences or current traffic conditions.
  4. Data Compression: Different compression algorithms, like gzip, zlib, or LZ77, can be implemented as strategies. The context can switch between compression strategies based on factors such as file type, data characteristics, or desired compression level.
  5. File Format Conversion: Applications dealing with file format conversions, such as image converters or document converters, can utilize the strategy pattern. Each file format conversion can be implemented as a strategy, enabling the application to support multiple formats and switch between conversion strategies as needed.
  6. Cache Management: Caching systems employ various cache replacement strategies, such as least recently used (LRU), first-in-first-out (FIFO), or least frequently used (LFU). Each cache replacement strategy can be encapsulated as a strategy, allowing the caching system to switch between strategies based on cache size, performance requirements, or access patterns.
  7. Text Analysis and Processing: Text analysis systems, like sentiment analysis or text classification, can leverage the strategy pattern. Each analysis or processing technique can be implemented as a strategy, allowing the system to switch between strategies based on the specific analysis needs or application requirements.

Implementation of Strategy Pattern

components

The implementation of the strategy pattern typically involves the following components:

  • Strategy Interface: This is an interface that defines the common methods that all strategies must implement. It declares the operations that the context can use to interact with the strategies.
// Step 1: Create an interface defining the common strategy methods
interface Strategy {
    void executeStrategy();
}
Java
  • Concrete Strategies: These are the implementation classes that provide specific implementations for the strategy interface. Each concrete strategy encapsulates a particular algorithm or behavior.
// Step 2: Implement concrete strategies that implement the Strategy interface
class ConcreteStrategyA implements Strategy {
    @Override
    public void executeStrategy() {
        System.out.println("Executing Strategy A");
        // Add specific implementation for Strategy A
    }
}

class ConcreteStrategyB implements Strategy {
    @Override
    public void executeStrategy() {
        System.out.println("Executing Strategy B");
        // Add specific implementation for Strategy B
    }
}
Java
  • Context: The context class is responsible for managing and using the strategies. It typically holds a reference to the current strategy and provides methods for the client code to interact with the strategies. The context class delegates the execution of the strategy to the current strategy object.
// Step 3: Create a context class that interacts with the strategy objects
class Context {
    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void execute() {
        strategy.executeStrategy();
    }
}
Java

Components Interaction in Strategy Pattern

  • The client code creates the context object and selects an initial strategy to use.
  • The context object receives requests from the client code and delegates the execution of the requested operation to the current strategy.
  • The current strategy performs its specific implementation of the operation defined in the strategy interface.
  • If needed, the client code can change the current strategy of the context at runtime, allowing for dynamic switching between different strategies.
// Step 4: Demonstrate the usage of the strategy pattern
public class StrategyPatternExample {
    public static void main(String[] args) {
        // Create the context with an initial strategy
        Context context = new Context(new ConcreteStrategyA());

        // Execute the current strategy
        context.execute();

        // Change the strategy dynamically
        context.setStrategy(new ConcreteStrategyB());

        // Execute the new strategy
        context.execute();
    }
}
Java

Java Example of Strategy Pattern

Let’s continue with our analogy of a payment system:

// Step 1: Create an interface defining the common payment gateway methods
interface PaymentGateway {
    void processPayment(double amount);
    // Other methods for integration, configuration, etc.
}

// Step 2: Implement concrete payment gateways that implement the PaymentGateway interface
class PayPalGateway implements PaymentGateway {
    @Override
    public void processPayment(double amount) {
        // Integration code for processing payment via PayPal gateway
        System.out.println("Processing payment of $" + amount + " via PayPal");
        // Additional logic specific to PayPal gateway
    }
}

class StripeGateway implements PaymentGateway {
    @Override
    public void processPayment(double amount) {
        // Integration code for processing payment via Stripe gateway
        System.out.println("Processing payment of $" + amount + " via Stripe");
        // Additional logic specific to Stripe gateway
    }
}

// Step 3: Create a payment system class that interacts with the payment gateways
class PaymentSystem {
    private PaymentGateway paymentGateway;

    public void setPaymentGateway(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }

    public void processPayment(double amount) {
        if (paymentGateway != null) {
            paymentGateway.processPayment(amount);
        } else {
            System.out.println("No payment gateway set.");
        }
    }
}

// Step 4: Demonstrate the usage of the strategy pattern for the payment system
public class PaymentSystemExample {
    public static void main(String[] args) {
        // Create the payment system
        PaymentSystem paymentSystem = new PaymentSystem();

        // Process payment using PayPal gateway
        PaymentGateway payPalGateway = new PayPalGateway();
        paymentSystem.setPaymentGateway(payPalGateway);
        paymentSystem.processPayment(100.50);

        // Process payment using Stripe gateway
        PaymentGateway stripeGateway = new StripeGateway();
        paymentSystem.setPaymentGateway(stripeGateway);
        paymentSystem.processPayment(50.75);
    }
}
Java

Relation With Other Design Patterns

The strategy pattern can be related to and combined with other design patterns to solve more complex problems or improve the overall design of a system. Here are a few design patterns that have a relationship with the strategy pattern:

  1. Factory Method Pattern: The strategy pattern can be used in conjunction with the factory method pattern. The factory method pattern encapsulates the creation of objects, while the strategy pattern encapsulates the behavior of those objects. By combining these patterns, you can dynamically create and configure strategies using a factory method, providing flexibility in strategy instantiation.
  2. Template Method Pattern: The strategy pattern can be seen as a specialized form of the template method pattern. In the strategy pattern, the context class defines the high-level algorithm structure, while the strategies encapsulate the varying steps or behaviors within that structure. The template method pattern provides a framework for defining an algorithm, while the strategy pattern provides interchangeable implementations of specific steps.
  3. Decorator Pattern: The strategy pattern can be combined with the decorator pattern to add additional functionality or modify the behavior of a strategy dynamically. The decorator pattern allows you to wrap strategies with decorators, providing a way to modify or extend their behavior at runtime without affecting the client code or other strategies.
  4. Composite Pattern: The strategy pattern can be used in combination with the composite pattern to handle complex behaviors in a hierarchical structure. The composite pattern allows you to treat groups of strategies as individual entities, enabling you to compose and manage strategies in a tree-like structure. This combination is useful when dealing with complex systems where strategies can be nested or combined.
  5. State Pattern: The strategy pattern and the state pattern share similarities in terms of encapsulating varying behaviors. The strategy pattern encapsulates different algorithms or strategies, while the state pattern encapsulates different states of an object and their associated behaviors. Both patterns provide a way to change behavior dynamically by encapsulating it in separate classes.
  6. Command Pattern: The strategy pattern can be used in combination with the command pattern to encapsulate behavior as commands that can be executed by the context. The strategy pattern defines the different strategies as separate command objects, allowing the context to invoke them dynamically based on certain conditions or user actions.

Read More: https://www.scaler.com/topics/design-patterns/strategy-design-pattern/