Стратегия

Описание
Стратегия — поведенческий шаблон проектирования, предназначенный для определения семейства алгоритмов, инкапсуляции каждого из них и обеспечения их взаимозаменяемости. Это позволяет выбирать алгоритм путём определения соответствующего класса. Шаблон Strategy позволяет менять выбранный алгоритм независимо от объектов-клиентов, которые его используют.

Шаблон стратегия позволяет переключаться между алгоритмами или стратегиями в зависимости от ситуации.

Когда использовать паттерн
  • Когда нужно иметь возможность выбирать между различными вариантами выполнения алгоритма.

  • Когда есть несколько похожих классов, различающихся только реализацией определённого поведения.

  • Когда необходимо избегать множества условных операторов, выбирающих алгоритм, и вместо этого сделать алгоритмы взаимозаменяемыми.
Основные участники паттерна
Strategy (Стратегия) — интерфейс, определяющий метод, который реализует алгоритм.

ConcreteStrategy (Конкретная стратегия) — конкретные реализации алгоритма, инкапсулированные в отдельных классах.

Context (Контекст) — класс, который использует объект стратегии для выполнения операции. Он не зависит от конкретной реализации стратегии.
Пример реализации паттерна
Рассмотрим пример с онлайн-магазином, который предлагает различные варианты расчёта стоимости доставки. В зависимости от ситуации можно выбрать один из алгоритмов расчёта стоимости — например, стандартная доставка, экспресс-доставка или доставка по скидке.
Шаг 1: Определим интерфейс стратегии
from abc import ABC, abstractmethod

class ShippingStrategy(ABC):
    @abstractmethod
    def calculate(self, order):
        pass
Здесь ShippingStrategy — это абстрактный базовый класс, определяющий метод calculate, который будет использоваться для расчёта стоимости доставки.
Шаг 2: Реализация конкретных стратегий
class StandardShipping(ShippingStrategy):
    def calculate(self, order):
        return 5.0  # фиксированная стоимость для стандартной доставки

class ExpressShipping(ShippingStrategy):
    def calculate(self, order):
        return 15.0  # фиксированная стоимость для экспресс-доставки

class DiscountedShipping(ShippingStrategy):
    def calculate(self, order):
        return 2.0  # скидка на доставку
Здесь:
  • StandardShipping, ExpressShipping, и DiscountedShipping — конкретные стратегии, каждая из которых реализует метод calculate с соответствующим расчётом.
Шаг 3: Создаём контекст
class Order:
    def __init__(self, items):
        self.items = items  # список товаров
        self.shipping_strategy = None  # стратегия по умолчанию

    def set_shipping_strategy(self, strategy: ShippingStrategy):
        self.shipping_strategy = strategy

    def calculate_shipping_cost(self):
        if not self.shipping_strategy:
            raise ValueError("Shipping strategy is not set")
        return self.shipping_strategy.calculate(self)
В этом коде:
  • Класс Order содержит информацию о заказе, и позволяет установить стратегию через метод set_shipping_strategy.

  • Метод calculate_shipping_cost вызывает метод calculate установленной стратегии для вычисления стоимости доставки.
Шаг 4: Использование паттерна «Стратегия»
# Создание заказа
order = Order(items=["book", "laptop", "pen"])

# Установка стратегии доставки и расчет стоимости
order.set_shipping_strategy(StandardShipping())
print("Standard Shipping Cost:", order.calculate_shipping_cost())  # Standard Shipping Cost: 5.0

order.set_shipping_strategy(ExpressShipping())
print("Express Shipping Cost:", order.calculate_shipping_cost())  # Express Shipping Cost: 15.0

order.set_shipping_strategy(DiscountedShipping())
print("Discounted Shipping Cost:", order.calculate_shipping_cost())  # Discounted Shipping Cost: 2.0
Как работает код?
  • Order использует стратегию доставки, выбранную пользователем, вызывая соответствующий метод calculate для расчёта стоимости доставки.

  • Класс Order не зависит от конкретных классов стратегий и может динамически изменять стратегию, что упрощает расширение и поддержку кода.
Преимущества
  • Гибкость выбора алгоритма. Паттерн позволяет выбирать алгоритмы динамически, подбирая подходящий вариант без модификации основного кода.

  • Снижение дублирования кода. Реализация каждого алгоритма в отдельном классе снижает дублирование и делает код более понятным и поддерживаемым.

  • Следование принципу открытости/закрытости. Можно добавлять новые стратегии, не изменяя существующий код.
Недостатки
  • Усложнение структуры. Каждая стратегия инкапсулируется в отдельный класс, что может усложнить структуру, особенно если вариантов алгоритмов много.

  • Дополнительные затраты на инстанцирование классов. При частой смене стратегий возможно увеличение времени выполнения и использования памяти.