TUTORIAL SERIES
Design Patterns in Python: Strategy
Dynamic Algorithm Switching
Have you encountered recurring coding challenges? Imagine having a toolbox of tried-and-true solutions readily available. That’s precisely what design patterns provide. In this series, we’ll explore what these patterns are and how they can elevate your coding skills.
Understanding the Strategy Pattern
What is the Strategy Design Pattern?
The Strategy Design Pattern is a behavioral design paradigm that encapsulates a family of interchangeable algorithms, allowing dynamic selection by a client class.
This decouples algorithmic implementation from the client, promoting flexibility and ease of modification without altering the client’s structure.
When to Use the Strategy Pattern:
Consider employing the Strategy Pattern for the following cases:
- Dynamic Algorithm Switching: We Need multiple variations of an algorithm within an object with the ability to switch during runtime.
- Behavioral Differences in Similar Classes: Dealing with numerous classes that share similarities but differ in behavior execution.
- Business Logic Isolation: Separating business logic from algorithm implementation details.
- Simplify Massive Conditional Statements: Your class has an extensive conditional statement handling different algorithm variants.
Practical Example: A Trading Strategy System
we’ll implement a trading strategy system in Python using the Strategy Design Pattern. We aim to demonstrate the adaptability and flexibility of the pattern by encapsulating different trading strategies, such as Moving Average and Mean Reversion, as interchangeable components.
Terminology and Key Components
To grasp the essence of the Strategy Design Pattern, let’s delve into its key components:
- Context: Maintains a reference to one of the concrete strategies and communicates with this object only via the strategy interface.
- Strategy Interface: Common to all concrete strategies, declares a method the context uses to execute a strategy.
- Concrete Strategies: Implement different variations of an algorithm the context uses.
Strategy Pattern Implementation in Python
Step 1: Strategy Interface
Define an abstract class Strategy
that declares a method execute_strategy
common to all concrete strategies.
from abc import ABC, abstractmethod
# Step 1: Create the Strategy Interface
class Strategy(ABC):
@abstractmethod
def execute_strategy(self):
pass
Step 2: Concrete Strategies
Implement concrete strategy classes ConcreteStrategyA
and ConcreteStrategyB
that provide specific algorithm variations.
# Step 2: Create Concrete Strategies
class ConcreteStrategyA(Strategy):
def execute_strategy(self):
return "Executing Strategy A"
class ConcreteStrategyB(Strategy):
def execute_strategy(self):
return "Executing Strategy B"
Step 3: Context
Develop a Context
class that maintains a reference to one of the concrete strategies and provides a method to execute the strategy.
# Step 3: Create the Context
class Context:
def __init__(self, strategy):
# Context maintains a reference to one of the concrete strategies
self._strategy = strategy
def set_strategy(self, strategy):
# Exposes a setter to replace the strategy
# associated with the context at runtime
self._strategy = strategy
def execute_strategy(self):
# Context calls the execution method on the linked strategy object
return self._strategy.execute_strategy()
Client
# Main Section to Showcase Usage
if __name__ == "__main__":
# Create concrete strategy objects
strategy_a = ConcreteStrategyA()
strategy_b = ConcreteStrategyB()
# Create context with a default strategy
context = Context(strategy_a)
# Execute the default strategy
print(context.execute_strategy()) # Output: Executing Strategy A
# Switch to a different strategy at runtime
context.set_strategy(strategy_b)
print(context.execute_strategy()) # Output: Executing Strategy B
GitHub Repo 🎉
Explore all code examples and design pattern implementations on GitHub!
Practical Example: A Trading Strategy System
In this section, we implement a dynamic trading strategy system in Python, showcasing the capability to switch strategies seamlessly at runtime.
Step 1: Create the Strategy Interface
# Step 1: Create the Strategy Interface
class TradingStrategy(ABC):
@abstractmethod
def execute_trade(self, data):
pass
Step 2: Create Concrete Strategies
# Step 2: Create Concrete Strategies
class MovingAverageStrategy(TradingStrategy):
def execute_trade(self, data):
# Calculate Moving Average (Simple example for illustration)
window_size = 3 # Adjust as needed
moving_average = sum(data[-window_size:]) / window_size
return f"Executing Moving Average Trading Strategy. Moving Average: {moving_average:.2f}"
class MeanReversionStrategy(TradingStrategy):
def execute_trade(self, data):
# Calculate Mean Reversion (Simple example for illustration)
mean_value = sum(data) / len(data)
deviation = data[-1] - mean_value
return f"Executing Mean Reversion Trading Strategy. Deviation from Mean: {deviation:.2f}"
Step 3: Create the Context
# Step 3: Create the Context
class TradingContext:
def __init__(self, strategy):
self._strategy = strategy
def set_strategy(self, strategy):
self._strategy = strategy
def execute_trade(self, data):
return self._strategy.execute_trade(data)
Client
# Main Section to Showcase Usage
if __name__ == "__main__":
# Sample data for trading
trading_data = [50, 55, 45, 60, 50]
# Create concrete strategy objects
moving_average_strategy = MovingAverageStrategy()
mean_reversion_strategy = MeanReversionStrategy()
# Create context with a default strategy
trading_context = TradingContext(moving_average_strategy)
# Execute the default strategy
result = trading_context.execute_trade(trading_data)
print(result) # Output: Executing Moving Average Trading Strategy. Moving Average: 51.67
# Switch to a different strategy at runtime
trading_context.set_strategy(mean_reversion_strategy)
# Execute the updated strategy
result = trading_context.execute_trade(trading_data)
print(result) # Output: Executing Mean Reversion Trading Strategy. Deviation from Mean: -1.00
Real-World Use Cases for Strategy Pattern
- Python’s
sort
function: Thesort
function in Python allows users to pass a custom comparison function, showcasing the Strategy Pattern. - Redux State Management in React: In React applications using Redux, the middleware pattern employs the Strategy Pattern for handling asynchronous actions.
- Scikit-learn Machine Learning Library in Python: Scikit-learn’s preprocessing module utilizes the Strategy Pattern for handling missing data.
- Unity Game Engine: Unity’s input system employs the Strategy Pattern for defining various input handling strategies.
- TensorFlow Machine Learning Library: In TensorFlow, optimizers like stochastic gradient descent use the Strategy Pattern for defining different optimization strategies.
Best Practices and Considerations
When employing the Strategy Design Pattern, it’s essential to be mindful of both its advantages and potential considerations:
Pros
- Runtime Algorithm Swap: The ability to switch algorithms dynamically at runtime enhances adaptability and flexibility.
- Implementation Isolation: The pattern isolates the implementation details of an algorithm, promoting cleaner and more maintainable code.
- Composition Over Inheritance: It allows replacing inheritance with composition, emphasizing a more flexible design approach.
Cons
- Programmatic Overhead: For a limited number of rarely changing algorithms, the introduction of extra classes and interfaces may lead to unnecessary complexity.
- Client Awareness: Clients need awareness of the differences between strategies to make informed selections, introducing potential cognitive load.
- Functional Alternatives: Some modern programming languages with functional support offer alternatives, implementing different algorithm versions without additional class and interface overhead.
Strategy Pattern’s Relations with Other Patterns
The Strategy Pattern shares structural similarities with other patterns such as Bridge, State, and to some extent, Adapter.
While they all utilize composition for delegating work to other objects, each pattern addresses distinct problems.
Command Pattern
Both Command and Strategy can be used to parameterize an object with an action.
Command: converts any operation into an object, allowing deferred execution, queuing, and remote command handling.
Strategy: describes different ways of doing the same thing, enabling the swapping of algorithms within a single context class.
Decorator Pattern
Both Decorator and Strategy allow altering an object’s behavior.
Decorator: changes the outer layer or “skin” of an object, enhancing its functionalities.
Strategy: focuses on changing the internal workings or “guts” of an object by providing different algorithms.
Template Method Pattern
Both Template Method and Strategy are based on altering parts of an algorithm.
Template Method: relies on inheritance to extend and alter algorithm parts in subclasses (class level).
Strategy: relies on composition to switch behaviors at runtime (object level).
State Pattern
The State pattern can be considered an extension of Strategy, both based on composition.
State: allows dependencies between concrete states, enabling them to alter the context’s state at will.
Strategy: makes objects independent and unaware of each other.
Conclusion
In wrapping up our exploration of the Strategy Pattern in Python, we’ve uncovered a pattern that brings dynamic algorithmic switches to your code. The trio of Strategy Interface, Concrete Strategies, and Context forms a pattern that allows one to change algorithms on the fly.
We covered the pattern's main components, pros and cons, and its relation and comparison to other patterns. We have also implemented a simple trading system that changes algorithms in real time to show the power of the strategy pattern.
Happy coding! 👩💻