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

Паттерн 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);
    }
}
// Стратегия — обычный интерфейс, реализации о нём даже не знают.
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 — поведенческий паттерн, который помогает выносить алгоритмы в отдельные классы и менять их динамически. Он широко используется в системах оплаты, маршрутизации, сортировке и других задачах, где нужно гибко выбирать способ выполнения действия.