Переваги стратегії


15

Чому вигідно використовувати шаблон стратегії, якщо ви можете просто написати свій код у випадках, якщо / тоді?

Наприклад: у мене є клас TaxPayer, і один із його методів обчислює податки за допомогою різних алгоритмів. То чому б вона не могла мати, якщо / тоді випадки і зрозуміти, який алгоритм використовувати в цьому методі, а не використовувати шаблон стратегії? Крім того, чому ви не можете просто реалізувати окремий метод для кожного алгоритму в класі TaxPayer?

Крім того, що означає зміна алгоритму під час виконання?


2
Це домашнє завдання? Краще заявити про це, якщо так.
Фурманатор

2
@Fuhrmanator no it isnt
Armon Safai

Відповіді:


20

З одного боку, великі скупчення if/elseблоків не є легко перевірити . Кожна нова "гілка" додає ще один шлях виконання і тим самим збільшує циклічну складність . Якщо ви хочете ретельно протестувати свій код, вам доведеться охопити всі шляхи виконання, і кожна умова вимагатиме від вас написати принаймні ще один тест (якщо ви пишете невеликі, зосереджені тести). З іншого боку, класи, які реалізують стратегії, зазвичай піддають лише 1 публічний метод, який легко перевірити.

Отже, за допомогою вкладеної програми if/elseви отримаєте багато тестів для однієї частини коду, тоді як зі стратегією у вас буде кілька тестів для кожної з декількох простіших стратегій. З останньою можливістю кращого покриття, оскільки складніше пропустити шляхи виконання.

Що стосується розширюваності , уявіть, що ви пишете рамки, де користувачі повинні мати можливість вводити власну поведінку. Наприклад, ви хочете створити якусь структуру податкових розрахунків і хочете підтримати податкові системи різних країн. Замість того, щоб реалізувати їх усі, ви просто хочете надати рамковим користувачам можливість реалізувати спосіб обчислення деяких конкретних податків.

Ось стратегія:

  • Ви визначаєте інтерфейс, наприклад TaxCalculation, і ваша система приймає екземпляри такого типу для обчислення податків
  • Користувач фреймворку створює клас, який реалізує цей інтерфейс і передає його вашому фреймворку, забезпечуючи таким чином спосіб виконати деяку частину обчислень

Ви не можете зробити те ж саме з if/elseтим, що це вимагало б змінити код фреймворку, і в цьому випадку це більше не буде рамкою. Оскільки рамки часто поширюються у складеному вигляді, це може бути єдиним варіантом.

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

Використання шаблону стратегії може покращити читабельність, оскільки, хоча клас, який реалізує якусь конкретну стратегію, зазвичай повинен мати описову назву, наприклад USAIncomeTaxCalculator, if/elseблоки є "безіменними", у кращих випадках просто коментуються, а коментарі можуть лежати. Крім того, з мого особистого смаку, просто маючи більше 3 if/elseблоків поспіль не читабельно, і це стає погано з вкладеними блоками.

Принцип відкритого / закритого типу також є дуже актуальним, оскільки, як я описав у прикладі вище, стратегія дозволяє розширити логіку в деяких частинах коду ("відкрито для розширення"), не переписуючи ці частини ("закриті для модифікації" ).


1
if/elseблоки також знижують читабельність коду. Що стосується структури стратегії, то в принципі відкритого / закритого варто згадати ІМО.
Мацей Чалапук

1
Заповітність - головна причина. (Більшість) Кожна гілка вашого коду повинна бути протестована. Чим більше ifу вас, тим більше можливих шляхів існує через ваш код, тим більше випробувань вам доведеться написати та більше способів відмови від цього методу. Якщо я можу процитувати покійного Йогі Берра: "Якщо ви приїдете на роздріб у дорогу, візьміть це". Це блискуче стосується тестування одиниць. Крім того, багато ifтверджень означають, що ви, ймовірно, повторите логіку для цих умов, ще більше збільшуючи тестове навантаження та збільшуючи ризик появи помилок.
Грег Бургхардт

дякую за відповідь. То чому я не можу використовувати окремі методи для різних алгоритмів в одному класі?
Armon Safai

Ви можете, але вам все одно знадобиться купа if/elseблоків, щоб зателефонувати на них (або всередині них, щоб визначити, чи потрібно щось робити чи ні), тому це не дуже допоможе, крім, можливо, більш читабельного коду. А також ніякого розширення для користувачів вашої гіпотетичної бази тоді.
сценарій

