BehavioralPythonverifiedVerified
Strategy Pattern in Python
Defines a family of algorithms, encapsulates each one, and makes them interchangeable so the algorithm can vary independently from the clients that use it.
How to Implement the Strategy Pattern in Python
1Step 1: Define the Strategy interface using Protocol
from typing import Protocol
class SortStrategy[T](Protocol):
def sort(self, data: list[T]) -> list[T]: ...2Step 2: Implement concrete strategies
class BubbleSort[T]:
def sort(self, data: list[T]) -> list[T]:
arr = list(data)
for i in range(len(arr)):
for j in range(len(arr) - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
class QuickSort[T]:
def sort(self, data: list[T]) -> list[T]:
if len(data) <= 1:
return data
pivot = data[len(data) // 2]
left = [x for x in data if x < pivot]
mid = [x for x in data if x == pivot]
right = [x for x in data if x > pivot]
return self.sort(left) + mid + self.sort(right)3Step 3: Create the Context class that uses a strategy
class Sorter[T]:
def __init__(self, strategy: SortStrategy[T]) -> None:
self._strategy = strategy
def set_strategy(self, strategy: SortStrategy[T]) -> None:
self._strategy = strategy
def sort(self, data: list[T]) -> list[T]:
return self._strategy.sort(data)4Step 4: Swap strategies at runtime
sorter: Sorter[int] = Sorter(BubbleSort())
print(sorter.sort([5, 3, 1, 4, 2])) # [1, 2, 3, 4, 5]
sorter.set_strategy(QuickSort())
print(sorter.sort([5, 3, 1, 4, 2])) # [1, 2, 3, 4, 5]"""Payment Processing with Strategy pattern and validation."""
import logging
import uuid
import time
from abc import ABC, abstractmethod
from dataclasses import dataclass
logger = logging.getLogger(__name__)
# [step] Define payment details and result types
@dataclass(frozen=True)
class PaymentDetails:
amount: float
currency: str
description: str
@dataclass(frozen=True)
class PaymentResult:
success: bool
transaction_id: str
timestamp: float
provider: str
error_message: str | None = None
class PaymentStrategy(ABC):
@property
@abstractmethod
def name(self) -> str: ...
@abstractmethod
def validate(self, details: PaymentDetails) -> list[str]: ...
@abstractmethod
async def process(self, details: PaymentDetails) -> PaymentResult: ...
# [step] Implement concrete payment strategies
class StripeStrategy(PaymentStrategy):
SUPPORTED_CURRENCIES = {"USD", "EUR", "GBP"}
def __init__(self, api_key: str) -> None:
self._api_key = api_key
@property
def name(self) -> str:
return "stripe"
def validate(self, details: PaymentDetails) -> list[str]:
errors: list[str] = []
if details.amount <= 0:
errors.append("Amount must be positive")
if details.currency not in self.SUPPORTED_CURRENCIES:
errors.append(f"Currency {details.currency} not supported by Stripe")
return errors
async def process(self, details: PaymentDetails) -> PaymentResult:
return PaymentResult(
success=True,
transaction_id=f"stripe_{uuid.uuid4().hex[:12]}",
timestamp=time.time(),
provider=self.name,
)
class PayPalStrategy(PaymentStrategy):
@property
def name(self) -> str:
return "paypal"
def validate(self, details: PaymentDetails) -> list[str]:
errors: list[str] = []
if details.amount < 1:
errors.append("PayPal minimum is $1.00")
return errors
async def process(self, details: PaymentDetails) -> PaymentResult:
return PaymentResult(
success=True,
transaction_id=f"pp_{uuid.uuid4().hex[:12]}",
timestamp=time.time(),
provider=self.name,
)
# [step] Implement the PaymentProcessor context
class PaymentProcessor:
def __init__(self, strategy: PaymentStrategy) -> None:
self._strategy = strategy
def set_strategy(self, strategy: PaymentStrategy) -> None:
self._strategy = strategy
async def charge(self, details: PaymentDetails) -> PaymentResult:
errors = self._strategy.validate(details)
if errors:
return PaymentResult(
success=False,
transaction_id="",
timestamp=time.time(),
provider=self._strategy.name,
error_message="; ".join(errors),
)
return await self._strategy.process(details)Strategy Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Strategy Pattern in the Real World
“Consider a GPS app offering route options: fastest, shortest, or avoid tolls. The destination is the same, but the navigation algorithm changes based on your preference. The app (context) simply hands the journey off to whichever routing strategy you selected; you can switch strategies mid-trip without the app needing to change its structure.”