Преимущества шаблона стратегии

15

Почему полезно использовать шаблон стратегии, если вы можете просто написать свой код в случаях if / then?

Например: у меня есть класс TaxPayer, и один из его методов рассчитывает налоги, используя разные алгоритмы. Так почему же он не может иметь if / then случаев и выяснить, какой алгоритм использовать в этом методе, вместо использования шаблона стратегии? Кроме того, почему вы не можете просто реализовать отдельный метод для каждого алгоритма в классе TaxPayer?

Кроме того, что означает изменение алгоритма во время выполнения?

Армон Сафай
источник
2
Это домашнее задание? Лучше заявить об этом заранее, если это так.
Фурманатор
2
@Fuhrmanator нет, это не так
Armon Safai

Ответы:

20

С одной стороны, большие скопления if/elseблоков не легко проверить . Каждая новая «ветвь» добавляет другой путь выполнения и, таким образом, увеличивает цикломатическую сложность . Если вы хотите тщательно протестировать свой код, вам нужно будет охватить все пути выполнения, и каждое условие потребует от вас написать хотя бы еще один тест (при условии, что вы пишете небольшие, сфокусированные тесты). С другой стороны, классы, которые реализуют стратегии, обычно предоставляют только один публичный метод, который легко протестировать.

Таким образом, с вложенными if/elseвы получите множество тестов для одной части вашего кода, в то время как со Стратегией у вас будет несколько тестов для каждой из нескольких более простых стратегий. С последним легче получить лучший охват, потому что труднее пропустить пути выполнения.

Что касается расширяемости , представьте, что вы пишете фреймворк, в котором пользователи должны иметь возможность внедрять свое собственное поведение. Например, вы хотите создать некую структуру налоговых расчетов и хотите поддерживать налоговые системы разных стран. Вместо того, чтобы реализовывать их все, вы просто хотите дать пользователям фреймворка возможность представить, как рассчитывать некоторые конкретные налоги.

Вот образец стратегии:

  • Вы определяете интерфейс, например TaxCalculation, и ваша структура принимает экземпляры этого типа для расчета налогов
  • Пользователь фреймворка создает класс, который реализует этот интерфейс и передает его в фреймворк, тем самым предоставляя способ выполнить некоторую часть вычислений.

Вы не можете сделать то же самое с if/else, потому что это потребует изменения кода фреймворка, и в этом случае он больше не будет фреймворком. Поскольку платформы часто распространяются в скомпилированном виде, это может быть единственным вариантом.

Тем не менее, даже если вы просто пишете какой-то обычный код, стратегия выгодна, потому что она проясняет ваши намерения. В нем говорится «эта логика является подключаемой и условной», то есть может быть несколько реализаций, которые могут варьироваться в зависимости от действий пользователя, конфигурации или даже платформы.

Использование шаблона «Стратегия» может улучшить читабельность, потому что, хотя класс, который реализует какую-то конкретную стратегию, обычно должен иметь описательное имя, например USAIncomeTaxCalculator, if/elseблоки являются «безымянными», в лучшем случае просто прокомментированными, а комментарии могут лгать. Кроме того, по моему личному вкусу, просто более трех if/elseблоков подряд не читаются, и это плохо работает с вложенными блоками.

Принцип Open / Closed также очень важен, потому что, как я описал в примере выше, Strategy позволяет расширять логику в некоторых частях вашего кода («открыто для расширения»), не переписывая эти части («закрыто для модификации»). ).

scriptin
источник
1
if/elseблоки также снижают читаемость кода. Что касается стратегии, принцип Open / Closed стоит упомянуть IMO.
Мацей Халапук
1
Тестируемость является большой причиной. (Большинство) Каждая ветка в вашем коде должна быть проверена. Чем больше у ifвас есть, тем больше возможных путей существует в вашем коде, тем больше тестов вы должны написать и тем больше путей для сбоя этого метода. Если я могу процитировать покойного Йоги Берры: «Если вы придете к развилке на дороге, возьмите ее». Это блестяще относится к юнит-тестированию. Более того, многие ifутверждения означают, что вы, скорее всего, будете повторять логику для этих условий, еще больше увеличивая тестовую нагрузку и увеличивая риск появления ошибок.
Грег Бургхардт
Спасибо за твой ответ. Так почему я не могу использовать отдельные методы для разных алгоритмов в одном классе?
Armon Safai
Вы можете, но вам все равно понадобится куча if/elseблоков для их вызова (или внутри них, чтобы определить, должно ли оно что-то делать или нет), так что это не очень помогает, за исключением, возможно, более читабельного кода. А также нет расширяемости для пользователей вашей гипотетической структуры тогда.
скрипт
1
Можете ли вы прояснить, почему это проще для тестирования? Пример рефакторинга оператора case (или if / then) к полиморфному методу (основа для стратегии) ​​довольно легко проверить. refactoring.com/catalog/replaceConditionalWithPolymorphism.html Если я знаю все условия для проверки, я пишу тест для каждого из них. Если у меня есть стратегии, я должен создать и выполнить одну для каждой. Как стратегический подход легче проверить? Мы не говорим о сложных вложенных ifs, когда вы рефакторинг стратегии.
Фурманатор
5

Почему полезно использовать шаблон стратегии, если вы можете просто написать свой код в случаях if / then?

Иногда вы должны просто использовать if / then. Это простой код, который легко читать.

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

Другая проблема - это связь. Используя if / then, все реализации привязываются к этой реализации, что затрудняет их изменение в будущем. Используя стратегию, единственное соединение - это интерфейс стратегии.

