Python Strategy Pattern
The Strategy Pattern is a behavioral design pattern that allows you to choose an algorithm or behavior at runtime. Simply put, the Strategy Pattern encapsulates different algorithms into independent classes, allowing them to replace each other.
Core Idea
Imagine you go to a restaurant to order food: you can choose different payment methods - cash, credit card, mobile payment, etc. No matter which payment method you choose, you can eventually complete the payment, but the specific payment process is different. This is a real-world example of the Strategy Pattern.
Design Principles
The Strategy Pattern follows the following important principles:
- Open-Closed Principle: Open for extension, closed for modification.
- Single Responsibility Principle: Each strategy class is responsible for only one algorithm.
- Dependency Inversion Principle: Depend on abstractions, not on concrete implementations.
Strategy Pattern Structure
Let's understand the composition of the Strategy Pattern through a UML class diagram:

Component Description
Context
- Maintains a reference to a strategy object.
- Can dynamically switch strategies.
- Delegates work to the strategy object.
Strategy (Strategy Interface)
- Defines the common interface for all supported algorithms.
- Declares the method for executing the algorithm.
ConcreteStrategy
- Implements the strategy interface.
- Provides concrete algorithm implementation.
Basic Syntax and Implementation
Strategy Interface Definition
In Python, we can use an abstract base class (ABC) to define the strategy interface:
Example
class PaymentStrategy(ABC):
"""Payment Strategy Abstract Base Class"""
@abstractmethod
def pay(self, amount: float) -> bool:
"""Payment Method"""
pass
Concrete Strategy Implementation
Example
"""Credit Card Payment Strategy"""
def __init__(self, card_number: str, expiry_date: str, cvv: str):
self.card_number = card_number
self.expiry_date = expiry_date
self.cvv = cvv
def pay(self, amount: float) -> bool:
print(f"Pay {amount} yuan with credit card")
print(f"Card Number: {self.card_number}")
# Actual payment logic should be here
return True
class AlipayPayment(PaymentStrategy):
"""Alipay Payment Strategy"""
def __init__(self, account: str):
self.account = account
def pay(self, amount: float) -> bool:
print(f"Pay {amount} yuan with Alipay")
print(f"Alipay Account: {self.account}")
# Actual payment logic should be here
return True
class WechatPayment(PaymentStrategy):
"""WeChat Pay Strategy"""
def __init__(self, openid: str):
self.openid = openid
def pay(self, amount: float) -> bool:
print(f"Pay {amount} yuan with WeChat Pay")
print(f"WeChat OpenID: {self.openid}")
# Actual payment logic should be here
return True
Context Class Implementation
Example
"""Payment Context Class"""
def __init__(self, strategy: PaymentStrategy = None):
self._strategy = strategy
def set_strategy(self, strategy: PaymentStrategy):
"""Set Payment Strategy"""
self._strategy = strategy
def execute_payment(self, amount: float) -> bool:
"""Execute Payment"""
if not self._strategy:
raise ValueError("No payment strategy set")
return self._strategy.pay(amount)
Complete Example: E-commerce Payment System
Let's demonstrate the practical application of the Strategy Pattern through a complete e-commerce payment system:
Example
from typing import Dict, Any
# Strategy interface
class DiscountStrategy(ABC):
"""Discount Strategy Interface"""
@abstractmethod
def calculate_discount(self, original_price: float) -> float:
"""Calculate price after discount"""
pass
# Concrete strategy implementations
class NoDiscountStrategy(DiscountStrategy):
"""No Discount Strategy"""
def calculate_discount(self, original_price: float) -> float:
return original_price
class PercentageDiscountStrategy(DiscountStrategy):
"""Percentage Discount Strategy"""
def __init__(self, percentage: float):
if not 0 <= percentage <= 100:
raise ValueError("Discount percentage must be between 0-100")
self.percentage = percentage
def calculate_discount(self, original_price: float) -> float:
discount_amount = original_price * (self.percentage / 100)
return original_price - discount_amount
class FixedAmountDiscountStrategy(DiscountStrategy):
"""Fixed amount discount strategy"""
def __init__(self, discount_amount: float):
if discount_amount < 0:
raise ValueError("Discount amount cannot be negative")
self.discount_amount = discount_amount
def calculate_discount(self, original_price: float) -> float:
return max(0, original_price - self.discount_amount)
class SeasonalDiscountStrategy(DiscountStrategy):
"""Seasonal discount strategy"""
def __init__(self, base_discount: float, seasonal_multiplier: float):
self.base_discount = base_discount
self.seasonal_multiplier = seasonal_multiplier
def calculate_discount(self, original_price: float) -> float:
total_discount = self.base_discount * self.seasonal_multiplier
return max(0, original_price - total_discount)
# Context class
class ShoppingCart:
"""Shopping cart class"""
def __init__(self):
self.items = []
self._discount_strategy = NoDiscountStrategy()
def add_item(self, item: str, price: float):
"""Add item"""
self.items.append({"item": item, "price": price})
def set_discount_strategy(self, strategy: DiscountStrategy):
"""Set discount strategy"""
self._discount_strategy = strategy
def calculate_total(self) -> float:
"""Calculate total price"""
total = sum(item["price"] for item in self.items)
return self._discount_strategy.calculate_discount(total)
def display_cart(self):
"""Display cart contents"""
print("Cart contents:")
for item in self.items:
print(f" - {item['item']}: {item['price']} yuan")
original_total = sum(item["price"] for item in self.items)
final_total = self.calculate_total()
print(f"Original price: {original_total} yuan")
print(f"Discounted price: {final_total} yuan")
if original_total != final_total:
discount = original_total - final_total
print(f"Savings: {discount} yuan")
# Usage example
def main():
# Create shopping cart
cart = ShoppingCart()
# Add items
cart.add_item("Python programming book", 89.0)
cart.add_item("Wireless mouse", 129.0)
cart.add_item("Mechanical keyboard", 399.0)
print("=== No discount ===")
cart.set_discount_strategy(NoDiscountStrategy())
cart.display_cart()
print("\n"=== 20% off ===")
cart.set_discount_strategy(PercentageDiscountStrategy(20)) # 20% off
cart.display_cart()
print("\n"=== Threshold discount (50 yuan off) ===")
cart.set_discount_strategy(FixedAmountDiscountStrategy(50))
cart.display_cart()
print("\n"=== Seasonal discount ===")
cart.set_discount_strategy(SeasonalDiscountStrategy(30, 1.5)) # Base discount 30, seasonal coefficient 1.5
cart.display_cart()
if __name__ == "__main__":
main()
Running the above code, you will see the following output:
=== 无折扣 === 购物车内容: - Python编程书: 89.0元 - 无线鼠标: 129.0元 - 机械键盘: 399.0元 原价: 617.0元 折后价: 617.0元 === 8折优惠 === 购物车内容: - Python编程书: 89.0元 - 无线鼠标: 129.0元 - 机械键盘: 399.0元 原价: 617.0元 折后价: 493.6元 节省: 123.4元 === 满减优惠(减50元)=== 购物车内容: - Python编程书: 89.0元 - 无线鼠标: 129.0元 - 机械键盘: 399.0元 原价: 617.0元 折后价: 567.0元 节省: 50.0元 === 季节性优惠 === 购物车内容: - Python编程书: 89.0元 - 无线鼠标: 129.0元 - 机械键盘: 399.0元 原价: 617.0元 折后价: 572.0元 节省: 45.0元
Advanced Usage of the Strategy Pattern
1. Strategy Factory Pattern
Combine the factory pattern to manage the creation of strategies:
Example
"""Discount strategy factory"""
@staticmethod
def create_strategy(strategy_type: str, **kwargs) -> DiscountStrategy:
"""Create discount strategy"""
strategies = {
"no_discount": NoDiscountStrategy,
"percentage": PercentageDiscountStrategy,
"fixed_amount": FixedAmountDiscountStrategy,
"seasonal": SeasonalDiscountStrategy
}
if strategy_type not in strategies:
raise ValueError(f"Unsupported strategy type: {strategy_type}")
return strategies[strategy_type](**kwargs)
# Use factory pattern
factory = DiscountStrategyFactory()
# Create different strategies
strategy1 = factory.create_strategy("percentage", percentage=15) # 15% off
strategy2 = factory.create_strategy("fixed_amount", discount_amount=100) # 100 yuan off
2. Dynamic Strategy Selection
Dynamically select strategies based on conditions:
Example
"""Dynamic discount selector"""
@staticmethod
def select_strategy(user_type: str, total_amount: float) -> DiscountStrategy:
"""Select strategy based on user type and total amount"""
if user_type == "vip":
if total_amount > 500:
return PercentageDiscountStrategy(25) # VIP: 25% off on orders of 500 or more
else:
return PercentageDiscountStrategy(15) # VIP: 15% off
elif user_type == "normal":
if total_amount > 300:
return FixedAmountDiscountStrategy(30) # Regular users: 30 yuan off on orders of 300 or more
else:
return NoDiscountStrategy()
else:
return NoDiscountStrategy()
# Use dynamic selection
cart = ShoppingCart()
cart.add_item("Product A", 200)
cart.add_item("Product B", 150)
selector = DynamicDiscountSelector()
strategy = selector.select_strategy("vip", cart.calculate_total())
cart.set_discount_strategy(strategy)
cart.display_cart()
Advantages and Applicable Scenarios of the Strategy Pattern
Advantage Comparison
| Feature | Traditional Approach | Strategy Pattern |
|---|---|---|
| Extensibility | Requires modifying existing code | Simply add a new strategy class |
| Maintainability | High code coupling | Separation of responsibilities, easy to maintain |
| Flexibility | Difficult to switch algorithms at runtime | Can dynamically switch strategies |
| Testability | Difficult to test algorithms in isolation | Each strategy can be tested independently |
Applicable Scenarios
- Multiple algorithm variants: When you have several similar classes that differ only in certain behaviors.
- Avoid conditional statements: When you want to avoid using a large number of conditional statements (if-else or switch).
- Runtime algorithm selection: When you need to choose different algorithms at runtime.
- Algorithm encapsulation: When you want to isolate algorithm details from the clients that use the algorithm.
Inapplicable Scenarios
- Simple algorithms: If there are only one or two algorithms that rarely change, it may be over-engineering.
- Client needs to know strategy details: If the client must know the concrete implementation of the strategy.
- Too many strategies: When the number of strategy classes explodes, consider other patterns.
Best Practices and Considerations
Code Organization Suggestions
Example
project/
├── strategies/
│ ├── __init__.py
│ ├── base_strategy.py # Base strategy interface
│ ├── discount_strategies.py # Discount-related strategies
│ └── payment_strategies.py # Payment-related strategies
├── contexts/
│ ├── __init__.py
│ └── shopping_cart.py # Context class
└── main.py
Error Handling
Example
"""Discount strategy with error handling"""
def __init__(self, base_strategy: DiscountStrategy, fallback_strategy: DiscountStrategy = None):
self.base_strategy = base_strategy
self.fallback_strategy = fallback_strategy or NoDiscountStrategy()
def calculate_discount(self, original_price: float) -> float:
try:
return self.base_strategy.calculate_discount(original_price)
except Exception as e:
print(f"Discount calculation error: {e}, using fallback strategy")
return self.fallback_strategy.calculate_discount(original_price)
Performance Considerations
For performance-sensitive scenarios, consider the following optimizations:
Example
class CachedDiscountStrategy(DiscountStrategy):
"""Discount strategy with caching"""
def __init__(self, base_strategy: DiscountStrategy):
self.base_strategy = base_strategy
@lru_cache(maxsize=128)
def calculate_discount(self, original_price: float) -> float:
return self.base_strategy.calculate_discount(original_price)
Practical Exercises
Exercise 1: Sorting Strategy Implementation
Implement a sorter that supports multiple sorting algorithms:
Example
from typing import List
class SortStrategy(ABC):
@abstractmethod
def sort(self, data: List) -> List:
pass
# TODO: Implement bubble sort strategy
class BubbleSortStrategy(SortStrategy):
def sort(self, data: List) -> List:
# Your implementation code
pass
# TODO: Implement quick sort strategy
class QuickSortStrategy(SortStrategy):
def sort(self, data: List) -> List:
# Your implementation code
pass
# TODO: Implement merge sort strategy
class MergeSortStrategy(SortStrategy):
def sort(self, data: List) -> List:
# Your implementation code
pass
class Sorter:
def __init__(self, strategy: SortStrategy = None):
self._strategy = strategy
def set_strategy(self, strategy: SortStrategy):
self._strategy = strategy
def sort_data(self, data: List) -> List:
if not self._strategy:
raise ValueError("Sorting strategy not set")
return self._strategy.sort(data)
# Test your implementation
data = [64, 34, 25, 12, 22, 11, 90]
sorter = Sorter(BubbleSortStrategy())
result = sorter.sort_data(data)
print(f"Sorting result: {result}")
Exercise 2: File Compression Strategy
Design a file compressor that supports multiple compression formats:
Example
class CompressionStrategy(ABC):
@abstractmethod
def compress(self, file_path: str) -> str:
pass
@abstractmethod
def decompress(self, file_path: str) -> str:
pass
# TODO: Implement ZIP compression strategy
class ZipCompressionStrategy(CompressionStrategy):
def compress(self, file_path: str) -> str:
# Your implementation code
pass
def decompress(self, file_path: str) -> str:
# Your implementation code
pass
# TODO: Implement GZIP compression strategy
class GzipCompressionStrategy(CompressionStrategy):
def compress(self, file_path: str) -> str:
# Your implementation code
pass
def decompress(self, file_path: str) -> str:
# Your implementation code
pass
class FileCompressor:
def __init__(self, strategy: CompressionStrategy = None):
self._strategy = strategy
def set_strategy(self, strategy: CompressionStrategy):
self._strategy = strategy
def compress_file(self, file_path: str) -> str:
if not self._strategy:
raise ValueError("Compression strategy not set")
return self._strategy.compress(file_path)
def decompress_file(self, file_path: str) -> str:
if not self._strategy:
raise ValueError("Compression strategy not set")
return self._strategy.decompress(file_path)
Summary
The Strategy pattern is a very practical design pattern in Python. By encapsulating algorithms into independent strategy classes, it provides good extensibility and flexibility. After studying this article, you should be able to:
- Understand the core concepts and applicable scenarios of the Strategy pattern
- Master the basic implementation methods of the Strategy pattern
- Apply the Strategy pattern appropriately in real projects
- Avoid common misuses of the Strategy pattern