$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогПаттерн «Стратегия»: замените гигантские if-else одним элегантным решением

Паттерн «Стратегия»: замените гигантские if-else одним элегантным решением

$ sudo teach IT
·16 июля 2026 г.·9 мин·69
Паттерн «Стратегия»: замените гигантские if-else одним элегантным решением

Разбираем поведенческий паттерн Strategy на примерах на Python и Java: когда он нужен, как его реализовать и чем он отличается от простого полиморфизма.

Содержание
Проблема: когда код начинает «знать слишком много»Суть паттерна StrategyРеализация на PythonКлассическая ООП-реализацияФункциональный подход в PythonРеализация на JavaЧерез интерфейсЛямбды в Java 8+Strategy vs. простой полиморфизм: в чём разница?Что общегоКлючевые отличияКогда применять Strategy, а когда — нетТипичные ошибки при реализацииВывод

Каждый разработчик рано или поздно сталкивается с одним и тем же симптомом: метод, который начинался как десять строк, разрастается до ста, потом до трёхсот. Внутри — нагромождение if-else или switch-case, которые проверяют тип операции, роль пользователя, режим работы системы. Добавить новый вариант поведения означает залезть внутрь этого метода, рискуя сломать всё, что уже работает.

Паттерн Strategy — один из классических ответов на эту проблему. Он описан в книге «банды четырёх» (GoF) ещё в 1994 году, но остаётся актуальным и сегодня — именно потому, что решает фундаментальную проблему проектирования, а не синтаксическую.

В этой статье разберём, что такое Strategy по существу, когда его стоит применять, как реализовать на Python и Java, и чем он отличается от обычного полиморфизма — потому что это различие понимают далеко не все.


Проблема: когда код начинает «знать слишком много»

Представьте систему обработки платежей. Поначалу она поддерживает только банковские карты. Потом добавляется PayPal. Потом криптовалюта. Потом внутренний баланс. Каждый раз разработчик открывает метод process_payment и дописывает очередную ветку:

code
def process_payment(amount, method):
    if method == "card":
        # логика для карты
        charge_card(amount)
    elif method == "paypal":
        # логика для PayPal
        call_paypal_api(amount)
    elif method == "crypto":
        # логика для крипты
        broadcast_transaction(amount)
    elif method == "balance":
        # логика для баланса
        deduct_from_balance(amount)
    else:
        raise ValueError(f"Unknown payment method: {method}")

На первый взгляд — ничего страшного. Но у этого подхода есть системные проблемы:

  • Нарушение принципа открытости/закрытости (OCP): каждое новое требование заставляет модифицировать существующий код.
  • Сложность тестирования: чтобы протестировать логику крипты, нужно инициализировать весь метод целиком.
  • Связанность: логика разных способов оплаты перемешана в одном месте. Ошибка в ветке paypal может повлиять на соседние ветки при рефакторинге.
  • Масштабирование в команде: два разработчика не могут одновременно работать над разными ветками без конфликтов в git.

Именно здесь Strategy предлагает принципиально другой подход.


Суть паттерна Strategy

Идея проста: вынести каждый вариант поведения в отдельный класс, объединив их общим интерфейсом. Контекст (класс, который использует поведение) работает только с этим интерфейсом и не знает, какая конкретная реализация подставлена.

Структура паттерна состоит из трёх элементов:

  1. Strategy (интерфейс/абстрактный класс) — объявляет метод, который реализуют все конкретные стратегии.
  2. ConcreteStrategy — конкретные реализации алгоритма.
  3. Context — класс, который хранит ссылку на стратегию и делегирует ей выполнение.

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


Реализация на Python

Python — динамический язык, поэтому здесь есть несколько способов реализации. Начнём с классического объектно-ориентированного подхода.

Классическая ООП-реализация

code
from abc import ABC, abstractmethod


class PaymentStrategy(ABC):
    """Абстрактная стратегия оплаты."""

    @abstractmethod
    def pay(self, amount: float) -> None:
        pass


class CardPayment(PaymentStrategy):
    def __init__(self, card_number: str):
        self._card_number = card_number

    def pay(self, amount: float) -> None:
        print(f"Списано {amount} с карты {self._card_number[-4:]}")