1
Чи можете ви зрозуміти, чому простіше тестувати? Приклад для рефакторингу заяви справи (або якщо / тоді) на поліморфний метод (основа для стратегії) досить легко перевірити. refactoring.com/catalog/replaceConditionalWithPolymorphism.html Якщо я знаю всі умови для тестування, я пишу тест для кожного. Якщо у мене є стратегії, я повинен інстанціювати та виконувати одну для кожної. Як легше перевірити стратегічний підхід? Ми не говоримо про складні вкладені ifs, коли ви перетворюєте стратегію.
Фурманатор

5

Чому вигідно використовувати шаблон стратегії, якщо ви можете просто написати свій код у випадках, якщо / тоді?

Іноді слід просто використовувати if / then. Це простий код, який легко читати.

Дві ключові проблеми з простим кодом if / then полягає в тому, що він може порушити принцип відкритого закритого типу . Якщо вам коли-небудь доведеться зайти, додати або змінити умову, ви змінюєте цей код. Якщо ви очікуєте, що у вас буде більше умов, просто додати нову стратегію - простіше / чистіше / менш імовірно, що вийде з ладу.

Інша проблема - зв’язок. Використовуючи if / then, всі реалізації пов'язані з цією реалізацією, що ускладнює їх зміни в майбутньому. Використовуючи стратегію, єдине з'єднання полягає в інтерфейсі стратегії.


що не так із зміною коду в коді if / then? Чи не доведеться також змінювати код у шаблоні стратегії, якщо ви вирішили змінити функціонування одного з алгоритмів?
Armon Safai

@armonsafai - якщо ви модифікуєте стратегію, вам потрібно просто протестувати стратегію. Якщо ви модифікуєте всі алгоритми, вам потрібно протестувати всі алгоритми. Гірше, якщо ви додасте нову стратегію, вам просто потрібно протестувати стратегію. Якщо ви додасте новий умовний, вам потрібно протестувати всі умови.
Теластин

4

Стратегія корисна, коли if/thenумови базуються на типах , як це пояснено в http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html

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

Основна причина Стратегії пояснюється в книзі Міністерства фінансів, с.316, яка запровадила цей шаблон:

Використовуйте шаблон стратегії, коли

...

  • клас визначає багато поведінки, і вони з'являються як множинні умовні висловлювання в його операціях. Замість багатьох умовних умов перемістіть відповідні умовні гілки у власний клас стратегії.

Як зазначено в інших відповідях, якщо застосовувати відповідний зразок, стратегія дозволяє додавати нові розширення (конкретні стратегії) без необхідності модифікації решти коду. Це так званий принцип відкритого закриття або принцип захищених варіацій . Звичайно, вам все одно доведеться кодувати нову конкретну стратегію, і клієнтський код повинен розпізнавати стратегії як плагіни (це не тривіально).

З if/thenумовними умовами необхідно змінити код класу, що містить умовну логіку. Як згадується в інших відповідях, іноді це нормально, коли ви не хочете додавати складності підтримці додавання нових функціональних можливостей (плагінів) без повторної компіляції.


3

[...] якщо ви можете просто написати свій код у випадках / тоді?

Це саме найбільша перевага стратегічного шаблону. Не маючи умов.

Ви хочете, щоб ваші класи / методи / функції були максимально простими та короткими. Короткий код дуже простий для тестування та дуже легкий для читання.

Умови ( if/ elseif/ else) роблять ваші класи / методи / функції довгими, тому що зазвичай код, в якому оцінюється одне рішення true, відрізняється від частини, в якій приймається рішення false.


Ще одна велика перевага стратегії полягає в тому, що вона може бути використана повторно протягом усього проекту.

Використовуючи шаблон дизайну стратегії, ви, ймовірно, маєте якийсь контейнер IoC, з якого ви отримуєте бажану реалізацію інтерфейсу, можливо, getById(int id)методом, де він idміг би бути членом перелічувача.

Це означає, що створення реалізується лише в одному місці вашого коду.

Якщо ви хочете додати більше реалізацій, ви додасте нову реалізацію до getByIdметоду, і ця зміна відображається скрізь у коді, де ви її викликаєте.

З if/ elseif/ elseце зробити неможливо. Додавши нову реалізацію, ви повинні додати новий elseifблок і робити це скрізь, де використовувались реалізації, або, можливо, ви отримаєте недійсний код, оскільки ви забули додати реалізацію до її структури.


Крім того, що означає зміна алгоритму під час виконання?

У моєму прикладі, idможе бути змінна, яка заповнюється на основі даних користувача. Якщо користувач натискає кнопку A, то id = 2, якщо він натискає кнопку B, то id = 8.

Через різну idцінність інша інтерфейс отримується з контейнера IoC і код виконує різні операції.


