Как в Python реализовать паттерн проектирования ‘Стратегия’

Ответ

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

Компоненты паттерна:

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

Пример реализации

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

from abc import ABC, abstractmethod
from typing import List, Type

# 1. Интерфейс Стратегии
class SortStrategy(ABC):
    @abstractmethod
    def sort(self, data: List) -> List:
        pass

# 2. Конкретные стратегии
class AscendingSort(SortStrategy):
    """Сортировка по возрастанию."""
    def sort(self, data: List) -> List:
        return sorted(data)

class DescendingSort(SortStrategy):
    """Сортировка по убыванию."""
    def sort(self, data: List) -> List:
        return sorted(data, reverse=True)

# 3. Контекст
class Sorter:
    def __init__(self, strategy: SortStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: SortStrategy):
        """Позволяет сменить стратегию на лету."""
        self._strategy = strategy

    def execute_sort(self, data: List):
        print(f"Используется стратегия: {self._strategy.__class__.__name__}")
        result = self._strategy.sort(data)
        print(f"Результат: {result}")
        return result

# Использование
data_to_sort = [5, 2, 8, 1, 9, 4]

# Изначально используем сортировку по возрастанию
sorter = Sorter(AscendingSort())
sorter.execute_sort(data_to_sort)
# Вывод:
# Используется стратегия: AscendingSort
# Результат: [1, 2, 4, 5, 8, 9]

# Динамически меняем стратегию на сортировку по убыванию
sorter.set_strategy(DescendingSort())
sorter.execute_sort(data_to_sort)
# Вывод:
# Используется стратегия: DescendingSort
# Результат: [9, 8, 5, 4, 2, 1]

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

  • Гибкость: Позволяет легко добавлять новые алгоритмы, не изменяя код контекста.
  • Изоляция: Код, данные и зависимости алгоритмов изолированы в своих классах.
  • Следование принципу открытости/закрытости: Система открыта для расширения (новые стратегии), но закрыта для модификации (не нужно менять класс Sorter).

Ответ 18+ 🔞

Так, слушай сюда, про этот паттерн "Стратегия". Представь себе, ты — главный по сортировке, царь и бог. Но вот беда: сегодня начальству надо по возрастанию, завтра — по убыванию, а послезавтра какой-нибудь ебаный менеджер придумает сортировку по цвету или размеру ботинка. И что, каждый раз переписывать всю свою, блядь, логику? Да ну нахуй!

Вот тут-то этот паттерн и выручает, как спасительный хуй в шторм. Суть проще пареной репы, хоть и звучит умно.

Что это за зверь? Это такой способ сказать: "Эй, чувак, вот у тебя куча разных алгоритмов (стратегий) сделать одно и то же. Давай не будем мешать всё в одну кучу, а раскидаем по отдельным коробочкам. А наш главный чувак (Контекст) будет просто тыкать пальцем: 'Эту коробочку давай сюда!'".

Из чего состоит этот цирк?

  1. Стратегия (Strategy): Это как общая инструкция для всех коробочек. Типа, "в каждой коробочке ДОЛЖЕН быть метод сделать_дело()". Просто бумажка, абстракция, пизда с ушами.
  2. Конкретная Стратегия (ConcreteStrategy): А вот это уже сами коробочки. Одна коробочка — "сортировка по возрастанию". Другая — "по убыванию". Третья, не дай бог, — "по размеру левого яйца". Каждая знает, КАК именно делать дело.
  3. Контекст (Context): Это наш главный чувак. У него в руках сейчас одна из коробочек. Он сам нихуя не умеет, он просто кричит: "Эй, коробочка, которую я держу, делай дело!". И всё. А если надо сменить поведение — он просто выкидывает старую коробочку и хватает новую. Вообще не парится.

Смотри, как это в коде выглядит, на примере сортировщика:

from abc import ABC, abstractmethod
from typing import List

# 1. Общая инструкция для всех коробочек (Стратегия)
# Говорит: "Любая сортировочная коробочка ДОЛЖНА иметь метод sort()"
class SortStrategy(ABC):
    @abstractmethod
    def sort(self, data: List) -> List:
        pass

# 2. Конкретные коробочки (Конкретные стратегии)
class AscendingSort(SortStrategy):
    """Коробочка 'По возрастанию'."""
    def sort(self, data: List) -> List:
        # Её внутренняя магия
        return sorted(data)  # Просто вызываем стандартную сортировку

class DescendingSort(SortStrategy):
    """Коробочка 'По убыванию'."""
    def sort(self, data: List) -> List:
        # Другая внутренняя магия
        return sorted(data, reverse=True)

# 3. Главный чувак (Контекст)
class Sorter:
    def __init__(self, strategy: SortStrategy):
        # При рождении ему дают первую коробочку в руки
        self._strategy = strategy

    def set_strategy(self, strategy: SortStrategy):
        """Метод, чтобы сменить коробочку на лету. Охуенно же!"""
        self._strategy = strategy

    def execute_sort(self, data: List):
        # Он не парится КАК сортировать. Он просто тыкает в коробочку.
        print(f"Достаю коробочку: {self._strategy.__class__.__name__}")
        result = self._strategy.sort(data)
        print(f"Насортировал: {result}")
        return result

# 4. Использование всей этой хуйни
data = [5, 2, 8, 1, 9, 4]

# Рождается Сортировщик, и ему сразу вручают коробочку AscendingSort
sorter = Sorter(AscendingSort())
sorter.execute_sort(data)
# Вывод:
# Достаю коробочку: AscendingSort
# Насортировал: [1, 2, 4, 5, 8, 9]

# А теперь, ёпта, начальство передумало! Надо по убыванию!
# Без паники! Не переписываем ничего! Просто меняем коробочку.
sorter.set_strategy(DescendingSort())
sorter.execute_sort(data)
# Вывод:
# Достаю коробочку: DescendingSort
# Насортировал: [9, 8, 5, 4, 2, 1]

И в чём, блядь, кайф?

  • Гибкость — овердохуища. Захотел новую сортировку (например, по последней цифре)? Берешь новую коробочку (LastDigitSort), пишешь в ней свой алгоритм и подсовываешь нашему Sorter-у. Сам Sorter даже не чихнет, ему похуй.
  • Всё по полочкам. Каждый алгоритм сидит в своём углу и не лезет к другим. Никаких гигантских if-else на 300 строк, где всё перемешано.
  • Принцип "Открыт/Закрыт" в действии. Систему можно расширять (добавлять новые коробочки-стратегии), но не нужно ковырять и ломать старый, отлаженный код главного чувака (Контекста).

Короче, паттерн "Стратегия" — это когда ты не спрашиваешь "как сделать?", а командуешь: "Сделай!". А уж как — это проблемы той штуки, которую ты держишь в руках. Удобно, чё.