← Назад к вопросам

Как реализовать паттерн стратегия в 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. Добавлять новые алгоритмы без изменения существующего кода

Плюсы:
- Изолирует логику алгоритма от клиента
- Облегчает добавление новых алгоритмов
- Упрощает тестирование
- Избегает множества условных операторов

Минусы:
- Может быть избыточной для простых случаев
- Добавляет дополнительные классы
- Клиент должен знать о всех стратегиях
"""