Telastyn
источник
что не так с изменением кода в коде if / then? Разве вам не придется изменять код в шаблоне стратегии, если вы решите изменить работу одного из алгоритмов?
Armon Safai
@armonsafai - если вы измените стратегию, вам просто нужно протестировать стратегию. Если вы модифицируете все алгоритмы, вам необходимо протестировать все алгоритмы. Хуже того, если вы добавите новую стратегию, вам просто нужно протестировать стратегию. Если вы добавляете новое условие, вам необходимо протестировать все условия.
Теластин
4

Стратегия полезна, когда if/thenусловия основаны на типах , как описано в http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html

Условные проверки типов обычно не имеют большой цикломатической сложности, поэтому я бы не сказал, что Strategy обязательно улучшит ситуацию там.

Основная причина Стратегии объяснена в книге GoF с.316, в которой представлен шаблон:

Используйте шаблон стратегии, когда

...

  • класс определяет много поведений, и они появляются как несколько условных операторов в своих операциях. Вместо многих условных выражений перенесите соответствующие условные ветви в свой собственный класс Strategy.

Как упоминалось в других ответах, при правильном применении шаблон «Стратегия» позволяет добавлять новые расширения (конкретные стратегии) ​​без необходимости изменения остальной части кода. Это так называемый принцип Open-Closed или Protected Variations . Конечно, вам все равно придется кодировать новую конкретную стратегию, а клиентский код должен распознавать стратегии как плагины (это не тривиально).

В случае if/thenусловных выражений необходимо изменить код класса, содержащего условную логику. Как упоминалось в других ответах, иногда это нормально, если вы не хотите добавлять сложность для поддержки добавления новых функций (плагинов) без перекомпиляции.

Fuhrmanator
источник
3

[...] если вы можете просто написать свой код в случаях if / then?

Это как раз самое большое преимущество стратегического паттерна. Не имея условий.

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

Условия ( 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состояние. Так же, как и раньше, просто в другом месте.
Энди
Так что, если / тогда дела будут в основном верно? Разве вы не должны использовать if / then кейсы в основном и для паттерна стратегии?
Armon Safai
1
@ArmonSafai Нет, ты бы не стал. Вы бы включили idпеременную в getByIdметоде, которая вернула бы конкретную реализацию. Каждый раз, когда вам потребуется реализация интерфейса, вы просите контейнер IoC доставить его вам.
Энди
1
@ArmonSafai Вы также можете иметь метод, getSortByEnumType(SortEnum type)возвращающий реализацию Sortинтерфейса, иметь метод, getSortTypeвозвращающий SortEnumпеременную и принимающий коллекцию в качестве параметра, и getSortByEnumTypeметод снова будет содержать typeпараметр, возвращающий вам правильный алгоритм сортировки. Если вам нужно было добавить новый алгоритм сортировки, вам нужно отредактировать только перечисление и один метод. И вы настроены.
Энди
2

Почему полезно использовать шаблон стратегии, если вы можете просто написать свой код в случаях if / then?

Шаблон стратегии позволяет вам отделить ваши алгоритмы (детали) от бизнес-логики (политики высокого уровня). Эти две вещи не только сбивают с толку, когда их смешивают, но и имеют совершенно разные причины для изменения.

Здесь также важен фактор масштабируемости командной работы. Представьте себе большую команду программистов, в которой много людей работают над этим бухгалтерским пакетом. Если все налоговые алгоритмы находятся в классе или модуле TaxPayer, то возникает вероятность слияния. Конфликты слияний занимают много времени и подвержены ошибкам. Это время снижает производительность труда команды, а ошибки, вызванные плохими слияниями, наносят ущерб надежности клиентов.

Кроме того, что означает изменение алгоритма во время выполнения?

Алгоритм, который изменяется во время выполнения, - это алгоритм, поведение которого определяется конфигурацией или контекстом. Если / то подойти на месте не эффективно включить это , поскольку предполагает перегрузку существующих активно используемых классов. С помощью паттерна стратегии объекты стратегии, реализующие каждый алгоритм, могут быть сконструированы при использовании. В результате изменения этих алгоритмов (исправления ошибок или улучшения) могут быть внесены и перезагружены во время выполнения. Этот подход можно использовать для обеспечения непрерывной доступности и нулевого времени простоя релизов.

Ален О'Ди
источник
1

В этом нет ничего плохого if/else. Во многих случаях if/elseэто самый простой и читаемый способ выражения логики. Таким образом, подход, который вы описываете, является вполне допустимым во многих случаях. (Это также отлично проверяется, так что это не проблема.)

Но есть некоторые конкретные случаи, когда шаблон стратегии может улучшить удобство сопровождения всего кода. Например:

  • Если конкретные алгоритмы расчета налога могут меняться независимо друг от друга и от основной логики. В этом случае было бы хорошо разделить их на отдельные классы, так как изменения будут локализованы.
  • Если новые алгоритмы могут быть добавлены в будущем, без изменения основной логики.
  • Если причина различий между двумя алгоритмами также влияет на другие части кода. Допустим, вы выбираете один из двух алгоритмов в зависимости от уровня дохода налогоплательщика. Если это ограничение дохода также приводит к тому, что вы выбираете другие ветви в других местах кода, то лучше один раз создать стратегию, соответствующую группе дохода, и вызывать ее при необходимости, а не разбрасывать несколько ветвей if / else по коду.

Чтобы шаблон стратегии имел смысл, интерфейс между базовой логикой и алгоритмами расчета налога должен быть более стабильным, чем отдельные компоненты. Если с такой же вероятностью изменение требований приведет к изменению интерфейса, тогда модель стратегии может фактически быть пассивом.

Все сводится к тому, что «алгоритмы расчета налогов» могут быть четко отделены от основной логики, которая их вызывает. Шаблон стратегии имеет некоторые накладные расходы по сравнению с шаблоном if/else, поэтому вам придется самостоятельно решать, стоит ли инвестировать.

JacquesB
источник