Какие плюсы и минусы у паттерна стратегия?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Strategy Pattern: Плюсы и минусы
Pattern Strategy (Стратегия) — это поведенческий паттерн проектирования, который позволяет выбирать алгоритм во время выполнения программы. Он инкапсулирует различные алгоритмы в отдельные классы.
Структура паттерна Strategy
from abc import ABC, abstractmethod
# Интерфейс стратегии
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount: float) -> bool:
pass
# Конкретные стратегии
class CreditCardPayment(PaymentStrategy):
def __init__(self, card_number, cvv):
self.card_number = card_number
self.cvv = cvv
def pay(self, amount: float) -> bool:
print(f"Processing credit card payment: ${amount}")
return True
class PayPalPayment(PaymentStrategy):
def __init__(self, email):
self.email = email
def pay(self, amount: float) -> bool:
print(f"Processing PayPal payment: ${amount}")
return True
class CryptoPayment(PaymentStrategy):
def __init__(self, wallet_address):
self.wallet_address = wallet_address
def pay(self, amount: float) -> bool:
print(f"Processing crypto payment: ${amount}")
return True
# Контекст, использующий стратегию
class ShoppingCart:
def __init__(self):
self.items = []
self.payment_strategy = None
def add_item(self, item, price):
self.items.append({"item": item, "price": price})
def set_payment_strategy(self, strategy: PaymentStrategy):
self.payment_strategy = strategy
def checkout(self):
total = sum(item["price"] for item in self.items)
if self.payment_strategy:
return self.payment_strategy.pay(total)
return False
# Использование
cart = ShoppingCart()
cart.add_item("Laptop", 1000)
cart.add_item("Mouse", 30)
# Выбираем стратегию во время выполнения
cart.set_payment_strategy(CreditCardPayment("1234-5678-9012", "123"))
cart.checkout() # Processing credit card payment: $1030
# Меняем стратегию
cart.set_payment_strategy(PayPalPayment("user@example.com"))
cart.checkout() # Processing PayPal payment: $1030
Плюсы паттерна Strategy
1. Избегание множественных условных операторов (if/else)
Проблема без паттерна:
# Без Strategy
class PaymentProcessor:
def process_payment(self, payment_type, amount, payment_data):
if payment_type == "credit_card":
# 50 строк кода обработки кредитной карты
pass
elif payment_type == "paypal":
# 50 строк кода обработки PayPal
pass
elif payment_type == "crypto":
# 50 строк кода обработки крипто
pass
elif payment_type == "bank_transfer":
# 50 строк кода для банковского перевода
pass
# ... ещё 20 условий
# Функция раздувается до 500+ строк!
Решение с Strategy:
# С Strategy
class PaymentProcessor:
def process_payment(self, strategy: PaymentStrategy, amount):
return strategy.pay(amount) # Просто одна строка!
Результат: Код более читаемый и чистый
2. Открыт/Закрыт принцип (Open/Closed Principle)
Преимущество: Легко добавлять новые алгоритмы без изменения существующего кода
# Новая стратегия через 2 года
class ApplePayPayment(PaymentStrategy):
def __init__(self, apple_id):
self.apple_id = apple_id
def pay(self, amount: float) -> bool:
print(f"Processing Apple Pay: ${amount}")
return True
# PaymentProcessor не требует изменений!
cart.set_payment_strategy(ApplePayPayment("user@icloud.com"))
cart.checkout() # Работает сразу!
Результат: Проще поддерживать код и добавлять функционал
3. Динамический выбор алгоритма
Преимущество: Выбор алгоритма во время выполнения, а не на этапе компиляции
class PaymentProcessor:
@staticmethod
def get_strategy(payment_method: str) -> PaymentStrategy:
strategies = {
"credit_card": CreditCardPayment,
"paypal": PayPalPayment,
"crypto": CryptoPayment,
}
return strategies.get(payment_method)
# Выбор основан на пользовательском вводу
user_choice = input("Choose payment method: ")
strategy_class = PaymentProcessor.get_strategy(user_choice)
strategy = strategy_class(...)
cart.set_payment_strategy(strategy)
4. Логика алгоритма инкапсулирована
Преимущество: Каждый алгоритм находится в своем классе, отделён от других
# Каждая стратегия несёт только свою ответственность
class BitcoinPayment(PaymentStrategy):
# Только логика Bitcoin платежей
def pay(self, amount: float) -> bool:
# Проверка адреса
# Проверка комиссии
# Отправка транзакции
# Логирование
pass
class EthereumPayment(PaymentStrategy):
# Только логика Ethereum платежей
def pay(self, amount: float) -> bool:
# Другая логика
pass
5. Упрощает тестирование
Преимущество: Легко тестировать каждую стратегию отдельно
import pytest
from unittest.mock import Mock
class TestPaymentStrategies:
def test_credit_card_payment(self):
strategy = CreditCardPayment("1234-5678-9012", "123")
assert strategy.pay(100) == True
def test_paypal_payment(self):
strategy = PayPalPayment("user@example.com")
assert strategy.pay(100) == True
def test_crypto_payment(self):
strategy = CryptoPayment("wallet123")
assert strategy.pay(100) == True
def test_cart_with_different_strategies(self):
cart = ShoppingCart()
cart.add_item("Item", 100)
# Тестируем с разными стратегиями
for strategy in [CreditCardPayment(...), PayPalPayment(...)]:
cart.set_payment_strategy(strategy)
assert cart.checkout() == True
6. Стратегии легко переиспользовать
Преимущество: Одну стратегию можно использовать в разных контекстах
# Стратегия работает не только с ShoppingCart
class Subscription:
def __init__(self):
self.payment_strategy = None
def set_payment_strategy(self, strategy: PaymentStrategy):
self.payment_strategy = strategy
def renew(self, amount):
return self.payment_strategy.pay(amount)
class DonationPlatform:
def __init__(self):
self.payment_strategy = None
def set_payment_strategy(self, strategy: PaymentStrategy):
self.payment_strategy = strategy
def donate(self, amount):
return self.payment_strategy.pay(amount)
# Одна стратегия используется везде
strategy = CreditCardPayment("1234", "123")
cart = ShoppingCart()
subscription = Subscription()
donation = DonationPlatform()
cart.set_payment_strategy(strategy)
subscription.set_payment_strategy(strategy)
donation.set_payment_strategy(strategy)
Минусы паттерна Strategy
1. Усложнение кода для простых случаев
Проблема: Для простых алгоритмов паттерн может быть оверкилл
# Если только 2 способа платежа
class PaymentProcessor:
def __init__(self):
self.payment_strategy = None
def set_strategy(self, strategy):
self.payment_strategy = strategy
def pay(self, amount):
return self.payment_strategy.pay(amount)
# Много кода для простой функциональности
# Может быть проще:
if payment_type == "credit_card":
# обработка
else:
# обработка
Результат: Излишняя сложность для простых задач
2. Увеличение количества классов
Проблема: Каждая стратегия требует отдельного класса
# Если есть 10 способов платежа, нужно 10 классов
class Strategy1: pass
class Strategy2: pass
class Strategy3: pass
class Strategy4: pass
class Strategy5: pass
class Strategy6: pass
class Strategy7: pass
class Strategy8: pass
class Strategy9: pass
class Strategy10: pass
# Может быть сложнее ориентироваться в коде
Результат: Много файлов, сложнее ориентироваться
3. Overhead памяти
Проблема: Каждая стратегия — объект, требующий память
# Без Strategy (экономнее)
def pay_with_credit_card(amount):
return True
def pay_with_paypal(amount):
return True
# С Strategy (больше памяти)
strategy = CreditCardPayment("card", "cvv") # Объект в памяти
# Для простой функции это излишне
4. Сложность выбора правильной стратегии
Проблема: Нужно правильно выбрать и сконфигурировать стратегию
# Если выбрать неправильную стратегию
strategy = CreditCardPayment("invalid_card", "invalid_cvv")
cart.set_payment_strategy(strategy)
cart.checkout() # Ошибка во время выполнения!
# Без Strategy это было бы очевидной ошибкой уже на этапе вызова
process_payment("credit_card", amount, "invalid_card")
5. Проблемы с сериализацией
Проблема: Сложно сериализовать стратегию (сохранить/загрузить)
import json
cart = ShoppingCart()
cart.set_payment_strategy(CreditCardPayment("1234", "123"))
# Как сохранить стратегию в JSON?
json.dumps(cart) # TypeError: Object is not JSON serializable
# Нужно вручную реализовать
class CreditCardPayment(PaymentStrategy):
def to_dict(self):
return {"type": "credit_card", "card": self.card_number}
@staticmethod
def from_dict(data):
return CreditCardPayment(data["card"], "123")
6. Скрытая зависимость контекста от стратегии
Проблема: Контекст зависит от какие-то параметры стратегии
# Контекст неявно зависит от деталей стратегии
class ShoppingCart:
def checkout(self):
# Контекст не знает, что нужно вызвать pay()
# Хорошо, если интерфейс четко определен
total = sum(...)
return self.payment_strategy.pay(total) # Это предполагает наличие pay()
# Но что если добавить новый метод validate()?
7. Difficuly с отладкой
Проблема: Сложнее следить, какая стратегия используется
# Где определяется выбор стратегии?
cart.set_payment_strategy(strategy) # Откуда это взялось?
# Нужно искать в коде, где set_payment_strategy() вызывается
# Без Strategy это было бы явным условием
if payment_type == "credit_card":
# Ясно, что происходит
Когда использовать Strategy
✅ Используй Strategy для:
- Множество похожих алгоритмов (3+)
- Алгоритмы, которые часто меняются
- Выбор алгоритма во время выполнения
- Тестирование разных реализаций
- Open/Closed принцип важен
❌ Не используй Strategy для:
- 1-2 способа выполнения
- Простые условные операторы
- Когда нет необходимости в расширении
- Прототип или MVP
Альтернативы Strategy
1. Условные операторы (для простых случаев)
def process_payment(method: str, amount: float) -> bool:
if method == "credit_card":
return process_credit_card(amount)
elif method == "paypal":
return process_paypal(amount)
else:
raise ValueError(f"Unknown method: {method}")
2. Dictionary с функциями
payment_methods = {
"credit_card": process_credit_card,
"paypal": process_paypal,
"crypto": process_crypto,
}
payment_methods[method](amount)
3. Полиморфизм через наследование (Strategy это делает)
Практический пример: Сортировка
from abc import ABC, abstractmethod
class SortingStrategy(ABC):
@abstractmethod
def sort(self, data: list) -> list:
pass
class BubbleSort(SortingStrategy):
def sort(self, data: list) -> list:
# Bubble sort implementation
return sorted(data)
class QuickSort(SortingStrategy):
def sort(self, data: list) -> list:
# Quick sort implementation
return sorted(data)
class MergeSort(SortingStrategy):
def sort(self, data: list) -> list:
# Merge sort implementation
return sorted(data)
class Sorter:
def __init__(self, strategy: SortingStrategy):
self.strategy = strategy
def sort(self, data: list) -> list:
return self.strategy.sort(data)
# Использование
data = [5, 2, 8, 1, 9]
sorter = Sorter(QuickSort())
print(sorter.sort(data)) # [1, 2, 5, 8, 9]
# Смена алгоритма во время выполнения
sorter.strategy = MergeSort()
print(sorter.sort(data)) # [1, 2, 5, 8, 9]
Вывод
Strategy — это отличный паттерн для управления множеством алгоритмов, но используй его разумно. Не применяй паттерн просто потому что он звучит круто — используй его только когда есть реальная необходимость в расширяемости и гибкости.