Паттерн Стратегия (Strategy) представляет шаблон проектирования, который определяет набор алгоритмов, инкапсулирует каждый из них и обеспечивает их взаимозаменяемость. В зависимости от ситуации мы можем легко заменить один используемый алгоритм другим. При этом замена алгоритма происходит независимо от объекта, который использует данный алгоритм.
Когда использовать стратегию?
- Когда есть несколько родственных классов, которые отличаются поведением. Можно задать один основной класс, а разные варианты поведения вынести в отдельные классы и при необходимости их применять
- Когда необходимо обеспечить выбор из нескольких вариантов алгоритмов, которые можно легко менять в зависимости от условий
- Когда необходимо менять поведение объектов на стадии выполнения программы
- Когда класс, применяющий определенную функциональность, ничего не должен знать о ее реализации
Формально паттерн Стратегия можно выразить следующей схемой UML:
Формальное определение паттерна на языке C# может выглядеть следующим образом:
public interface IStrategy
{
void Algorithm();
}
public class ConcreteStrategy1 : IStrategy
{
public void Algorithm()
{}
}
public class ConcreteStrategy2 : IStrategy
{
public void Algorithm()
{}
}
public class Context
{
public IStrategy ContextStrategy { get; set; }
public Context(IStrategy _strategy)
{
ContextStrategy = _strategy;
}
public void ExecuteAlgorithm()
{
ContextStrategy.Algorithm();
}
}
Участники
Как видно из диаграммы, здесь есть следующие участники:
- Интерфейс IStrategy, который определяет метод
Algorithm()
. Это общий интерфейс для всех реализующих его алгоритмов. Вместо интерфейса здесь также можно было бы использовать абстрактный класс. - Классы ConcreteStrategy1 и ConcreteStrategy, которые реализуют интерфейс IStrategy, предоставляя свою версию метода
Algorithm()
. Подобных классов-реализаций может быть множество. - Класс Context хранит ссылку на объект IStrategy и связан с интерфейсом IStrategy отношением агрегации.
В данном случае объект IStrategy заключена в свойстве ContextStrategy, хотя также для нее можно было бы определить приватную переменную, а для динамической установки использовать специальный метод.
Теперь рассмотрим конкретный пример. Существуют различные легковые машины, которые используют разные источники энергии: электричество, бензин, газ и так далее. Есть гибридные автомобили. В целом они похожи и отличаются преимущественно видом источника энергии. Не говоря уже о том, что мы можем изменить применяемый источник энергии, модифицировав автомобиль. И в данном случае вполне можно применить паттерн стратегию:
class Program
{
static void Main(string[] args)
{
Car auto = new Car(4, "Volvo", new PetrolMove());
auto.Move();
auto.Movable = new ElectricMove();
auto.Move();
Console.ReadLine();
}
}
interface IMovable
{
void Move();
}
class PetrolMove : IMovable
{
public void Move()
{
Console.WriteLine("Перемещение на бензине");
}
}
class ElectricMove : IMovable
{
public void Move()
{
Console.WriteLine("Перемещение на электричестве");
}
}
class Car
{
protected int passengers; // кол-во пассажиров
protected string model; // модель автомобиля
public Car(int num, string model, IMovable mov)
{
this.passengers = num;
this.model = model;
Movable = mov;
}
public IMovable Movable { private get; set; }
public void Move()
{
Movable.Move();
}
}
В данном случае в качестве IStrategy выступает интерфейс IMovable, определяющий метод Move()
. А реализующий этот интерфейс семейство алгоритмов представлено классами ElectricMove и PetroleMove. И данные алгоритмы использует класс Car.
Пример применения: переключаться между различными эвристиками для поиска A *, в зависимости от того, в какой местности вы находитесь, или, возможно, даже использовать одну и ту же среду A * для выполнения поиска пути и более общего планирования