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