Что такое паттерн Стратегия в программировании?

Ответ

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

Назначение: Основная цель Стратегии — предоставить способ изменения поведения объекта (контекста) без изменения его структуры. Это достигается путем вынесения алгоритмов в отдельные классы, что позволяет легко добавлять новые алгоритмы и динамически переключаться между ними. Паттерн Стратегия является отличным примером применения Принципа Открытости/Закрытости (Open/Closed Principle): система открыта для расширения (добавления новых стратегий), но закрыта для модификации (клиентский код не меняется).

Основные компоненты:

  1. Контекст (Context): Класс, который содержит ссылку на объект стратегии и делегирует ему выполнение алгоритма. Он не знает конкретной реализации стратегии, только ее интерфейс.
  2. Интерфейс Стратегии (Strategy Interface): Абстрактный класс или интерфейс, который объявляет общий метод для всех конкретных стратегий.
  3. Конкретные Стратегии (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: Система открыта для расширения новыми стратегиями, но закрыта для модификации.

Когда использовать паттерн Стратегия?

  • Когда объект должен вести себя по-разному в зависимости от условий, и эти условия могут меняться.
  • Когда у вас есть много похожих классов, которые отличаются только своим поведением.
  • Когда нужно изолировать сложную бизнес-логику или алгоритмы от основного класса.

Ответ 18+ 🔞

Давай я тебе про этот паттерн «Стратегия» так расскажу, чтобы ты не просто понял, а чтобы у тебя в голове, блядь, щёлкнуло, как у кассового аппарата, когда покупка проходит.

Представь себе, ты в магазине. Ты — это контекст, понимаешь? Просто корзина с товарами. А вот кассирша — это твоя стратегия, блядь. Она может быть разная.

Вот подходишь ты с корзиной, а там сидит бабка Зина «Без скидки». Она тупо складывает цифры на калькуляторе и говорит: «С тебя, петушок, 150 рублей, плати». Это стратегия NoDiscountStrategy. Тупая, прямолинейная, нихуя не интересная.

А в соседней кассе сидит Ленка «Процентница» (PercentageDiscountStrategy). Глаза подведённые, говорит: «Ой, дорогой, у нас сегодня скидочка 10%, будет 135». Она умножает на свой коэфициент и всё.

А в третьей — Васька «От бороды» (FixedDiscountStrategy). Мужик здоровый, бородатый. Говорит хрипло: «От 150 рублей 20 списываю. Держи 130 и проваливай».

Суть в чём, ёпта? Твоя корзина-то одна и та же! Товары те же! А итоговая цена — пиздец как разная! Потому что ты подставляешь под свою корзину разных кассиров (стратегий). И корзине (контексту) похуй, кто там считает. Её дело — передать сумму и получить ответ. А уж Зина, Ленка или Васька будут там выёбываться со своими формулами — это их проблемы.

Вот смотри, как это в коде выглядит, только не засыпай:

from abc import ABC, abstractmethod

# Это типа общий устав для всех кассиров. Все должны уметь одну операцию: применить скидку.
class DiscountStrategy(ABC):
    @abstractmethod
    def apply_discount(self, price: float) -> float:
        pass

# А это сами кассиры. Каждый — отдельный класс, живет своей жизнью.

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("Ты чё, мудила, скидку больше 100% делать собрался?")
        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)  # Чтобы в ноль уйти, а не в минус, а то Васька не поймёт

# А вот это и есть наша корзина — КОНТЕКСТ.
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. Скукотища.

# А теперь — ВНИМАНИЕ! — меняем стратегию на лету!
cart.set_discount_strategy(PercentageDiscountStrategy(0.10))  "Зина, отдыхай! Ленка, работай!"
print(f"С Ленкой (10%): {cart.calculate_total()}")  # 135.0. Уже веселее.

cart.set_discount_strategy(FixedDiscountStrategy(20))  # "Ленка, вали! Васька, давай, руби с плеча!"
print(f"С Васькой (-20): {cart.calculate_total()}")  # 130.0. Во, агрессивно!

И вся магия, ёпта, в том, что корзина ShoppingCart — она тупая как пробка. Она не знает, как там проценты считаются или фиксированная сумма вычитается. Её дело — спросить: «Эй, стратегия, вот сумма, что делать-то?». А стратегия уже сама решает, по своим внутренним, ебейшим правилам.

Зачем этот цирк? А затем, сука, чтобы не было в коде этих бесконечных if:

if user_type == "vip":
    price = price * 0.8
elif day == "black_friday":
    price = price - 50
elif coupon == "PIZDA":
    price = 0
else:
    price = price * 1.1 # Налог на дурака

Это пиздец, а не код. Добавишь новую акцию — придёшь весь этот блок переписывать, а там уже 25 условий. А со стратегией ты просто создашь новый класс BlackFridayDiscountStrategy и подсунешь его корзине. И ни одна старая строчка кода не дрогнет! Это и есть принцип «открыт для расширения, закрыт для изменений». Красота, блядь, а не паттерн.

Итог: Стратегия — это когда ты выносишь мозг разной логики в отдельные штуки и подсовываешь их основной программе, как сменные насадки на дрель. Хочешь сверлить — ставь сверло, хочешь мешать — ставь миксер. Дрель одна, а возможностей — овердохуища.