Множинні розгалуження та шаблон «Правила»

Здрастуйте, шановні хабрачитачі. У цій статті я хотів би поділитися знаннями про один невеликий і простий, але корисний шаблон, про який зазвичай не пишуть у книжках (можливо тому, що він є окремим випадком шаблону «Команда»). Це шаблон "Правила" (Rules Pattern). Мабуть, для багатьох він буде дуже знайомим, але комусь буде цікаво з ним познайомитись.

розгалуження

Сутність питання

Знайомо? Отже, які тут трапляються проблеми?

Проблема 1:Зростаюча цикломатична складність. Якщо говорити просто, то цикломатична складність - це глибина вкладеності if-ів та циклів з урахуванням логічних операторів. Інструменти аналізу коду дають змогу оцінити цей параметр для всіх ділянок коду. Вважається, що параметр цикломатичної складності для окремої ділянки коду не повинен перевищувати 10. З цієї проблеми зростає така.

Проблема 2:Додавання нової логіки. З часом та додаванням нових умов стає складно зрозуміти, куди саме додавати нову логіку та як.

Проблема 3:Дублювання коду. Якщо дерево умов розгалужене, то часом не можна позбутися ситуації, коли той самий код присутній у кількох гілках.

Тут і на допомогу приходить шаблон «Правила». Його структура дуже проста:

шаблон

Тут клас Evaluator містить колекцію реалізацій інтерфейсу IRule. Evaluator виконує правила та вирішує, яке правило треба використовувати для отримання результату. Щоб зрозуміти, як це працює та виглядає у коді, розглянемо невеликий приклад на C#.

приклад. Гра в кістки (на кшталт «Талі»)

Гравець кидає одночасно 5 кубиків, і залежно від їхньої комбінації отримує певну кількість очок. Комбінації можуть бути такими: 1 X X X X - 100 очок 5 X X X X - 50 очок 1 1 1 X X - 1000 очок 2 2 2 X X - 200 очок 3 3 3 X X - 300 очок 4 4 4 X X - 400 очок >5 5 5 X X - 500 очок 6 6 6 X X - 600 очок

Приклади комбінацій: [1,1,1,5,1] - 1150 очок [2,3,4,6,2] - 0 очок [3,4,5,3,3 ] - 350 очок

Звичайно, всі вони можуть бути й іншими, і їх може бути набагато більше, але про це пізніше.

Роби раз! Без шаблонів.
Роби два! Додавання правил? Модульні випробування.

Начебто 50 рядків коду це дуже мало. Але що буде, якщо правила гри змінюватимуться і додаватимуться? Наприклад, ми додамо правила для різних комбінацій кубиків:

1 1 1 1 X - 2000 1 1 1 1 1 - 4000 1 2 3 4 5 - 8000 2 3 4 5 6 - 8000 A A B B X - 4000 і так далі.

У цьому випадку код ризикує перетворитися на дуже заплутаний. Щоб цього уникнути, перепишемо код із використанням шаблону «Правила».(Тут мені також варто було б сказати про те, що до рефакторингу треба покрити всі випадки модульними тестами, побурчати про їхню важливість та необхідність для рефакторингу коду)

Роби три! Застосовуємо шаблон «Правила»

Ура! Завдання вирішено! Тепер кожен клас займається тим, що йому належить, цикломатична складність не зростає, а нові правила додаються легко і просто. Вибір правила тепер здійснюється за допомогою класу RuleSet, що містить набір правил, а додавання правил та підрахунок очок – класом Game.

Про що пам'ятати?

При проектуванні програми, що містить логіку, засновану на правилах, корисно мати на увазі такі питання: — Чи слід правилам бути read-only щодо системи, щоб не змінювати її стан? — Чи мають бути залежності між правилами? Чи варто приділити увагу порядку виконання правил, у разі,коли одне правило може вимагати результат роботи іншого правила до роботи. — Чи має порядок виконання правил бути суворо визначеним? — Чи мають бути пріоритети у виконанні правил? - Чи варто дозволяти кінцевим користувачам редагувати правила? та багато інших.

Пара слів про системи правил бізнес-логіки (Business Rules Engines)

Концепція Business Rules Engines дуже близька до ідеї шаблону «Правила» — це системи, які дозволяють визначити системи правил для бізнес-логіки. Зазвичай вони мають певний графічний інтерфейс і дозволяють користувачам визначати правила та ієрархії правил, які можуть зберігатися у базі даних чи файловій системі. Зокрема, цей функціонал має і Workflow Foundation від Microsoft.

1) Використовуємо шаблон «правила», коли треба позбутися складності умов та розгалужень 2) Поміщаємо логіку кожного правила та його ефекти у свої класи 3) Відокремлюємо вибір та обробку правил в окремий клас — Evaluator 4 ) Знаємо, що є готові «рухові» рішення для бізнес-логіки.

