Strategy Pattern

====================

Introduction


The Strategy pattern is a Behavioral Design Pattern that allows you to define a family of algorithms, encapsulate each one as a separate class, and make them Interchangeable at runtime. It defines an Interface for algorithms and allows subclasses to select which Algorithm to use based on the Context.

How it Works


The Strategy pattern works by creating a common Interface or Abstract Base Class (ABC) that defines the basic strategy. This ABC has methods that describe the Behavior of each different strategy. A client, however, does not know about the Concrete Strategies and only knows how to use the abstract ABC.

The Concrete Strategies are then implemented as separate classes, which implement the ABC. These classes have instances of the Strategy ABC, and when an Instance is created, it can be swapped out with a new one at runtime using Polymorphism.

Advantages


  • Decoupling: The Strategy pattern helps to decouple different algorithms from each other.
  • Testability: It makes code more testable by providing a common Interface for Testing different Strategies.
  • Flexibility: It allows you to change the strategy at runtime, which can be useful in Scenarios where the Algorithm might need to adapt to changing requirements.

Disadvantages


  • Added Complexity: The Strategy pattern adds complexity to the codebase due to the creation of an additional Interface and Implementation.
  • Increased Memory Usage: It may require more memory if you have many different Strategies, as each one requires its own Instance of the strategy ABC.

Implementing the Strategy Pattern


Here is a basic example of how you might implement the Strategy pattern in Python:

from [ABC](/ABC) import [ABC](/ABC), abstractmethod

# Define an [Interface](/Interface) for algorithms
class Strategy([ABC](/ABC)):
    @abstractmethod
    def operation(self):
        pass


# <a href="/Concrete_Strategy" class="missing-article"><a href="/Concrete" class="missing-article">Concrete</a> Strategy</a> classes
class ConcreteStrategy1(Strategy):
    def operation(self):
        print("<a href="/Concrete_Strategy" class="missing-article"><a href="/Concrete" class="missing-article">Concrete</a> Strategy</a> 1")


class ConcreteStrategy2(Strategy):
    def operation(self):
        print("<a href="/Concrete_Strategy" class="missing-article"><a href="/Concrete" class="missing-article">Concrete</a> Strategy</a> 2")


# Client code that uses the abstract [Interface](/Interface)
def get_strategy():
    [Strategies](/Strategies) = {
        "strategy1": ConcreteStrategy1(),
        "strategy2": ConcreteStrategy2()
    }
    return [Strategies](/Strategies)["strategy1"]


# Usage example
if __name__ == "__main__":
    strategy = get_strategy()

    # Use different [Strategies](/Strategies)
    print(strategy.operation())  # Output: <a href="/Concrete_Strategy" class="missing-article"><a href="/Concrete" class="missing-article">Concrete</a> Strategy</a> 1

Using the Strategy Pattern with Multiple Algorithms


The strategy pattern can be applied to any scenario where you need to define a family of algorithms, encapsulate each one as a separate class, and make them Interchangeable at runtime.

Here is an example of how you might use the Strategy pattern with multiple algorithms:

from [ABC](/ABC) import [ABC](/ABC), abstractmethod

# Define an [Interface](/Interface) for algorithms
class <a href="/Algorithm" class="missing-article">Algorithm</a>([ABC](/ABC)):
    @abstractmethod
    def execute(self):
        pass


# <a href="/Concrete" class="missing-article">Concrete</a> <a href="/Algorithm" class="missing-article">Algorithm</a> classes
class ConcreteAlgorithm1(<a href="/Algorithm" class="missing-article">Algorithm</a>):
    def execute(self):
        print("<a href="/Concrete" class="missing-article">Concrete</a> <a href="/Algorithm" class="missing-article">Algorithm</a> 1")


class ConcreteAlgorithm2(<a href="/Algorithm" class="missing-article">Algorithm</a>):
    def execute(self):
        print("<a href="/Concrete" class="missing-article">Concrete</a> <a href="/Algorithm" class="missing-article">Algorithm</a> 2")


# Strategy [Interface](/Interface) with multiple algorithms
class StrategyInterface([ABC](/ABC)):
    @abstractmethod
    def get_algorithm(self):
        pass


# Client code that uses the abstract strategy [Interface](/Interface)
def get_algorithm(algorithm_interface: StrategyInterface) -> "<a href="/Algorithm" class="missing-article">Algorithm</a>":
    [Strategies](/Strategies) = {
        "algorithm1": ConcreteAlgorithm1(),
        "algorithm2": ConcreteAlgorithm2()
    }
    <a href="/Algorithm" class="missing-article">Algorithm</a> = [Strategies](/Strategies).get_algorithm(algorithm_interface)
    return <a href="/Algorithm" class="missing-article">Algorithm</a>


# Usage example
if __name__ == "__main__":
    strategy = get_algorithm(StrategyInterface("strategy1"))

    # Use different algorithms
    print(strategy.execute())  # Output: <a href="/Concrete" class="missing-article">Concrete</a> <a href="/Algorithm" class="missing-article">Algorithm</a> 1

Real-World Example


The Strategy pattern is commonly used in software development to implement complex algorithms that require multiple steps or decisions. For example, a payment processing system might use the strategy pattern to implement different payment methods (e.g., credit card, PayPal) with their respective algorithmic approaches.

Here is an example of how you might apply the Strategy pattern to a real-world scenario:

from [ABC](/ABC) import [ABC](/ABC), abstractmethod

# Define an [Interface](/Interface) for algorithms
class PaymentStrategy([ABC](/ABC)):
    @abstractmethod
    def process_payment(self):
        pass


# <a href="/Concrete" class="missing-article">Concrete</a> payment strategy classes
class CreditCardPaymentStrategy(PaymentStrategy):
    def process_payment(self):
        print("Processing credit card payment")


class PayPalPaymentStrategy(PaymentStrategy):
    def process_payment(self):
        print("Processing PayPal payment")


# Client code that uses the abstract payment strategy [Interface](/Interface)
def get_payment_strategy(payment_strategy: PaymentStrategy) -> "PaymentStrategy":
    [Strategies](/Strategies) = {
        "payment1": CreditCardPaymentStrategy(),
        "payment2": PayPalPaymentStrategy()
    }
    return [Strategies](/Strategies).get_payment_strategy(payment_strategy)


# Usage example
if __name__ == "__main__":
    credit_card_payment_strategy = get_payment_strategy(CreditCardPaymentStrategy())
    paypal_payment_strategy = get_payment_strategy(PayPalPaymentStrategy())

    # Use different payment methods
    credit_card_payment_strategy.process_payment()  # Output: Processing credit card payment

    paypal_payment_strategy.process_payment()  # Output: Processing PayPal payment

Conclusion


The Strategy pattern is a powerful Design Pattern that allows you to define a family of algorithms, encapsulate each one as a separate class, and make them Interchangeable at runtime. Its flexibility, testability, and maintainability make it an attractive choice for complex algorithmic Scenarios in software development.