Strategy

Один из самых полезных паттернов: заменяем ветвистый if по «режиму» на взаимозаменяемые объекты-алгоритмы.

Strategy определяет семейство взаимозаменяемых алгоритмов, инкапсулирует каждый и позволяет менять их в рантайме независимо от клиента.

Какую задачу решает

Классический запах кода — функция с разрастающимся if режим == "...": способ оплаты, тип сортировки, политика скидки. Каждый новый режим правит общую функцию (нарушая OCP) и усложняет тесты. Strategy выносит каждый вариант в отдельный объект с общим интерфейсом, а клиент держит ссылку на текущую стратегию и просто вызывает её.

Идея и реализация

from abc import ABC, abstractmethod


class Discount(ABC):
    @abstractmethod
    def apply(self, price): ...


class NoDiscount(Discount):
    def apply(self, price):
        return price


class PercentDiscount(Discount):
    def __init__(self, percent):
        self.percent = percent

    def apply(self, price):
        return price * (1 - self.percent / 100)


class Cart:
    def __init__(self, strategy: Discount):
        self.strategy = strategy      # текущая стратегия
        self.total = 0

    def checkout(self, price):
        return self.strategy.apply(price)


cart = Cart(PercentDiscount(10))
print(cart.checkout(1000))

cart.strategy = NoDiscount()          # подмена в рантайме
print(cart.checkout(1000))

Вывод:

900.0
1000

Корзина не знает, как считается скидка — она делегирует это стратегии. Заменить алгоритм можно прямо в рантайме (cart.strategy = ...). Новая политика — это новый класс Discount, код Cart остаётся нетронутым. Это образцовое применение Open/Closed.

Питоничный вариант: просто функция

В Python функции — объекты первого класса, поэтому стратегией часто служит обычная функция, без классов. Это лаконичнее, когда у стратегии нет своего состояния.

def percent(p):
    return lambda price: price * (1 - p / 100)


def none():
    return lambda price: price


def checkout(price, strategy):
    return strategy(price)


print(checkout(1000, percent(20)))
print(checkout(1000, none()))

Вывод:

800.0
1000

Strategy против State

Структурно похожи, но Strategy обычно выбирает клиент ради разных алгоритмов, а в State объект сам меняет своё поведение при смене внутреннего состояния (об этом — в следующем разделе).

Где встречается

Ключ сортировки (sorted(data, key=...) — это стратегия!), политики ретраев, алгоритмы сжатия/шифрования по настройке, способы оплаты, валидаторы форм, ИИ-поведение в играх.

Итог

  • Strategy заменяет ветвление по режиму на взаимозаменяемые объекты-алгоритмы.
  • Алгоритм можно подменить в рантайме; новый алгоритм не трогает клиента (OCP).
  • В Python стратегией часто служит обычная функция.
Проверьте себя
1. Какой «запах кода» обычно устраняет Strategy?
AСлишком много комментариев
BРазрастающийся if/elif по режиму или типу алгоритма
CДлинные имена переменных
DГлубокую вложенность циклов
2. Главное преимущество Strategy?
AЭкономия памяти
BАлгоритм можно заменить в рантайме, не меняя клиентский код
CГарантия одного экземпляра
DУскорение ввода-вывода
3. Что в Python часто играет роль стратегии вместо класса?
AСловарь
BОбычная функция (объект первого класса)
CКортеж
DМножество
Поддержать проект