Патерн «Стратегія» (Strategy) визначає набір алгоритмів, інкапсулює кожен із них та забезпечує їхню взаємозамінність. У залежності від ситуації ми легко можемо замінити один алгоритм іншим — при цьому об’єкт, який його використовує, не має знати про конкретну реалізацію.
Основна ідея: винести поведінку, що змінюється, в окремі класи-стратегії, а в основному класі зберігати посилання на інтерфейс цієї поведінки. Це дозволяє динамічно підставляти різні реалізації без зміни коду клієнта.
Ключові елементи:
Коли застосовувати Стратегію?
Формально патерн «Стратегія» можна відобразити через UML‑схему:

Є інтерфейс 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;
}
// Викликає метод Execute() поточної стратегії
public void ExecuteStrategy()
{
_strategy.Execute();
}
}
У Main створюється контекст зі стратегією №1, виконується вона, потім замінюється на стратегію №2 і виконується знову.
class Program
{
static void Main(string[] args)
{
// Створення контексту з першою стратегією
Context context = new Context(new ConcreteStrategy1());
// Виконання поточної стратегії (виведе "Виконання стратегії 1")
context.ExecuteStrategy();
// Зміна стратегії на другу
context.SetStrategy(new ConcreteStrategy2());
// Виконання нової стратегії (виведе "Виконання стратегії 2")
context.ExecuteStrategy();
}
}

В 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()
{
// Площа круга = π * r²
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; // @base – використання зарезервованого слова base як імені змінної
_height = height;
}
public double CalculateArea()
{
// Площа трикутника = 0.5 * основа * висота
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());
}
}

Створити модель ставка з качками різних видів.
Визначається суперклас Duck, а на його основі — підкласи конкретних качок.
Компанія вирішила додати всім качкам здатність літати.
У результаті по екрану летіли навіть гумові качки — метод fly() був не підходить всім підкласам. Додана поведінка виявилась непридатною для частини класів.
Потім додали качок, які не літають і не крякають.
Унаслідок цього стало зрозуміло, що спадкування не вирішує проблему — адже продукт оновлюється щопівроку, і доводиться переозначувати fly() і quack() для кожного нового класу.
Нова ідея:
IQuackable, бо крякати не всі качки;fly() з суперкласу Duck; визначити інтерфейс IFlyable з методом fly();IFlyable та мають метод fly().Відділяємо змінні компоненти, делегуючи поведінку з Duck:

У новій структурі є принципова особливість: клас Duck тепер делегує аспекти поведінки через спеціальні класи, а не реалізує їх безпосередньо.
Ось як це відбувається:
IFlyBehavior flyBehavior та IQuackBehavior quackBehavior, оголошені як інтерфейси. На етапі виконання кожному об’єкту присвоюється конкретна реалізація поведінки (наприклад, FlyWithWings або Squeak).fly() та quack() видаляються з Duck і підкласів, оскільки реалізація переміщується до відповідних класів поведінки. У Duck замість них використовуються performFly() та performQuack().
Додайте новий тип качки — качку-істеричку.
Качка-істеричка має такі особливості поведінки:
Що потрібно зробити:
ISwimBehavior (оскільки поведінка плавання теж може змінюватись).DiveIntoWater (реалізує IFlyBehavior) — качка пірнає;HystericalScream (реалізує IQuackBehavior) — істеричний крик;SwimOnSurface та SwimUnderwater (реалізують ISwimBehavior) — плавання на поверхні та під водою.Duck: додати поле swimBehavior, властивість SwimBehavior та метод PerformSwim().swimBehavior (наприклад, SwimOnSurface).HystericalDuck, у конструкторі якого встановити відповідні стратегії.Main продемонструвати поведінку качки-істерички та показати динамічну зміну поведінки (наприклад, замінити крик на писк).Повний код з виділенням змін можна переглянути за посиланням: Качка-істеричка (повний код).