Ответ
Паттерн Стратегия (Strategy) — это поведенческий паттерн проектирования, который позволяет определять семейство алгоритмов, инкапсулировать каждый из них в отдельный класс и делать их взаимозаменяемыми. Он позволяет клиенту выбирать алгоритм во время выполнения, не изменяя код клиента, который его использует.
Назначение: Основная цель Стратегии — предоставить способ изменения поведения объекта (контекста) без изменения его структуры. Это достигается путем вынесения алгоритмов в отдельные классы, что позволяет легко добавлять новые алгоритмы и динамически переключаться между ними. Паттерн Стратегия является отличным примером применения Принципа Открытости/Закрытости (Open/Closed Principle): система открыта для расширения (добавления новых стратегий), но закрыта для модификации (клиентский код не меняется).
Основные компоненты:
- Контекст (Context): Класс, который содержит ссылку на объект стратегии и делегирует ему выполнение алгоритма. Он не знает конкретной реализации стратегии, только ее интерфейс.
- Интерфейс Стратегии (Strategy Interface): Абстрактный класс или интерфейс, который объявляет общий метод для всех конкретных стратегий.
- Конкретные Стратегии (Concrete Strategies): Классы, реализующие интерфейс стратегии и содержащие конкретную реализацию алгоритма.
Пример на Python (различные стратегии расчета скидок):
from abc import ABC, abstractmethod
# 2. Интерфейс Стратегии
class DiscountStrategy(ABC):
@abstractmethod
def apply_discount(self, price: float) -> float:
pass
# 3. Конкретные Стратегии
class NoDiscountStrategy(DiscountStrategy):
def apply_discount(self, price: float) -> float:
return price
class PercentageDiscountStrategy(DiscountStrategy):
def __init__(self, percentage: float):
if not (0 <= percentage <= 1):
raise ValueError("Процент скидки должен быть от 0 до 1")
self._percentage = percentage
def apply_discount(self, price: float) -> float:
return price * (1 - self._percentage)
class FixedDiscountStrategy(DiscountStrategy):
def __init__(self, amount: float):
if amount < 0:
raise ValueError("Сумма скидки не может быть отрицательной")
self._amount = amount
def apply_discount(self, price: float) -> float:
return max(0, price - self._amount)
# 1. Контекст
class ShoppingCart:
def __init__(self, discount_strategy: DiscountStrategy):
self._items = []
self._discount_strategy = discount_strategy
def add_item(self, item_price: float):
self._items.append(item_price)
def set_discount_strategy(self, strategy: DiscountStrategy):
self._discount_strategy = strategy
def calculate_total(self) -> float:
total_price = sum(self._items)
return self._discount_strategy.apply_discount(total_price)
# Использование:
cart = ShoppingCart(NoDiscountStrategy()) # Изначально без скидки
cart.add_item(100)
cart.add_item(50)
print(f"Общая сумма без скидки: {cart.calculate_total()}") # 150.0
# Применяем процентную скидку 10%
cart.set_discount_strategy(PercentageDiscountStrategy(0.10))
print(f"Общая сумма с 10% скидкой: {cart.calculate_total()}") # 135.0
# Применяем фиксированную скидку 20
cart.set_discount_strategy(FixedDiscountStrategy(20))
print(f"Общая сумма с фиксированной скидкой 20: {cart.calculate_total()}") # 130.0
Преимущества:
- Гибкость и расширяемость: Легко добавлять новые алгоритмы без изменения существующего кода.
- Избегание условных операторов: Устраняет длинные цепочки
if/elseилиswitchдля выбора алгоритма. - Динамическая смена поведения: Позволяет изменять алгоритм, используемый объектом, во время выполнения.
- Соответствие OCP: Система открыта для расширения новыми стратегиями, но закрыта для модификации.
Когда использовать паттерн Стратегия?
- Когда объект должен вести себя по-разному в зависимости от условий, и эти условия могут меняться.
- Когда у вас есть много похожих классов, которые отличаются только своим поведением.
- Когда нужно изолировать сложную бизнес-логику или алгоритмы от основного класса.