Каковы преимущества и недостатки паттерна проектирования «Стратегия»?

Ответ

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

Преимущества:

  • Устранение условных операторов: Заменяет множественные условные конструкции (if/else, switch) полиморфизмом, делая код более чистым, легко читаемым и расширяемым.
  • Гибкость и расширяемость: Позволяет легко добавлять новые стратегии (алгоритмы) без изменения существующего клиентского кода или других стратегий, следуя принципу открытости/закрытости (Open/Closed Principle).
  • Изоляция логики алгоритмов: Каждый алгоритм инкапсулируется в свой собственный класс, что соответствует принципу единственной ответственности (SRP) и улучшает модульность и читаемость кода.
  • Упрощение тестирования: Каждую стратегию можно тестировать независимо от контекста, что упрощает процесс отладки и верификации корректности реализации.

Недостатки:

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

Пример реализации паттерна Стратегия для различных способов оплаты:

from abc import ABC, abstractmethod

# Интерфейс стратегии
class PaymentStrategy(ABC):
    @abstractmethod
    def pay(self, amount: float):
        pass

# Конкретные стратегии
class CreditCardPayment(PaymentStrategy):
    def pay(self, amount: float):
        print(f"Оплата {amount:.2f} USD через кредитную карту.")

class PayPalPayment(PaymentStrategy):
    def pay(self, amount: float):
        print(f"Оплата {amount:.2f} USD через PayPal.")

class BitcoinPayment(PaymentStrategy):
    def pay(self, amount: float):
        print(f"Оплата {amount:.2f} USD через Bitcoin кошелек.")

# Контекст, использующий стратегию
class PaymentContext:
    def __init__(self, strategy: PaymentStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: PaymentStrategy):
        self._strategy = strategy

    def execute_payment(self, amount: float):
        print("Выполнение платежа...")
        self._strategy.pay(amount)

# Использование паттерна
context = PaymentContext(CreditCardPayment())
context.execute_payment(100.50)

context.set_strategy(PayPalPayment())
context.execute_payment(25.00)

context.set_strategy(BitcoinPayment())
context.execute_payment(500.75)

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

Ответ 18+ 🔞

А, паттерн «Стратегия»! Ну это ж классика, блядь, как та самая история с Герасимом и Муму, только про алгоритмы, ёпта.

Смотри, представь: у тебя есть задача, а способов её решить — дохуя. Можно в лоб, можно сбоку, можно через жопу. И вот чтобы не городить в коде эти вечные if (способ == "в лоб") { ... } else if (способ == "через жопу") { ... }, которые потом раздуваются, как бомж после хорошей пайки, умные дядьки и придумали эту стратегию.

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

Что хорошего-то?

  • Свистопляску с if/else убираешь. Вместо кучи ветвлений — чистая полиморфная магия. Добавил новый класс — и всё, пиздец. Ничего нигде не поломалось. Красота!
  • Гибкость — овердохуища. Новый алгоритм? Да хуй с ним, не вопрос! Создал класс, вписал его в общую линию, и контекст даже не заметит подмены. Принцип «открыт для расширения, закрыт для изменений» в действии, ёбана!
  • Всё по полочкам. Каждая стратегия сидит в своей избушке и не суёт нос в чужие дела. Тестировать — одно удовольствие, потому что каждая — самостоятельная единица, а не кусок спагетти-кода.
  • Простота, блядь. Клиенту (тому, кто использует) — похуй, как именно там внутри всё посчиталось. Его дело — сказать «считай вот так» и получить результат. Абстракция, ёпта!

Но и подводные ебли тут есть:

  • Классов становится, как собак нерезаных. За каждую мелкую подстратегию — отдельный файл. Для простых задач это — как из пушки по воробьям, чистое распиздяйство. Архитектура превращается в бюрократический ад.
  • Клиент всё равно должен быть в курсе. Ему же надо выбрать, какую стратегию применять. Так что знание о них всё равно куда-то просачивается. Полной изоляции не выходит, блядь.
  • Иногда это стрельба из танка по тараканам. Если у тебя алгоритм один и навсегда, и меняться он не собирается, то вся эта возня с интерфейсами и классами — просто пиздопроебибна, прости мой французский.

Ну а теперь, как это выглядит в коде, на примере оплаты:

from abc import ABC, abstractmethod

# Это типа общий договор, как все стратегии должны выглядеть. Все должны уметь `pay`.
class PaymentStrategy(ABC):
    @abstractmethod
    def pay(self, amount: float):
        pass

# Конкретные бандиты, которые выполняют договор по-своему.
class CreditCardPayment(PaymentStrategy):
    def pay(self, amount: float):
        print(f"Списано {amount:.2f} баксов картой. Будь осторожен, друг.")

class PayPalPayment(PaymentStrategy):
    def pay(self, amount: float):
        print(f"ПиПал забрал свои {amount:.2f} USD. Лёгким движением руки.")

class BitcoinPayment(PaymentStrategy):
    def pay(self, amount: float):
        print(f"Ушло {amount:.2f} зелёных в биткойнах. Анонимность, блядь, рулит.")

# А это наш главный распорядитель, который решает, кого сегодня на сцену пускать.
class PaymentContext:
    def __init__(self, strategy: PaymentStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: PaymentStrategy):
        self._strategy = strategy

    def execute_payment(self, amount: float):
        print("Ща всё будет, не дёргайся...")
        self._strategy.pay(amount)

# Ну и демонстрация, как этим хозяйством пользоваться.
context = PaymentContext(CreditCardPayment())
context.execute_payment(100.50)  # Платим картой

context.set_strategy(PayPalPayment())
context.execute_payment(25.00)   # А теперь через PayPal

context.set_strategy(BitcoinPayment())
context.execute_payment(500.75)  # И на десерт — крипта

Вот и весь сказ, блядь. Паттерн — как швейцарский нож: когда нужно иметь под рукой кучу инструментов и быстро менять их — он незаменим. А когда тебе нужно всего лишь разок хлеб отрезать — проще взять обычный нож, а не разворачивать эту многоэтажную конструкцию. Думай головой, когда применять, а не просто потому, что модно.