Стратегия
Дмитрий БахтенковПаттерн "Стратегия", несомненно, является самым главным поведенческим паттерном. Многие его используют, даже не подозревая об этом, а ещё он лежит в основе некоторых других паттернов проектирования. Например можно сказать, что фабричный метод - это стратегия порождения объектов.

Когда и зачем применять?
Этот паттерн применяется, когда нам необходимо создать взаимозаменяемые алгоритмы, и менять их в зависимости от ситуации. Рассмотрим несколько примеров.
Случай 0:
Когда наше хайповое веб-приложение находится в режиме отладки, логгирование происходит только в консоль. А когда оно на проде, все логи должны сохраняться в специальный файл или в базу данных.
Интересная ситуация 1:
Если клиентом вашего крутого облачного сервиса является российская компания, ваше приложение должно взаимодействовать с яндекс-облаком, а если ваш клиент из Европы - с облаком гугла
Реальный кейс 2:
Вашим приложением пользуется много компаний, и большинство из них хотят интеграцию с 1С, но некоторые пользуются системой SAP, а у третьих вообще самописная система. Чтобы удовлетворить всех, вы делаете реализации как с 1С, так и с другими системами, а в процессе выполнения программы просто подставляете нужный модуль в зависимости от данных компании-клиента
В чём суть?
В первую очередь, необходимо создать интерфейс, в котором вы опишете методы, которые будут вызывать ваши алгоритмы:
interface IStrategy
{
void Execute();
}
Затем нужно написать столько реализаций интерфейса, сколько у вас есть вариантов использования стратегии.
class FirstStrategy : IStrategy
{
public void Execute()
{
// логика первого алгоритма
}
}
class SecondStrategy : IStrategy
{
public void Execute()
{
// логика второго алгоритма
}
}
Затем, с помощью отношения агрегации мы можем использовать нашу стратегию. Класс, который это делает, назовём контекстом стратегии, или "StrategyContext":
class StrategyContext
{
private IStrategy _strategy;
public StrategyContext(IStrategy strategy)
{
_strategy = strategy;
}
public void ExecuteContext
{
// какая-то логика
// вызываем метод нашей стратегии
_strategy.Execute();
// снова какая-то логика
}
}
На диаграмме классов UML это будет выглядеть следующим образом:

А может лучше реальный пример?
Предлагаю написать свой консольный калькулятор с логгированием, где мы будем использовать паттерн "Стратегия".
Задача
Необходимо написать программу, которая будет принимать на вход два числа и знак математической операции, и в зависимости от знака выполнять с этими числами математические действия. Реализовать логгирование всех операций, с записью даты выполнения операции. Для логгирования ошибок сделать отдельную метку. Реализовать логгирование как в файл, так и в консольный интерфейс - пользователь должен устанавливать необходимый тип логгирования при запуске программы.
Выделение сущностей
Основная сущность, которая будет выполнять математические операции - Calculator. У калькулятора будет 4 метода: сложить, вычесть, разделить, умножить.
Ещё у нас есть два логгера, одним из которых будет пользоваться наш калькулятор.
Ну и метод Main в классе Program - будет обрабатывать пользовательский ввод и вызывать действия калькулятора.
Проектирование

Стратегия будет заключаться в том, что в конструктор класса Calculator мы будем подставлять одну из реализаций интерфейса ILogger, а методы калькулятора будут вызывать методы логгера (процесс, когда один объект вызывает методы у другого объекта, называется посылкой сообщений).
Код
- Определим интерфейс ILogger:
public interface ILogger
{
public void LogError(string message);
public void LogInfo(string message);
}
- Определим реализацию консольного логгера:
public void LogError(string message)
{
// балуемся с цветом текста в консоли, так как
// логгирование должно быть красивым
Console.ForegroundColor = ConsoleColor.Red;
// выводим сообщение с типом события и текущей датой
Console.Write($"{DateTime.Now} | ERROR: ");
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine(message);
}
public void LogInfo(string message)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.Write($"{DateTime.Now} | INFO: ");
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine(message);
}
- Определим реализацию файлового логгера:
public class FileLogger : ILogger
{
private const string FileName = "log.txt";
public void LogError(string message)
{
// открываем поток к файлу, чтобы добавить в него
// сообщение о событии. Директива using сама его потом закроет.
using (var stream = new StreamWriter(FileName, true))
{
var text = $"{DateTime.Now} | ERROR: {message}";
stream.WriteLine(text);
}
}
public void LogInfo(string message)
{
using (var stream = new StreamWriter(FileName, true))
{
var text = $"{DateTime.Now} | INFO: {message}";
stream.WriteLine(text);
}
}
}
- Теперь можно сделать калькулятор:
public class Calculator
{
private readonly ILogger _logger;
public Calculator(ILogger logger)
{
_logger = logger;
}
public int CalculateSum(int a, int b)
{
_logger.LogInfo($"{a} + {b} = {a+b}");
return a + b;
}
public int CalculateSubtract(int a, int b)
{
_logger.LogInfo($"{a} - {b} = {a-b}");
return a - b;
}
public int CalculateMultiple(int a, int b)
{
_logger.LogInfo($"{a} * {b} = {a*b}");
return a * b;
}
public int CalculateDivide(int a, int b)
{
if (b == 0)
{
_logger.LogError($"Divide {a} by zero!!!");
return 0;
}
_logger.LogInfo($"{a} / {b} = {a/b}");
return a / b;
}
}
Тут стоит сказать пару слов об интерфейсах. Мы можем спокойно приводить реализации интерфейса к самому типу интерфейса, это называется апкаст (upcast). Например:
ILogger logger = new ConsoleLogger().
- Ну и сам метод Main:
static void Main(string[] args)
{
Calculator calculator;
Console.WriteLine("Введите 1 для консольного логгирования или нажмите Enter для использования файлового логгера");
var input = Console.ReadLine();
// инициализируем наш калькулятор необходимой реализацией логгера
if (input == "1")
calculator = new Calculator(new ConsoleLogger());
else
calculator = new Calculator(new FileLogger());
Console.WriteLine("Введите первое число: ");
var first = int.Parse(Console.ReadLine());
Console.WriteLine("Введите второе число: ");
var second = int.Parse(Console.ReadLine());
Console.WriteLine("Введите действие: ");
var action = Console.ReadLine();
// проверяем введённое действие
switch (action)
{
case "+":
Console.WriteLine("Result: " + calculator.CalculateSum(first, second));
break;
case "-":
Console.WriteLine("Result: " + calculator.CalculateSubtract(first, second));
break;
case "*":
Console.WriteLine("Result: " + calculator.CalculateMultiple(first, second));
break;
case "/":
Console.WriteLine("Result: " + calculator.CalculateDivide(first, second));
break;
default:
Console.WriteLine("Некорректное действие!");
break;
}
}
- Тестируем:




Делаем выводы:
- Мы научились использовать паттерн "Стратегия". Это самый крутой поведенческий паттерн, так как в том или ином виде он используется очень часто, а также является основой для других паттернов, например - например, паттерн "Фабричный метод" можно считать просто стратегией порождения объектов
- Он используется, когда необходимо в процессе выполнения программы необходимо просто подменять различные реализации одного алгоритма.
- В его основе лежат такие отношения между классами и объектами, как агрегация и реализация.
- Апкаст (upcast) - процесс приведения конкретного типа к более абстрактному, такому как интерфейс.
__________________
Исходники: ГитХаб
С вами был FlexCode