class PayPalPayment(PaymentStrategy):
    def __init__(self, email: str):
        self._email = email

    def pay(self, amount: float) -> None:
        print(f"Оплата через PayPal ({self._email}): {amount}")


class CryptoPayment(PaymentStrategy):
    def __init__(self, wallet_address: str):
        self._wallet = wallet_address

    def pay(self, amount: float) -> None:
        print(f"Транзакция на кошелёк {self._wallet[:8]}...: {amount} BTC")


class PaymentContext:
    """Контекст — не знает, какой именно способ оплаты используется."""

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

    def set_strategy(self, strategy: PaymentStrategy) -> None:
        """Стратегию можно менять в рантайме."""
        self._strategy = strategy

    def checkout(self, amount: float) -> None:
        self._strategy.pay(amount)


# Использование
payment = PaymentContext(CardPayment("4111111111111234"))
payment.checkout(1500.00)

payment.set_strategy(PayPalPayment("user@example.com"))
payment.checkout(750.00)

Обратите внимание на метод set_strategy — это одна из ключевых особенностей паттерна: стратегию можно менять в процессе работы программы, не пересоздавая контекст.

Функциональный подход в Python

В Python стратегии не обязательно оформлять как классы — функции являются объектами первого класса, что даёт более лаконичный вариант:

code
from typing import Callable


def card_payment(amount: float) -> None:
    print(f"Оплата картой: {amount}")


def paypal_payment(amount: float) -> None:
    print(f"Оплата через PayPal: {amount}")


class PaymentContext:
    def __init__(self, strategy: Callable[[float], None]):
        self._strategy = strategy

    def checkout(self, amount: float) -> None:
        self._strategy(amount)


payment = PaymentContext(card_payment)
payment.checkout(500.0)

payment._strategy = paypal_payment
payment.checkout(300.0)

Этот подход работает, но у него есть ограничение: функции не несут состояния. Если стратегии нужны параметры (номер карты, адрес кошелька), придётся использовать замыкания или functools.partial — что снижает читаемость. Классовый подход здесь нагляднее.


Реализация на Java

Java — строготипизированный язык, и паттерн Strategy здесь выглядит особенно органично, поскольку интерфейсы — основной инструмент абстракции.

Через интерфейс

code
// Интерфейс стратегии
public interface SortStrategy {
    void sort(int[] array);
}

// Конкретные стратегии
public class BubbleSort implements SortStrategy {
    @Override
    public void sort(int[] array) {
        System.out.println("Сортировка пузырьком");
        // реализация bubble sort
        for (int i = 0; i < array.length - 1; i++) {
            for (int j = 0; j < array.length - i - 1; j++) {
                if (array[j] > array[j + 1]) {
                    int temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                }
            }
        }
    }
}

public class QuickSort implements SortStrategy {
    @Override
    public void sort(int[] array) {
        System.out.println("Быстрая сортировка");
        quickSort(array, 0, array.length - 1);
    }

