Course program← Объектно ориентированное программирование
1История ООП
2Объект и Класс
3Базовые концепции ООП
4Принцип проектирования GRASP
5Принцип проектирования SOLID
6Паттерны GOF

Strategy (Стратегия)

This lesson is not translated yet – the Russian original is below.

Экран оплаты с одним слотом: в него включён модуль оплаты картой, рядом лежат сменные модули – терминал, QR-код и кошелёк

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

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

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

PlantUML-диаграмма

UML диаграмма Strategy

// Интерфейс стратегии
public interface IPaymentStrategy
{
    void Pay(decimal amount);
}

// Конкретные стратегии
public class CreditCardPayment : IPaymentStrategy
{
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Оплата {amount:C} банковской картой.");
    }
}

public class PayPalPayment : IPaymentStrategy
{
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Оплата {amount:C} через PayPal.");
    }
}

public class CryptoPayment : IPaymentStrategy
{
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Оплата {amount:C} в криптовалюте.");
    }
}

// Контекст
public class PaymentContext
{
    private IPaymentStrategy _strategy;

    public PaymentContext(IPaymentStrategy strategy)
    {
        _strategy = strategy;
    }

    public void SetStrategy(IPaymentStrategy strategy)
    {
        _strategy = strategy;
    }

    public void ExecutePayment(decimal amount)
    {
        _strategy.Pay(amount);
    }
}

// Клиентский код
class Program
{
    static void Main()
    {
        var payment = new PaymentContext(new CreditCardPayment());
        payment.ExecutePayment(1000m);

        payment.SetStrategy(new PayPalPayment());
        payment.ExecutePayment(250m);

        payment.SetStrategy(new CryptoPayment());
        payment.ExecutePayment(0.05m);
    }
}
// Интерфейс стратегии
class IPaymentStrategy {
public:
    virtual void Pay(double amount) const = 0;
    virtual ~IPaymentStrategy() = default;
};

class CreditCardPayment : public IPaymentStrategy {
public:
    void Pay(double amount) const override {
        std::cout << "Оплата " << amount << " банковской картой." << std::endl;
    }
};

class PayPalPayment : public IPaymentStrategy {
public:
    void Pay(double amount) const override {
        std::cout << "Оплата " << amount << " через PayPal." << std::endl;
    }
};

class CryptoPayment : public IPaymentStrategy {
public:
    void Pay(double amount) const override {
        std::cout << "Оплата " << amount << " в криптовалюте." << std::endl;
    }
};

// Контекст
class PaymentContext {
public:
    explicit PaymentContext(std::shared_ptr<IPaymentStrategy> strategy)
        : strategy_(std::move(strategy)) {}

    void SetStrategy(std::shared_ptr<IPaymentStrategy> strategy) {
        strategy_ = std::move(strategy);
    }

    void ExecutePayment(double amount) const { strategy_->Pay(amount); }

private:
    std::shared_ptr<IPaymentStrategy> strategy_;
};
// Стратегия – обычный интерфейс, реализации о нём даже не знают.
type PaymentStrategy interface {
	Pay(amount float64)
}

type CreditCard struct{}

func (CreditCard) Pay(amount float64) { fmt.Printf("Оплата %.2f банковской картой.\n", amount) }

type PayPal struct{}

func (PayPal) Pay(amount float64) { fmt.Printf("Оплата %.2f через PayPal.\n", amount) }

type Crypto struct{}

func (Crypto) Pay(amount float64) { fmt.Printf("Оплата %.2f в криптовалюте.\n", amount) }

type PaymentContext struct {
	strategy PaymentStrategy
}

func (c *PaymentContext) SetStrategy(s PaymentStrategy) { c.strategy = s }

func (c *PaymentContext) Execute(amount float64) { c.strategy.Pay(amount) }

func main() {
	payment := &PaymentContext{strategy: CreditCard{}}
	payment.Execute(1000)

	payment.SetStrategy(PayPal{})
	payment.Execute(250)

	payment.SetStrategy(Crypto{})
	payment.Execute(0.05)
}
from typing import Protocol

