← Назад к вопросам
Как реализовать паттерн стратегия в Python?
2.0 Middle🔥 111 комментариев
#Python Core#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как реализовать паттерн стратегия в Python?
Паттерн Strategy (Стратегия) — это поведенческий паттерн проектирования, который позволяет определить семейство алгоритмов, инкапсулировать каждый из них и сделать их взаимозаменяемыми. Это один из наиболее полезных паттернов для избежания больших блоков if-else.
Базовая реализация через наследование
1. Классический подход с ABC (Abstract Base Classes)
from abc import ABC, abstractmethod
# Абстрактный базовый класс - определяет интерфейс стратегии
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount: float) -> bool:
"""Выполнить платёж"""
pass
# Конкретные стратегии
class CreditCardPayment(PaymentStrategy):
def __init__(self, card_number: str):
self.card_number = card_number
def pay(self, amount: float) -> bool:
print(f"Processing credit card payment: ${amount}")
print(f"Card: {self.card_number[-4:]} (****)")
return True
class PayPalPayment(PaymentStrategy):
def __init__(self, email: str):
self.email = email
def pay(self, amount: float) -> bool:
print(f"Processing PayPal payment: ${amount}")
print(f"Account: {self.email}")
return True
class CryptoCurrencyPayment(PaymentStrategy):
def __init__(self, wallet_address: str):
self.wallet_address = wallet_address
def pay(self, amount: float) -> bool:
print(f"Processing cryptocurrency payment: ${amount}")
print(f"Wallet: {self.wallet_address[:10]}...")
return True
# Контекст - использует стратегию
class PaymentProcessor:
def __init__(self, strategy: PaymentStrategy):
self.strategy = strategy
def checkout(self, amount: float) -> bool:
"""Выполнить платёж используя текущую стратегию"""
return self.strategy.pay(amount)
def set_payment_strategy(self, strategy: PaymentStrategy):
"""Изменить стратегию в рантайме"""
self.strategy = strategy
# Использование
processor = PaymentProcessor(CreditCardPayment("1234567890123456"))
processor.checkout(99.99) # Output: Processing credit card payment: $99.99
# Изменение стратегии во время выполнения
processor.set_payment_strategy(PayPalPayment("user@example.com"))
processor.checkout(49.99) # Output: Processing PayPal payment: $49.99
processor.set_payment_strategy(CryptoCurrencyPayment("0x742d35Cc6634C0532925a3b844Bc9e7595f"))
processor.checkout(29.99) # Output: Processing cryptocurrency payment: $29.99
Реализация через callable объекты (более pythonic)
2. Стратегии как функции
from typing import Callable, Protocol
from enum import Enum
# Определить сигнатуру стратегии
class PaymentProcessor:
def __init__(self, payment_func: Callable[[float], bool]):
self.payment_func = payment_func
def checkout(self, amount: float) -> bool:
return self.payment_func(amount)
# Стратегии как функции
def credit_card_payment(amount: float) -> bool:
print(f"Credit card payment: ${amount}")
return True
def paypal_payment(amount: float) -> bool:
print(f"PayPal payment: ${amount}")
return True
def crypto_payment(amount: float) -> bool:
print(f"Crypto payment: ${amount}")
return True
# Использование
processor = PaymentProcessor(credit_card_payment)
processor.checkout(99.99)
processor = PaymentProcessor(paypal_payment)
processor.checkout(49.99)
Использование Protocol для типизации
3. Структурная типизация (duck typing с типами)
from typing import Protocol
class PaymentStrategy(Protocol):
"""Любой объект с методом pay() считается PaymentStrategy"""
def pay(self, amount: float) -> bool:
...
class CreditCard:
def pay(self, amount: float) -> bool:
print(f"Credit card: ${amount}")
return True
class Bitcoin:
def pay(self, amount: float) -> bool:
print(f"Bitcoin: ${amount}")
return True
class PaymentProcessor:
def __init__(self, strategy: PaymentStrategy):
self.strategy = strategy
def process_payment(self, amount: float) -> bool:
return self.strategy.pay(amount)
# Работает без явного наследования!
processor = PaymentProcessor(CreditCard())
processor.process_payment(100)
processor = PaymentProcessor(Bitcoin())
processor.process_payment(50)
Стратегии с состоянием и конфигурацией
4. Расширенный пример: стратегии сжатия данных
from abc import ABC, abstractmethod
import gzip
import bz2
import lzma
class CompressionStrategy(ABC):
@abstractmethod
def compress(self, data: bytes) -> bytes:
"""Сжать данные"""
pass
@abstractmethod
def decompress(self, data: bytes) -> bytes:
"""Распаковать данные"""
pass
@abstractmethod
def get_compression_ratio(self) -> float:
"""Вернуть ratio сжатия"""
pass
class GZipCompression(CompressionStrategy):
def compress(self, data: bytes) -> bytes:
return gzip.compress(data)
def decompress(self, data: bytes) -> bytes:
return gzip.decompress(data)
def get_compression_ratio(self) -> float:
return 0.7 # Обычный ratio для текста
class BZ2Compression(CompressionStrategy):
def compress(self, data: bytes) -> bytes:
return bz2.compress(data)
def decompress(self, data: bytes) -> bytes:
return bz2.decompress(data)
def get_compression_ratio(self) -> float:
return 0.65 # Чуть лучше, чем gzip
class LZMACompression(CompressionStrategy):
def compress(self, data: bytes) -> bytes:
return lzma.compress(data)
def decompress(self, data: bytes) -> bytes:
return lzma.decompress(data)
def get_compression_ratio(self) -> float:
return 0.6 # Лучшее сжатие
# Архиватор с выбираемой стратегией
class Archiver:
def __init__(self, strategy: CompressionStrategy):
self._strategy = strategy
def set_strategy(self, strategy: CompressionStrategy):
self._strategy = strategy
def compress_file(self, filename: str) -> bytes:
with open(filename, 'rb') as f:
data = f.read()
compressed = self._strategy.compress(data)
print(f"Compressed {filename} using {self._strategy.__class__.__name__}")
print(f"Original: {len(data)} bytes")
print(f"Compressed: {len(compressed)} bytes")
print(f"Ratio: {len(compressed) / len(data) * 100:.1f}%")
return compressed
# Использование
archiver = Archiver(GZipCompression())
data = archiver.compress_file("large_file.txt")
# Переключение стратегии
archiver.set_strategy(LZMACompression())
data = archiver.compress_file("large_file.txt")
Фабрика стратегий
5. Создание стратегий через фабрику
from typing import Dict, Type
from enum import Enum
class SortingStrategy(ABC):
@abstractmethod
def sort(self, data: list) -> list:
pass
class BubbleSort(SortingStrategy):
def sort(self, data: list) -> list:
arr = data[:]
n = len(arr)
for i in range(n):
for j in range(0, n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
class QuickSort(SortingStrategy):
def sort(self, data: list) -> list:
if len(data) <= 1:
return data
pivot = data[len(data) // 2]
left = [x for x in data if x < pivot]
middle = [x for x in data if x == pivot]
right = [x for x in data if x > pivot]
return self.sort(left) + middle + self.sort(right)
class SortingAlgorithm(Enum):
BUBBLE = "bubble"
QUICK = "quick"
# Фабрика
class SortingStrategyFactory:
_strategies: Dict[SortingAlgorithm, Type[SortingStrategy]] = {
SortingAlgorithm.BUBBLE: BubbleSort,
SortingAlgorithm.QUICK: QuickSort,
}
@classmethod
def create(cls, algorithm: SortingAlgorithm) -> SortingStrategy:
strategy_class = cls._strategies.get(algorithm)
if not strategy_class:
raise ValueError(f"Unknown algorithm: {algorithm}")
return strategy_class()
# Использование
class DataProcessor:
def __init__(self, algorithm: SortingAlgorithm):
self.strategy = SortingStrategyFactory.create(algorithm)
def process(self, data: list) -> list:
return self.strategy.sort(data)
processor = DataProcessor(SortingAlgorithm.QUICK)
result = processor.process([5, 2, 8, 1, 9])
print(result) # [1, 2, 5, 8, 9]
Стратегии для обработки платежей с логированием
6. Декораторы и стратегии
import functools
from datetime import datetime
def log_payment(func):
@functools.wraps(func)
def wrapper(self, amount: float) -> bool:
print(f"[{datetime.now().isoformat()}] Starting payment: ${amount}")
result = func(self, amount)
status = "SUCCESS" if result else "FAILED"
print(f"[{datetime.now().isoformat()}] Payment {status}: ${amount}")
return result
return wrapper
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount: float) -> bool:
pass
class AdvancedCreditCard(PaymentStrategy):
def __init__(self, card_number: str, cvv: str):
self.card_number = card_number
self.cvv = cvv
@log_payment
def pay(self, amount: float) -> bool:
# Симуляция обработки платежа
print(f"Charging card: {self.card_number[-4:]}")
return True
class SecurePayPal(PaymentStrategy):
def __init__(self, email: str, password: str):
self.email = email
self.password = password
@log_payment
def pay(self, amount: float) -> bool:
print(f"PayPal transaction: {self.email}")
return True
# Использование
strategy = AdvancedCreditCard("4532015112830366", "123")
strategy.pay(150.00)
Когда использовать Strategy
"""
Strategy эффективна когда:
1. Много условных ветвлений с похожей логикой
БЫЛО:
if payment_type == 'card': process_card(...)
elif payment_type == 'paypal': process_paypal(...)
elif payment_type == 'crypto': process_crypto(...)
СТАЛО:
processor = PaymentProcessor(strategy)
processor.checkout(amount)
2. Необходимо переключаться между алгоритмами в рантайме
3. Избежать дублирования кода
4. Облегчить тестирование (можно мокировать стратегии)
5. Добавлять новые алгоритмы без изменения существующего кода
Плюсы:
- Изолирует логику алгоритма от клиента
- Облегчает добавление новых алгоритмов
- Упрощает тестирование
- Избегает множества условных операторов
Минусы:
- Может быть избыточной для простых случаев
- Добавляет дополнительные классы
- Клиент должен знать о всех стратегиях
"""