Стратегия
Дмитрий БахтенковПаттерн "Стратегия", несомненно, является самым главным поведенческим паттерном. Многие его используют, даже не подозревая об этом, а ещё он лежит в основе некоторых других паттернов проектирования. Например можно сказать, что фабричный метод - это стратегия порождения объектов.
Когда и зачем применять?
Этот паттерн применяется, когда нам необходимо создать взаимозаменяемые алгоритмы, и менять их в зависимости от ситуации. Рассмотрим несколько примеров.
Случай 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