Всем привет, друзья. Меня зовут Alex, я профессиональный разработчик и создатель программных продуктов в веб индустрии. Много лет изучаю языки, делюсь опытом с другими.  Сегодня хочу с вами поговорить про шаблон проектирования Стратегия (Strategy). Постараюсь донести до вас принципы и суть шаблона без воды, и покажу как его применять на практике.  Design patter Strategy  или шаблон проектирования Стратегия  относится к поведенческим шаблонам проектирования. Его задача - выделить схожие алгоритмы, решающие конкретную задачу. Реализация алгоритмов выносится в отдельные классы и предоставляется возможность выбирать алгоритмы во время выполнения программы. Шаблон дает возможность в процессе выполнения выбрать стратегию (алгоритм, инструмент, подход) решения задачи.   Рассмотрим задачи, при решении которых можно применять такой подход.   Представьте, что перед вами стоит задача написать веб-портал по поиску недвижимости. MVP (Minimum Viable Product) или минимально работающий продукт был спроектирован и приоритизирован вашей командой Product Manager’ов и на портале должен появиться функционал для покупателей квартир. То есть целевые пользователи вашего продукта в первую очередь - это те, кто ищет себе новое жилье для покупки. Одной из самых востребованных функций должна быть возможность:  Выбрать область на карте, где покупатель желает приобрести жилье И указать ценовой диапазон цен на квартиры для фильтрации. Первая версия вашего портала отлично справилась с поставленной задачей и пользователи могли без проблем искать, сужая свой поиск квартир по ценовом диапазону и выбранной географической области на карте. Ну и конечно вы хорошо постарались, как разработчик и все правильно сделали на ваш взгляд с точки зрения архитектуры кода, реализовали классы, которые ищет квартиры на продажу в вашей базе.  Но тут приходят к вам Product Manager'ы и говорят, что нужно добавить возможность искать и отображать недвижимость, которая сдается в аренду. У нас появляется еще один тип пользователя - арендаторы. Для арендаторов не так важно показывать фильтры по цене, им важно состояние квартиры, поэтому нужно отображать фотографии арендуемых квартир.  Следующей версией вашего продукта была значительная доработка текущей архитектуры и добавление нового функционала, чтобы можно было в зависимости от роли пользователя искать разные типы недвижимости и отображать разные элементы интерфейса - для покупателей фильтры по цене и географическое расположение, для арендаторов фотографии квартир.  Конечно на этом все не закончилось. В ближайших планах добавить функционал работы юридических лиц, функционал оплаты и бронирования квартир сразу на сайте. Дальше-больше - добавить возможность просматривать историю недвижимости, запрашивать пакет документов для сделки и связь с владельцами, оформление кредита и так далее.   Если функционал поиска и фильтрации с квартирами на продажу было довольно легко реализовать, то любые новые изменения вызывали много вопросов и головную боль по архитектуре. Пришлось очень многое менять в коде. Вы понимали, что любое изменение алгоритмов выдачи нужных квартир и элементов для отображения затрагивает основные базовые классы, в которых реализован весь функционал фильтрации. Основной функционал поиска квартир изначально был реализован в одном классе, при добавлении нового функционала этот класс разрастался, вы добавляли новые условия, новые ветвления, новые методы и функции.  Изменение такого класса, даже в случае исправление багов, сильно повышает риск сделать новые ошибки. Над данным функционалом работала целая команда разработчиков, любые изменения в итоге проходили долгую обкатку и тестирование, а изменения, добавляемые другими программистами в базовые классы, создавала новые проблемы и конфликты, релизы затягивались. Вы столкнулись со следующими проблемами:   Основной алгоритм поиска квартир был реализован в одном супер-классе  Алгоритм выбора и отображения элементов интерфейса был реализован в одном супер-классе  Изменения в этих классах, сделанные разными программистами, приводили к конфликтам и необходимости регрессивного тестирования Релизы продукта затягивались, время на разработку нового функционала увеличилась в несколько раз  Больше времени стало уходить на разработку, тестирование, появилось множество багов.  В данном примере мы имеем несколько алгоритмов для одной функции:   Поиск квартир с продажей  Поиск квартир в аренду  Отображение или нет различных наборов фильтров  Отображение различных элементов интерфейса - фотографии, кнопки бронирования, кнопки обратной связи и т.д.  Шаблон проектирования Стратегия - решает такую задачу. Он предлагает выделить семейство похожих алгоритмов, вынести их в отдельные классы. Это позволит без проблем изменять нужный алгоритм, расширять его, сводя к минимум конфликты разработки, зависимости от других классов и функционала. Вместо того, чтобы реализовывать алгоритм в едином классе, наш класс будет работать с объектами классов-стратегиями через объект-контекста и в нужным момент делегировать работу нужному объекту. Для смены алгоритма достаточно в нужным момент подставить в контекст нужный объект-стратегию.   Чтобы работа нашего класса была одинаковой для разного поведения, у объектов-стратегии должен быть общий интерфейс. Используя такой интерфейс вы делаете независимым наш класс-контекста от классов-стратегий.  Возвращаясь к нашему примеру с порталом по поиску недвижимости, мы должны вынести алгоритмы поиска недвижимости на продажу и аренду в отдельные классы-стратегии, где реализовать конкретные алгоритмы по поиску разных типов объектов. Аналогичное можно проделать и с классами работы с элементами интерфейса для различных видов пользователей.  В классах-стратегиях, реализующих поиск объектов недвижимости, будет определен только один метод doSearch(filters), принимающий в качестве аргумента фильтры, по которым будет совершен поиск конкретных объектов с использованием конкретного алгоритма.   Несмотря на то, что каждый класс-стратегия реализует поиск по своему алгоритму, с применением необходимых фильтров (продажа, аренда, загородный дом, жилая, не жилая и т.д.), не исключено, кстати, что они будут работать с разными базами - все это для веб-интерфейса, который отображает список найденных объектов, не имеет никакого значения. После того, как пользователь выбрал интересующий его тип недвижимости в фильтрах на сайте, будет происходить запрос в контроллер на backend, с экшеном получения данных по входящим фильтрам и типам пользователя.  Задача контроллера определить класс-стратегию и запросить у класса-контекста данные для отображения, передав ему известный набор фильтров. Класс-контекст в этой схеме  - это класс, которые реализует метод поиска квартир по заданным фильтрам. На диаграмме классов выше мы видим, что класс контекста определяет метод getData, и принимает аргументы filters. У него должен быть конструктор, принимающий активный в данный момент объект-стратегии и сеттер setStrategy, устанавливающий активную стратегию. Такой метод пригодится для случая, когда пользователь меняет тип искомого объекта, например, он ищет недвижимость на продажу и хочет снять квартиру.  Ниже рассмотрим пример, как решается описанная задача на языке GOlang. Первое что сделаем - определим интерфейс с методом doSearch:   Данный метод определяет общее поведение для конкретных алгоритмов, реализующих разные стратегии. Метод может принимать различные аргументы, позволяющие реализовать ветвления в ваших алгоритмах. В примера я передаю пользовательские фильтры с типом Map. Для реализации конкретных алгоритмов создаем два файла. В каждом файле определяется свой определяемый тип с базовым типом struct, реализующие интерфейс Strategy. Соответственно, в методы, определяемые интерфейсом для каждого алгоритма, будут передаваться пользовательские фильтры. Реализации выглядит следующим образом: Посмотрим на нашу диаграмму классов. Нам осталось реализовать класс-контекста и клиентский код вызова конкретных алгоритмов в нужным момент. Как это сделать? Для создания слоя класса-контекста реализуем исходник, реализующий: определяемый тип в базовым типом struct функцию initStrategy, инициализирующий стратегию по-умолчанию и пользовательские фильтры метод типа struct setStrategy, устанавливающий активную стратегию и функция getData, вызывающий конкретную стратегию и возвращаемый данные для показа пользователю. Последний шаг - посмотрим на клиентский код. Сначала инициализируется стратегия по-умолчанию, получаем данные из этой стратегии. Затем выставляем новую активную стратегию и повторно вызываем функцию получения данных getData. Как видим, каждый раз вызывается одна и та же функция файла-контекста (или класса-контекста в диаграмме классов) для получения данных рендеринга, отображаемых в дальнейшем пользователю. Для простоты я вывожу отладочную информацию в консоль, чтобы убедиться, что работают разные алгоритмы при вызовах. Вот код:  Вот вывод такого подхода: First implements strategy map[role:1]  Second implements strategy map[role:2]  Как видите, мы можем управлять вызовом разных алгоритмов в зависимости от контекста и пользовательских фильтров. Алгоритмы могут создавать ветвления в зависимости от входных фильтров и других параметров, переданных из клиентского кода в методы, реализующие конкретные алгоритмы.  Вот такой интересный пример.  Объектно-ориентированный подход можно посмотреть. например, в этом курсе. Там показан пример на PHP.  Напоследок поговорим когда применяется шаблон Strategy?  У вас есть множество похожих реализаций отличающихся незначительным поведением. Можно вынести отличающее поведение в классы-стратегии, а повторяющий код свести к единому классу-контекста.   Ваш алгоритм реализован в супер-классе с множественными условными операторами. Выделите блоки условных операторов в отдельные классы-стратегии, а управление вызовов нужных доверьте классу-контекста.   Конкретные стратегии позволяют инкапсулировать алгоритмы в своих конкретных классах. Используйте этот подход для снижения зависимостей от других классов.   В зависимости от ситуации вы можете менять стратегию выполнения задачи в процессе выполнения программы. Например, в зависимости от скорости интернета использовать разные стратегии-поведения, возвращающие разный набор данных для отображения страницы.  Друзья, мы познакомились с поведенческим шаблоном проектирования Strategy. Шаблон используется для выделения схожих алгоритмов, решающих конкретную задачу. Посмотрели с вами реализацию на языке GOlang, ознакомились в возможностями подхода и разобрали когда его лучше применять. Рад был с вами пообщаться, Alex Versus. Успехов! Full-Stack разработка WEB/IT-программных продуктов