дякую за відповідь. То чому я не можу використовувати окремі методи для різних алгоритмів в одному класі?
Armon Safai

@ArmonSafai Чи окремі методи дійсно вирішили б щось? Я не думаю, що так. Ви переміщуєте проблему з одного місця на інше, і рішення, який метод викликати, буде прийнято на основі результату умови. Знову ж, if/ elseif/ elseдержава. Те саме, що раніше, просто в іншому місці.
Енді

Тож випадки якщо / тоді були б в основному правильними? Чи не доведеться вам використовувати, якщо / тоді головні випадки також для моделі стратегії?
Армон Сафай

1
@ArmonSafai Ні, ви б не стали. У вас був би перемикач на idзмінну в getByIdметоді, яка б повернула конкретну реалізацію. Кожного разу, коли вам знадобиться реалізація інтерфейсу, ви б просили контейнер IoC доставити його вам.
Енді

1
@ArmonSafai Ви також можете мати метод getSortByEnumType(SortEnum type)повернення реалізації Sortінтерфейсу, метод getSortTypeповернення SortEnumзмінної та прийняття колекції як параметр, і getSortByEnumTypeметод знову міститиме перемикач на typeпараметр, повертаючи вам правильний алгоритм сортування. Якщо вам потрібно було додати новий алгоритм сортування, вам потрібно лише відредагувати перерахунок та один метод. І ви налаштовані.
Енді

2

Чому вигідно використовувати шаблон стратегії, якщо ви можете просто написати свій код у випадках, якщо / тоді?

Шаблон стратегії дозволяє відділяти алгоритми (деталі) від вашої бізнес-логіки (політика високого рівня). Ці дві речі не лише заплутано читають, коли їх змішують, але й мають дуже різні причини зміни.

Тут також є головний фактор масштабованості роботи в команді. Уявіть собі велику команду програмування, де багато людей працюють над цим пакетом обліку. Якщо алгоритми оподаткування входять у клас або модуль TaxPayer , конфлікти злиття стають ймовірними. Конфлікти злиття займають багато часу і схильні до вирішення. Цього разу витрата знижує продуктивність роботи команди, а помилки, спричинені поганим злиттям збитку, викликають довіру до клієнтів.

Крім того, що означає зміна алгоритму під час виконання?

Алгоритм, який змінюється під час виконання, - це той, чия поведінка визначається конфігурацією чи контекстом. Створений підхід if / then не дозволяє ефективно цього, оскільки він включає перезавантаження існуючих активно використовуваних класів. За допомогою шаблону стратегії об'єкти стратегії, що реалізують кожен алгоритм, можуть бути побудовані під час використання. В результаті зміни цих алгоритмів (виправлення помилок або вдосконалення) можуть бути зроблені та перезавантажені під час виконання. Цей підхід може бути використаний для забезпечення постійної доступності та нульових простоїв.


1

У цьому немає нічого поганого if/else. У багатьох випадках if/elseце найпростіший і найчитабельніший спосіб вираження логіки. Тож підхід, який ви описуєте, цілком справедливий у багатьох випадках. (Це також ідеально перевірено, так що це не проблема.)

Але є деякі конкретні випадки, коли модель стратегії може покращити ремонтопридатність загального коду. Наприклад:

  • Якщо конкретні алгоритми обчислення податку можуть змінюватися незалежно один від одного та від основної логіки. У цьому випадку було б добре розділити їх на окремі класи, оскільки зміни будуть локалізовані.
  • Якщо в майбутньому можуть бути додані нові алгоритми без зміни основної логіки.
  • Якщо причина різниці між двома алгоритмами впливає і на інші частини коду. Скажімо, ви вибираєте між двома алгоритмами, заснованими на діаграмі доходів платника податку. Якщо ця таблиця доходів також змушує вибирати різні гілки інших місць у коді, то чистіше запрограмувати стратегію, відповідну діаграмі доходу один раз, і зателефонувати за потреби, а не мати кілька, якщо / else-гілки розкидані по коду.

Щоб модель стратегії мала сенс, інтерфейс між основною логікою та алгоритмами розрахунку податку повинен бути більш стабільним, ніж окремі компоненти. Якщо так само ймовірно, що зміна вимоги призведе до зміни інтерфейсу, то структура стратегії може бути фактично відповідальною.

Все зводиться до того, якщо "алгоритми обчислення податку" можна чітко відокремити від основної логіки, яка викликає його. Модель стратегії має деякі накладні витрати порівняно з такою if/else, тож вам доведеться вирішувати в кожному конкретному випадку, якщо інвестиція того варта.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.