    private void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            int pi = partition(arr, low, high);
            quickSort(arr, low, pi - 1);
            quickSort(arr, pi + 1, high);
        }
    }

    private int partition(int[] arr, int low, int high) {
        int pivot = arr[high];
        int i = low - 1;
        for (int j = low; j < high; j++) {
            if (arr[j] <= pivot) {
                i++;
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
        int temp = arr[i + 1];
        arr[i + 1] = arr[high];
        arr[high] = temp;
        return i + 1;
    }
}

// Контекст
public class Sorter {
    private SortStrategy strategy;

    public Sorter(SortStrategy strategy) {
        this.strategy = strategy;
    }

    public void setStrategy(SortStrategy strategy) {
        this.strategy = strategy;
    }

    public void sort(int[] array) {
        strategy.sort(array);
    }
}

// Использование
public class Main {
    public static void main(String[] args) {
        int[] data = {64, 34, 25, 12, 22, 11, 90};

        Sorter sorter = new Sorter(new BubbleSort());
        sorter.sort(data);

        // Переключаемся на быструю сортировку без изменения контекста
        sorter.setStrategy(new QuickSort());
        sorter.sort(data);
    }
}

Лямбды в Java 8+

Начиная с Java 8, функциональные интерфейсы и лямбды позволяют писать стратегии компактнее:

code
import java.util.Arrays;
import java.util.function.Consumer;

public class Sorter {
    private Consumer<int[]> strategy;

    public Sorter(Consumer<int[]> strategy) {
        this.strategy = strategy;
    }

    public void sort(int[] array) {
        strategy.accept(array);
    }
}

// Использование с лямбдой
Sorter sorter = new Sorter(arr -> Arrays.sort(arr));
sorter.sort(new int[]{5, 3, 1, 4, 2});

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


Strategy vs. простой полиморфизм: в чём разница?

Это, пожалуй, самый важный вопрос — и самый частый источник путаницы. Многие разработчики, увидев паттерн Strategy впервые, говорят: «Но это же просто полиморфизм». И они правы лишь отчасти.

Что общего

И Strategy, и обычный полиморфизм используют интерфейсы и подстановку реализаций. Оба позволяют вызывать pay() или sort(), не зная конкретного типа объекта.

Ключевые отличия

1. Намерение и семантика

Полиморфизм — это про «что это такое» (иерархия типов). Strategy — про «как это делается» (выбор алгоритма). Dog и Cat — разные типы, которые по-разному реализуют makeSound(). Это полиморфизм. BubbleSort и QuickSort — не разные типы сортировщиков, это разные способы сортировки одного и того же. Это Strategy.

2. Изменяемость в рантайме

При обычном полиморфизме объект создаётся как конкретный тип и остаётся им. Strategy позволяет подменить алгоритм у существующего объекта через setStrategy() — без пересоздания контекста.

3. Отделение алгоритма от данных

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

code
# Полиморфизм: поведение жёстко привязано к типу
class Duck:
    def quack(self):
        print("Кря!")

class RubberDuck(Duck):
    def quack(self):
        print("Пи-пи!")

# Strategy: поведение — отдельный объект
class Duck:
    def __init__(self, quack_strategy):
        self._quack = quack_strategy

    def quack(self):
        self._quack()

real_quack = lambda: print("Кря!")
rubber_quack = lambda: print("Пи-пи!")

duck = Duck(real_quack)
duck.quack()  # Кря!

duck._quack = rubber_quack  # Меняем поведение без создания нового объекта
duck.quack()  # Пи-пи!

Именно эта гибкость делает Strategy мощным инструментом там, где полиморфизм недостаточен.


Когда применять Strategy, а когда — нет

Паттерн хорошо подходит, если:

  • Есть несколько вариантов алгоритма, которые нужно переключать в рантайме или конфигурировать извне.
  • Логика выбора алгоритма засоряет основной класс (if method == "X", if mode == "Y").
  • Алгоритмы нужно тестировать независимо друг от друга.
  • Планируется добавление новых вариантов без изменения существующего кода.

Паттерн избыточен, если:

  • Вариантов поведения всего два, и они никогда не изменятся.
  • Алгоритм не имеет состояния и не будет меняться — достаточно простой функции.
  • Команда небольшая, а код одноразовый — дополнительная абстракция только усложнит чтение.

Как и любой паттерн, Strategy — это инструмент, а не догма. Применять его ради «правильной архитектуры» без реальной необходимости — это оверинжиниринг, который вредит проекту не меньше, чем гигантский if-else.

Если хотите систематически разобраться с тем, когда и как применять паттерны проектирования в реальных проектах, стоит обратить внимание на курс «» — там эти темы разбираются на практических задачах, а не в отрыве от реального кода.


Типичные ошибки при реализации

1. Контекст знает о конкретных стратегиях

code
# Плохо: контекст проверяет тип стратегии
def checkout(self, amount):
    if isinstance(self._strategy, CryptoPayment):
        amount = self._convert_to_btc(amount)
    self._strategy.pay(amount)

Если контекст начинает различать стратегии — паттерн реализован неправильно. Конвертация должна быть внутри CryptoPayment.pay().

2. Стратегия знает о контексте

Стратегия должна быть независимой. Если она обращается к полям контекста напрямую — это нарушение инкапсуляции и признак плохого разделения ответственности.

3. Слишком тонкозернистые стратегии

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

4. Игнорирование фабрики для создания стратегий

Если выбор стратегии всё равно происходит через if-else где-то в клиентском коде — проблема не исчезла, она просто переехала. Для выбора стратегии по параметру стоит использовать словарь-маппинг или фабричный метод:

code
PAYMENT_STRATEGIES = {
    "card": CardPayment,
    "paypal": PayPalPayment,
    "crypto": CryptoPayment,
}

def get_strategy(method: str, **kwargs) -> PaymentStrategy:
    cls = PAYMENT_STRATEGIES.get(method)
    if not cls:
        raise ValueError(f"Неизвестный метод оплаты: {method}")
    return cls(**kwargs)

Теперь добавление нового способа оплаты — это создание нового класса и одна строка в словаре. Существующий код не трогается.


Вывод

Паттерн Strategy — это не про синтаксис и не про «красивый код ради красоты». Это про управление изменениями. Когда требования меняются (а они всегда меняются), код, построенный на Strategy, расширяется добавлением нового класса. Код с гигантским if-else — модификацией существующего, с риском регрессий.

Разница между Strategy и простым полиморфизмом — в намерении: полиморфизм описывает иерархию типов, Strategy инкапсулирует изменяемое поведение как самостоятельный объект. Это тонкое, но принципиальное различие, которое становится очевидным в реальных проектах.

Python и Java реализуют паттерн по-разному в деталях, но идея одна: отделите то, что меняется, от того, что остаётся стабильным. Именно этот принцип лежит в основе большинства паттернов GoF — и именно его понимание отличает архитектурно грамотный код от набора работающих, но хрупких конструкций.

Если хотите выстроить системное понимание паттернов проектирования и научиться применять их осознанно — курс «» может стать хорошей отправной точкой для углублённого изучения темы.

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

Автор

$ sudo teach IT

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

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

Перейти к курсу
Простыми словами о защищенном соединении

Простыми словами о защищенном соединении

Базовый курс для обычных людей: что такое защищённое соединение, как оно устроено, что оно умеет и чего не умеет, как выбрать сервис и настроить его на своих устройствах.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Сервис, который не падает - как построить безопасную зашифрованную сеть

Сервис, который не падает - как построить безопасную зашифрованную сеть

Практический курс по безопасности сети: как собрать собственный сервис защищённого доступа - от пустого сервера до подписки с автовыбором - и, что важнее, как потом не уронить его своими руками.

Перейти к курсу

Другие статьи

Cargo для продакшена: workspaces, фичи и стратегия версий зависимостей
cargo

Cargo для продакшена: workspaces, фичи и стратегия версий зависимостей

Разберём, как организовать Rust workspace, управлять optional features, фиксировать версии библиотек и уменьшать “дрейф” зависимостей при командной разработке и CI.

18 сентября 2026 г.
1000
Внедрение ruff в существующий проект: стратегия “снизу вверх”, чтобы не сломать команду
внедрение

Внедрение ruff в существующий проект: стратегия “снизу вверх”, чтобы не сломать команду

Как добавить линтер поэтапно: базовая конфигурация, исключения по каталогам, поэтапное ужесточение правил и контроль качества в PR. Дадим план внедрения на 2–4 недели.

26 июля 2026 г.
550
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1431
Как выбрать, куда идти в IT: веб, боты, данные — без хаоса
выбрать

Как выбрать, куда идти в IT: веб, боты, данные — без хаоса

Разберём самые понятные направления для новичка (веб, боты, аналитика/данные) и чем они отличаются по шагам обучения. В конце вы получите простой план, как проверить, что вам подходит, и какой стартовый путь выбрать.

5 октября 2026 г.
580
Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть
путь

Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть

Разберём структуру репозитория, как упаковать проект в понятную историю (проблема → решение → запуск → метрики качества) и какие артефакты повышают доверие: скринкасты, архитектурная схема, чек-лист продакшн-готовности.

17 сентября 2026 г.
1090
Как понять, какой язык программирования учить первым: простой тест и выбор
понять

Как понять, какой язык программирования учить первым: простой тест и выбор

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

2 октября 2026 г.
630

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

Проблема: когда код начинает «знать слишком много»Суть паттерна StrategyРеализация на PythonКлассическая ООП-реализацияФункциональный подход в PythonРеализация на JavaЧерез интерфейсЛямбды в Java 8+Strategy vs. простой полиморфизм: в чём разница?Что общегоКлючевые отличияКогда применять Strategy, а когда — нетТипичные ошибки при реализацииВывод