Интересности      Книги      Утилиты    

4 июля 2017 г.

Цепочка обязанностей, chain of responsibilities

11-07-05%20Chain%20of%20Responsibility[1]
Понимание паттернов – это одна из ступеней, следующая – это правильное их применение, причем без перегибов и изменение их под свои нужды когда придется. Про паттерны написано достаточно много. Среди ресурсов откуда можно почерпнуть информацию о них – книги серии Head First Design Patterns вместе с книгой Object-Oriented Analysis and Design из той же серии, или известная книга “банды четырех” Design Patterns: Elements of Reusable Object-Oriented Software, хотя она достаточно сложная при чтении, или вот еще одна, написанная моим коллегой, и простая к пониманию “Дизайн-патерни — просто, як двері”.
Информации достаточно много. Но за последнее время мне chain of responsibilities применялся у меня достаточно часто и позволили неплохо отрефакторить как legacy так и сложный  связанный код. Возможно если поделится типичным сценариями в которых он применился с другими – кому-то это будет полезно. Поэтому я рассмотрю пару сценариев, в которых его применил, и собственно сам базовый код.

 

Сценарий 1 – Grid, data driven application

В Enterprise секторе приложений очень много приложений финансового плана, также как и для планирования и отчетности. Идеальный инструмент для ребят из соответствующих отделов – Excel натравленный на большие источники данных и кубы. Почему-то обязательные атрибуты таких приложений – grid максимально близкий к Excel, импорт/экспорт в Excel. Короче, народ создает облегчённые аналоги Excel для таких нужд, некоторые пишут свои расширения на VSTO, сейчас вот будут на Office 2013 Web Apps тоже самое делать.
Так вот есть приложение, центральной частью которого является грид, на элементы которого (ячейка, столбец, рядок и т.п.)накладываются определенные формулы, цвет, свойства и пр.
Для особо сложных гридов, классы начали быть очень сложными и связанными, в классах появилось много дублирующегося кода, излишние итерации по элементам грида. Класс стал просто наводнен почти одинаковыми сложными методами а-ля SetCellsProductionProperties, SetCellsRetailPresentation, ApplyRowFormating и т.п. Все методы содержали почти одно и тоже: идентификаторы столбцов, и строк к которым нужно что-то применить (подсветить негативные значения, изменить формулу, сделать ячейку “только для чтения”), и итерации по гриду с применением к элементам которые удовлетворяют условию, т.е. их можно идентифицировать по строке/столбцу и элемент удовлетворят условию, например в значение ячейки отрицательное число.
В итоге напрашивается то что необходимо создать маленький rule enigne, и менять свойства желательно за одну итерацию. Rule engine должен содержать маленькие простые к пониманию классы, например с двумя методами – CanHandle (говорит может ли выполнятся это конкретное правило) и Process (собственно применение правила к гриду).

 

Сценарий 2 – Legacy ASP.NET приложение с actions

Legacy ASP.NET приложение, где на одну вебформу навешали через Query string очень много разных actions. Понятное дело что так как приложение легаси и по определенным причинам (бюджет, клиент) мигрировать никуда нельзя, должно работать как и прежде но с новыми фичами – нужно как-то с этим жить. Класс вебформы розбух до невероятных размеров с дублирующийся кодом и еще кучей разных приятных вещей.
В зависимости от кода которые приходит с запросов через Query string необходимо выполнить конкретную команду или action, и в случае выполнения или отмены команды сделать что то на UI.
Тут как и в первом случае с правилами, напрашивается выделить каждую команду/action в отдельный класс. Будем иметь набор команд, среди которых необходимо выбрать подходящую, в данном случае по коду. Принципиальное отличие от первого сценария в том, что когда найдена подходящая команда проверять другие не нужно. В случае с правилами необходимо проверять применимость всех правил. Кстати тут подойдет еще паттерн Команда.

Код

Код в обеих случая будет очень похожий. Применяем цепочку обязанностей. Есть такой базовый класс:



Чтобы после того как первый найденное правило выполнилось, отменить проверку других, просто стоит немного изменить структуру условий и добавить один else в методе ProcessRules.




Теперь посмотрим как будет выглядеть типичное правило:



В итоге достаточно большое количество специфических бизнес правил оформляются в небольшие классы с правилом. Среди которых при правильном их именовании легко ориентироваться. В случае с командами/actions происходит тоже самое. Главное стараться в методе CanHandle делать небольшие условия. При правильном именовании, если условия там получаются достаточно громоздкими, то и имя класса с правилом и файла получаются достаточно длинными, это как индикатор, что можно правило дробить. Возможно, что для некоторых правил или их типов, вырисуется какая-то схожесть и общая логика, тогда можно создать наследника от RulesHandler и переместить общую логику для группы правил туда. Как результат у вас сформируется набор правил, которые легко читать и менять.
Теперь как же запустить и натравить правила на жертву:



Можно если есть необходимость зайти дальше и маркировать каждое правило соответствующим атрибутом, например правило применимое к ячейкам – CellRule, с помощью одного простого метода и рефлексии можно собрать все правила по типам из сборки или сборок и применить их к гриду.



Вывод

Цепочка обязанностей неплохо подходит если есть достаточно много правил, команд/action, которые необходимо применять к объекту, также подходит для валидации. Код, который я показал, скорее всего будет отличатся, в разных его применениях. И это хорошо так как важно понимание самой идеи, а не бездумное следование букве.

Комментариев нет:

Отправить комментарий