class PaymentStrategy(Protocol):
    def pay(self, amount: float) -> None: ...

class CreditCard:
    def pay(self, amount: float) -> None:
        print(f"Оплата {amount:.2f} банковской картой.")

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

class Crypto:
    def pay(self, amount: float) -> None:
        print(f"Оплата {amount:.2f} в криптовалюте.")

class PaymentContext:
    def __init__(self, strategy: PaymentStrategy) -> None:
        self.strategy = strategy

    def execute(self, amount: float) -> None:
        self.strategy.pay(amount)

payment = PaymentContext(CreditCard())
payment.execute(1000)

payment.strategy = PayPal()
payment.execute(250)

payment.strategy = Crypto()
payment.execute(0.05)
interface PaymentStrategy {
    pay(amount: number): void;
}

class CreditCard implements PaymentStrategy {
    pay(amount: number): void {
        console.log(`Оплата ${amount} банковской картой.`);
    }
}

class PayPal implements PaymentStrategy {
    pay(amount: number): void {
        console.log(`Оплата ${amount} через PayPal.`);
    }
}

class Crypto implements PaymentStrategy {
    pay(amount: number): void {
        console.log(`Оплата ${amount} в криптовалюте.`);
    }
}

class PaymentContext {
    constructor(private strategy: PaymentStrategy) {}

    setStrategy(strategy: PaymentStrategy): void {
        this.strategy = strategy;
    }

    execute(amount: number): void {
        this.strategy.pay(amount);
    }
}

const payment = new PaymentContext(new CreditCard());
payment.execute(1000);

payment.setStrategy(new PayPal());
payment.execute(250);

payment.setStrategy(new Crypto());
payment.execute(0.05);
interface PaymentStrategy {
    void pay(double amount);
}

class CreditCardPayment implements PaymentStrategy {
    public void pay(double amount) {
        System.out.printf("Оплата %.2f банковской картой.%n", amount);
    }
}

class PayPalPayment implements PaymentStrategy {
    public void pay(double amount) {
        System.out.printf("Оплата %.2f через PayPal.%n", amount);
    }
}

class CryptoPayment implements PaymentStrategy {
    public void pay(double amount) {
        System.out.printf("Оплата %.2f в криптовалюте.%n", amount);
    }
}

class PaymentContext {
    private PaymentStrategy strategy;

    PaymentContext(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    void execute(double amount) {
        strategy.pay(amount);
    }
}

public class Program {
    public static void main(String[] args) {
        PaymentContext payment = new PaymentContext(new CreditCardPayment());
        payment.execute(1000);

        payment.setStrategy(new PayPalPayment());
        payment.execute(250);

        payment.setStrategy(new CryptoPayment());
        payment.execute(0.05);
    }
}
// Отдельный класс на каждую стратегию не нужен: в Kotlin стратегия – это тип функции.
typealias PaymentStrategy = (amount: Double) -> Unit

val creditCard: PaymentStrategy = { amount -> println("Оплата $amount банковской картой.") }
val payPal: PaymentStrategy = { amount -> println("Оплата $amount через PayPal.") }
val crypto: PaymentStrategy = { amount -> println("Оплата $amount в криптовалюте.") }

class PaymentContext(var strategy: PaymentStrategy) {
    fun execute(amount: Double) = strategy(amount)
}

fun main() {
    val payment = PaymentContext(creditCard)
    payment.execute(1000.0)

    payment.strategy = payPal
    payment.execute(250.0)

    payment.strategy = crypto
    payment.execute(0.05)
}

Плюсы и минусы

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

Заключение

Strategy – поведенческий паттерн, который помогает выносить алгоритмы в отдельные классы и менять их динамически. Он широко используется в системах оплаты, маршрутизации, сортировке и других задачах, где нужно гибко выбирать способ выполнения действия.