Что такое паттерн проектирования «Стратегия» (Strategy)?

Ответ

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

Зачем он нужен? Чтобы избавиться от громоздких условных операторов (if/else или switch) при выборе поведения и сделать систему гибкой, соответствующей принципу открытости/закрытости (код открыт для расширения, но закрыт для модификации).

Пример на Java (платежная система):

// 1. Общий интерфейс стратегии
interface PaymentStrategy {
    void pay(int amount);
}

// 2. Конкретные стратегии
class CreditCardPayment implements PaymentStrategy {
    private String cardNumber;
    public CreditCardPayment(String cardNumber) { this.cardNumber = cardNumber; }

    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using Credit Card " + cardNumber.substring(12));
    }
}

class PayPalPayment implements PaymentStrategy {
    private String email;
    public PayPalPayment(String email) { this.email = email; }

    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using PayPal account " + email);
    }
}

// 3. Контекст, использующий стратегию
class ShoppingCart {
    private PaymentStrategy paymentStrategy;

    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }

    public void checkout(int totalAmount) {
        paymentStrategy.pay(totalAmount);
    }
}

// 4. Использование
public class Main {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();
        cart.setPaymentStrategy(new CreditCardPayment("1234-5678-9012-3456"));
        cart.checkout(100); // Paid 100 using Credit Card 3456

        // Динамическая смена стратегии
        cart.setPaymentStrategy(new PayPalPayment("user@example.com"));
        cart.checkout(50); // Paid 50 using PayPal account user@example.com
    }
}

Ключевые преимущества:

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

Типичное применение: системы оплаты, алгоритмы сортировки или сжатия, вариативная логика валидации или расчета доставки.

Ответ 18+ 🔞

А, паттерн Стратегия! Ну это ж классика, блядь, как та самая история про Герасима и Муму, только в мире кода. Слушай, представь себе — ты пишешь код, а там на каждом шагу эти ёбаные if-else или switch, которые раздуваются, как жопа у ткачихи из сказки после пиршества!

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

А паттерн Стратегия говорит: «Да похуй! Выноси каждый алгоритм в отдельный класс, сделай у них общий интерфейс и подсовывай нужный, когда надо». И всё, блядь! Красота!

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

// 1. Интерфейс стратегии — общий контракт
interface PaymentStrategy {
    void pay(int amount);
}

// 2. Конкретные стратегии — каждая живёт отдельно
class CreditCardPayment implements PaymentStrategy {
    private String cardNumber;
    public CreditCardPayment(String cardNumber) { this.cardNumber = cardNumber; }

    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using Credit Card " + cardNumber.substring(12));
    }
}

class PayPalPayment implements PaymentStrategy {
    private String email;
    public PayPalPayment(String email) { this.email = email; }

    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using PayPal account " + email);
    }
}

// 3. Контекст — тот, кто использует стратегию
class ShoppingCart {
    private PaymentStrategy paymentStrategy;

    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy; // Подсунули нужную стратегию
    }

    public void checkout(int totalAmount) {
        paymentStrategy.pay(totalAmount); // Выполнили
    }
}

// 4. Использование
public class Main {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();

        // Выбрали карту
        cart.setPaymentStrategy(new CreditCardPayment("1234-5678-9012-3456"));
        cart.checkout(100); // Paid 100 using Credit Card 3456

        // А потом резко передумали — дай PayPal!
        cart.setPaymentStrategy(new PayPalPayment("user@example.com"));
        cart.checkout(50); // Paid 50 using PayPal account user@example.com
    }
}

Видишь? Ни одного if! Никакого «если карта — то так, если PayPal — то эдак». Просто подменил объект — и алгоритм поменялся, как перчатки, блядь. Это и есть динамическая смена стратегии на лету.

Зачем это всё, спросишь?
Да чтобы не превращать код в помойку, где всё завязано на условия! Каждая стратегия живёт в своём классе — её можно тестировать отдельно, расширять, не боясь сломать остальное. Добавил новую платёжку? Создал новый класс, реализовал интерфейс — и вперёд, без правки существующей логики. Это и есть тот самый принцип открытости/закрытости — открыто для расширения, но закрыто для модификации, ёпта!

Где применяется? Да везде, блядь!
— Платёжные системы (как в примере)
— Алгоритмы сортировки или сжатия (выбрал стратегию — получил результат)
— Логика расчёта доставки (почта, курьер, самовывоз)
— Валидация данных (разные правила для разных типов пользователей)

Короче, если видишь, что у тебя в коде расплодились условные ветки на одну и ту же операцию — это верный признак, что пора, сука, внедрять Стратегию. Иначе потом будешь как тот Герасим — молча страдать, глядя на утонувшую в if-else логику.