Назад Вперед Зміст


Патерни поведінки

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

Патерн «Стратегія» (Strategy) визначає набір алгоритмів, інкапсулює кожен із них та забезпечує їхню взаємозамінність. У залежності від ситуації ми легко можемо замінити один алгоритм іншим — при цьому об’єкт, який його використовує, не має знати про конкретну реалізацію.

Коли застосовувати Стратегію?

Формально патерн «Стратегія» можна відобразити через UML‑схему:

Елементи шаблону

  1. IStrategy — стратегія: описує загальний для всіх алгоритмів інтерфейс.
  2. Context — контекст, який використовує цей інтерфейс для виклику конкретного алгоритму, визначеного в ConcreteStrategy.
  3. ConcreteStrategy — конкретна стратегія: реалізує алгоритм, що описаний в інтерфейсі IStrategy.
  4. Context:
    1. налаштовується об’єктом класу ConcreteStrategy;
    2. зберігає посилання на IStrategy;
    3. може надавати інтерфейс для доступу стратегій до даних контексту.

Приклад 1 (реалізація патерна Стратегія)

Інтерфейс стратегії

Є інтерфейс IStrategy, що визначає метод Execute(), реалізований у конкретних стратегіях ConcreteStrategy1 і ConcreteStrategy2, кожна з яких виконує Execute() по‑своєму.

using System;

// Інтерфейс стратегії
public interface IStrategy
{
    void Execute();
}

// Конкретна стратегія 1
public class ConcreteStrategy1 : IStrategy
{
    public void Execute()
    {
        Console.WriteLine("Виконання стратегії 1");
    }
}

// Конкретна стратегія 2
public class ConcreteStrategy2 : IStrategy
{
    public void Execute()
    {
        Console.WriteLine("Виконання стратегії 2");
    }
}

Клас Context містить поточну стратегію, має методи для її зміни та виконання.

using System;// Контекст, який використовує стратегію
public class Context
{
    private IStrategy _strategy;

    // Конструктор з встановленням початкової стратегії
    public Context(IStrategy strategy)
    {
        _strategy = strategy;
    }

    // Метод для зміни стратегії під час виконання
    public void SetStrategy(IStrategy strategy)
    {
        _strategy = strategy;
    }

    // Метод, який виконує обрану стратегію
    public void ExecuteStrategy()
    {
        _strategy.Execute();
    }
}

У Main створюється контекст зі стратегією №1, виконується вона, потім замінюється на стратегію №2 і виконується знову.

class Program
{
    static void Main(string[] args)
    {
        // Створення контексту з першою стратегією
        Context context = new Context(new ConcreteStrategy1());

        // Виконання стратегії
        context.ExecuteStrategy();

        // Зміна стратегії на другу
        context.SetStrategy(new ConcreteStrategy2());
        
        // Виконання стратегії
        context.ExecuteStrategy();
    }
}

Приклад 2 (стратегія обчислення площі фігур)

В C# іноді використовують @base як ім’я змінної, щоб уникнути конфліктів із ключовим словом base. У прикладі змінна @base означає основу трикутника.

Створюється інтерфейс IShapeAreaCalculator з методом CalculateArea(), а потім реалізується три конкретні стратегії для кола, прямокутника і трикутника.

using System;

// Інтерфейс стратегії для обчислення площі фігури
public interface IShapeAreaCalculator
{
    double CalculateArea();
}

// Конкретна стратегія для обчислення площі круга
public class CircleAreaCalculator : IShapeAreaCalculator
{
    private double _radius;
    
    public CircleAreaCalculator(double radius)
    { 
        _radius = radius; 
    }
    
    public double CalculateArea()
    { 
        return Math.PI * _radius * _radius; 
    }
}

// Конкретна стратегія для обчислення площі прямокутника
public class RectangleAreaCalculator : IShapeAreaCalculator
{
    private double _width;
    private double _height;
    
    public RectangleAreaCalculator(double width, double height)
    { 
        _width = width; 
        _height = height; 
    }
    
    public double CalculateArea()
    { 
        return _width * _height; 
    }
}

// Конкретна стратегія для обчислення площі трикутника
public class TriangleAreaCalculator : IShapeAreaCalculator
{
    private double _base;
    private double _height;
    
    public TriangleAreaCalculator(double @base, double height)
    { 
        _base = @base; 
        _height = height; 
    }
    
    public double CalculateArea()
    { 
        return 0.5 * _base * _height; 
    }
}

У Main створюються екземпляри стратегій, викликається CalculateArea() для кожної фігури.

class Program
{
    static void Main(string[] args)
    {
        // Створюємо різні фігури
        IShapeAreaCalculator circleCalculator = new CircleAreaCalculator(5);
        IShapeAreaCalculator rectangleCalculator = new RectangleAreaCalculator(4, 6);
        IShapeAreaCalculator triangleCalculator = new TriangleAreaCalculator(3, 8);

        // Обчислюємо площі фігур та виводимо результати
        Console.WriteLine("Площа круга: " + circleCalculator.CalculateArea());
        Console.WriteLine("Площа прямокутника: " + rectangleCalculator.CalculateArea());
        Console.WriteLine("Площа трикутника: " + triangleCalculator.CalculateArea());
    }
}

Приклад 3 (з книги Фрімена. Патерни проектування)

Практика: Стратегія

Постановка задачі:

Створити модель ставка з качками різних видів.

Реалізація: Перший спосіб (класичне ООП)

Визначається суперклас Duck, а на його основі — підкласи конкретних качок.

Компанія вирішила додати всім качкам здатність літати.

У результаті по екрану летіли навіть гумові качки — метод fly() був не підходить всім підкласам. Додана поведінка виявилась непридатною для частини класів.

Потім додали качок, які не літають і не крякають.

Унаслідок цього стало зрозуміло, що спадкування не вирішує проблему — адже продукт оновлюється щопівроку, і доводиться переозначувати fly() і quack() для кожного нового класу.

Реалізація: Другий спосіб — із застосуванням стратегії

Нова ідея:

Відділяємо змінні компоненти, делегуючи поведінку з Duck:

Інтеграція поведінки з класом Duck

У новій структурі є принципова особливість: клас Duck тепер делегує аспекти поведінки через спеціальні класи, а не реалізує їх безпосередньо.

Ось як це відбувається:

  1. У клас Duck додаються дві змінні екземпляру: IFlyBehavior flyBehavior та IQuackBehavior quackBehavior, оголошені як інтерфейси. На етапі виконання кожному об’єкту присвоюється конкретна реалізація поведінки (наприклад, FlyWithWings або Squeak).
  2. Методи fly() та quack() видаляються з Duck і підкласів, оскільки реалізація переміщується до відповідних класів поведінки. У Duck замість них використовуються performFly() та performQuack().

Перейти до програмного коду


Назад Вперед Зміст