Паттерны - это проверенные решения; стандартизация кода; общий программистский словарь.
Стратегия - это паттерн, который позволяет определить семейство алгоритмов, инкапсулировать каждый из них и обеспечить их взаимозаменяемость.
Наш опыт
У нас есть база IDM, которая находится в РФ, консолидирует информацию и управляет N-ным количеством других баз, называемых WE и находящиеся в других странах. По сути IDM агрегирует и управляет данными других баз. Эти базы могут быть разными — с разными версиями и бизнес-логикой. IDM выступает в роли центра, откуда приходят команды:
- чтение данных
- проверка доступности
- обновление служебной информации
- изменение данных
- и т.д.
Раздумывая над возможными вариантами команд, мы пришли к выводу, что для выполнения любой команды из IDM базам WE нужно:
- прочитать команду
- понять что нужно сделать
- выполнить команду
- отправить ответ/результат
Звучит просто: IDM говорит “сделай”, а WE постоянно проверяют наличие команды и, в случае её наличия, выполняют команду. Для выполнения используют определенный алгоритм, нужный конкретной команде. При этом механизм чтения команды и отправки, по сути, не меняется. А алгоритмы могут сильно отличаться, меняться и зависеть от версии WE. Соответственно нужен подход, который позволит легко дополнять и менять алгоритмы выполнения команд, не меняя чтение и отправку. Именно здесь на помощь приходит паттерн Стратегия.
У нас есть МенеджерКоманд - он является Клиентом. В наших базах он постоянно читает кафку в поисках команд. Если находит подходящую, тогда использует свой метод Processor, передает в него Message, создает контекст, выясняет по Message, какую стратегию нужно использовать, помещает стратегию в контекст и запускает у контекста метод начала работы - Start().
Procedure Processor(Message) Export
Headers = EnterpriseServiceBusClientServer.MessageHeadersInStructure(Message.Headers);
// проверяем нужно ли выполнять эту команду
If MessageForAnotherCountry(Headers) Then
Return;
EndIf;
// создаем контекст
Context = DataProcessors.IDM_Context.Create();
// создаем стратегию
Strategy = Strategy(Message.Value,
Headers,
StrategyBaseName(Headers.Strategy),
Number(Headers.Version),
Number(Headers.Downgrading));
// Помещаем в контекст стратегию и запускаем работу контекста
Context.SetStategy(Strategy).Start();
EndProcedure
Контекст в свою очередь получает какую-то Стратегию и вызывает у неё нужные методы в правильной последовательности, проверяет успешность их выполнения. Для подключения Стратегии к Контексту, внутри Стратегии нужно реализовать определенный интерфейс - иначе ничего работать не будет.
Function Start() Export
Result = Strategy.ProcessorRecipient();
If Not Result.IsSuccess Then
IDM_Common.SendError(Result);
EndIf;
Result = Strategy.ProcessorSender();
If Not Result.IsSuccess Then
IDM_Common.SendError(Result);
EndIf;
Return ThisObject;
EndFunction
Стратегии выполняют различные алгоритмы в зависимости от целей. Например, метод ProcessorRecipient() может: корректно расшифровывать сообщение, создать объекты в базе, выполнить запрос, или просто вернуть “успех” в контекст и ничего не делать. Затем Контекст вызывает ProcessorSender(), который собирает ответ, отправить его, чистит хвосты и тоже возвращает результат. Каждую стратегию мы можем настраивать отдельно - единственное требование: реализация наш Интерфейса (два упомянутых выше метода и структура ответа).
Function ProcessorRecipient() Export
// Тут должна быть бизнес-логика. Или например:
// ни чего не делаем возвращаем результат, что метод отработал корректно.
Return IDM_Common.Result(True);
EndFunction
Function ProcessorSender() Export
// Тут бизнес-логика
// отправка сообщения обратна в кафку
// возвращаем результат отправки метода
Return Result;
EndFunction
Что мы получили?
- Лёгкое масштабирование. Добавить новую команду - это просто создать новый объект, реализующий стандартный интерфейс Стратегии. Ни МенеджерКоманд, ни Контекст при этом менять не нужно. Мы соблюдаем SOLID, а именно принцип Открытости/Закрытости (OCP) - система открыта для расширения (добавление новых стратегий), но закрыта для модификации (основной код контекста и менеджера команд не меняется).
- Код стал чище и понятнее. Без паттерна и SOLID пришлось бы писать гигантские Если ... ИначеЕсли ... реализуя внутри одного метода выполнение всех типов команд. Стратегия разделяет это на отдельные объекты - так проще читать и поддерживать.
- Более спокойное изменение алгоритмов. Стратегия следует принципу единственной ответственности (SRP): изменение одной стратегии не влечет изменений в других частях системы. Это также отделяет бизнес-логику от работы с брокером сообщений, обработкой ошибок и логированием. Контекст работает с абстракциями (интерфейсом), а стратегии реализуют этот интерфейс, не зная, кто их использует — это принцип инверсии зависимостей (DIP).
- Проще тестировать. Конкретные стратегии - являются самостоятельными объектами с четким интерфейсом. Это дает возможность, подкидывать контекст и проверять ожидаемый результат. Спасибо коллегам из YAxUnit за удобный фреймворк.
Коллеги, Стратегия - это паттерн, который облегчает нам жизнь. Разработка становится проще. Попробуйте - вам понравится :)
Какие ещё интересные штуки мы используем и как они нам помогают, вы можете послушать на моем докладе, который я готовлю к Infostart 2025. Проголосовать и тем самым поддержать автора, можно тут: https://event.infostart.ru/2025/agenda/2443472/
Вступайте в нашу телеграмм-группу Инфостарт