Дуже дякую за увагу, сподіваюся, моя творча переробка цього навчального матеріалу комусь допоможе.* Джерелом натхнення для цієї статті послужив урок "Rules Pattern" з курсу "Design Patterns" сайту pluralsight.com від Стіва Сміта

Хардкорна конфа за С++. Ми запрошуємо лише профі.

Читають зараз

PHP шаблони проектування. Частина 1. Що породжують

Шпаргалка за шаблонами проектування

Питання Карлосу Олгуїну про його роботу в галузі 3d-біодруку, програмування речовини, 4d-проектування і не тільки

Коментарі 37

> [3,4,5,3,3] - 350 очок

Як здобули? Якщо допустити переупорядкування, тоді зрозуміло:

Я ще явно визначив би порядок застосування правил. У вашому коді порядок«по максимуму очок доданих від застосування чергового правила до кісток, що залишилися», і я б спростив його до«першими застосовуються правила, що приносять більшу кількість очок»і задав його так:

Але часто зустрічається інший порядок: «У будь-якому порядку, головне отримати найбільшу кількість очок»: Правила: 111xx - 300 11xxx - 200 221xx - 200 Вхід: 11122 . Ваша реалізація порахує 300 очок, але можливий варіант із 400 очок.

А може виявитися, що гравцеві дозволяється вибирати із можливих варіантів. Тоді доведеться інтерфейс цієї частини системи міняти.

Повторюся, я б щеявно поставив порядок застосування правил.

Та й не дивні правила теж у поточну логіку не лягають, наприклад: 112XX - 900 13XXX - 900 111XX - 1000

На комбінації 11123 - видасть 1000 очок, хоча має 1800.

Більше того, такий підхід (саме як у статті, а не сам патерн) має фундаментальний мінус: навіть відразу можна запропонувати методику проведення тестів за правилами, за O(K^2) часу, де K — кількість кісток. При цьому в запропонованій техніці виконуватиметься O(N*K^2) часу, де N — число правил. Оскільки зазвичай у реальних системах треба вважати не 10 правил при викиді 5 кісток, а сотні і тисячі правил на складніших структурах — такий підхід відразу просаджує продуктивність.

Сам собою патерн, безумовно, корисний, але його правильної реалізації здебільшого потрібно писати ще систему, яка будуватиме стратегію застосування за прийнятний час.

Вибачте, але це жахливо. Візуально той код, який називався поганим для гри в кістки, виглядає краще. Він менший. Так, він незрозумілий, але тільки тому, щоабсолютно потворно написані імена, трійка стрибає за кодом. Якщо його зачесати і якщо клас дійсно повинен рахувати кубики і там написаний мінімум на даний момент (YAGNI), то він буде кращим. Але він правда і ягня не відповідає. Просто дивно в жодні ворота написано.

Але я особисто патерни і не люблю. Патерни з'являтися повинні самостійно, якщо треба. Зазвичай вони потрібні для гнучкості. Але програмине повинні бути гнучкими. Програмимають бути точними. А це буквально навпаки, аніж те, що тут написано. Тобто. ви з чогось думаєте про якесь вигадане завтра, коли раптом з'являться нові правила. І навернули бозна-що. Вивернули логіку, змусили страждати на компілятор.

Давайте і подивимося:

1 X X X X - 100 очок 5 X X X X - 50 очок 1 1 1 X X - 1000 очок 2 2 2 X X - 200 очок 3 3 3 X X - 300 очок 4 4 4 X X - 400 очок 5 5 5 X X - 500 очок 6 6 6 X X - 600 очок

Це буквально юз-кейси. Які вимоги? 1. Порядок не важливий. 2. З усіх комбінацій шукаємо набори комбінацій (назву шаблонів), що дають максимальну кількість очок. І ось код:

Погодьтеся, так читання набагато краще. Вимоги видно неозброєним оком. Не треба розумітися на конструкторах класів. Та й класи писати не треба. Буквально видно по кроках, як звідки виходить. Не повинно бути у простих завдань багато коду! У ньому так, жорстко зашиті вимоги, як зрозумілі мною зараз. Так, у цьому коді закладено, що я вибираю завжди найдорожчий шаблон. Але ж так і треба зараз. Саме це і потрібно із завдання. Немає жодного майбутнього. Якщо мене попросили перекласти англійською «собачка перебігла дорогу», то саме так і треба чинити, а не писати «ххх перебігло дорогу». Щобколись післязавтра кішку туди підставити. Коли вимоги виражені чітко, коли коду мінімум, тоді і переписати буде не проблема, якщо зміниться вимоги.

Код, можливо, не ідеально простий. Може, змусить задуматися про замикання та сайд-ефект. Але загалом не в патернах щастя.