策略模式的优势


15

如果仅在if / then情况下编写代码,使用策略模式为什么有好处?

例如:我有一个TaxPayer类,它的一种方法使用不同的算法来计算税收。那么,为什么不具有if / then情况并找出在该方法中使用哪种算法而不是使用策略模式呢?另外,为什么不能只为TaxPayer类中的每个算法实现单独的方法?

另外,算法在运行时更改意味着什么?


2
这是作业吗?最好事先声明一下。
Fuhrmanator 2015年

2
@Fuhrmanator不,不是

Answers:


20

一方面,很难测试if/else大块的块。每个新的“分支”都会添加另一个执行路径,从而增加循环复杂性。如果要彻底测试代码,则必须涵盖所有执行路径,并且每种情况都将要求您至少再编写一个测试(假设您编写了集中的小型测试)。另一方面,实现策略的类通常仅公开一种公共方法,该方法易于测试。

所以,与嵌套 if/else您将对代码的单个部分进行许多测试,而使用Strategy时,针对多个较简单的策略中的每种策略,您将仅有很少的测试。有了后者,就很容易获得更好的覆盖范围,因为很难错过执行路径。

至于可扩展性,假设您正在编写一个框架,假定用户应该能够注入自己的行为。例如,您要创建某种税收计算框架,并希望支持不同国家的税收系统。无需实现所有功能,您只是想给框架用户提供实现如何计算某些特定税金的机会。

这是策略模式:

  • 您定义一个接口,例如TaxCalculation,并且您的框架接受此类型的实例来计算税金
  • 框架的用户创建一个实现此接口的类,并将其传递给您的框架,从而提供一种执行部分计算的方法

您不能对进行相同的操作if/else,因为这将需要更改框架的代码,在这种情况下,它将不再是框架。由于框架通常以编译形式分发,因此这可能是唯一的选择。

尽管如此,即使您只是编写一些常规代码,策略也是有益的,因为它使您的意图更加清晰。它说“此逻辑是可插入的,并且是有条件的”,即可以有多种实现,具体取决于用户的操作,配置甚至平台。

使用策略模式可以提高可读性,因为虽然实现某些特定策略的类通常应具有描述性名称,例如USAIncomeTaxCalculatorif/else块是“无名的”,在最佳情况下仅是注释过的,并且可能存在注释。另外,从我个人的喜好来看if/else,连续读取超过3 个块是不可读的,而嵌套块会变得非常糟糕。

打开/关闭”原理也非常相关,因为,正如我在上面的示例中所描述的,“策略”允许您在代码的某些部分(“扩展为扩展”)中扩展逻辑,而无需重写这些部分(“为修改而闭合”) )。


1
if/else块还降低了代码的可读性。至于战略模式,开放/封闭原则值得一提。
MaciejChałapuk2015年

1
可测试性是主要原因。(最多)应该测试代码中的每个分支。越多if,你得了,通过您的代码存在多个可能的路径,更多的测试,你必须写多的方式为这个方法失败。如果我要引用已故的瑜伽士贝拉(Yogi Berra):“如果您在路上遇到叉子,那就把它拿走。” 这非常适用于单元测试。此外,许多if语句意味着您可能会重复这些条件的逻辑,从而进一步增加测试负载并增加出现错误的风险。
格雷格·伯格哈特

谢谢你的回答。那为什么不能在同一个类中为不同的算法使用单独的方法呢?
Armon Safai 2015年

可以,但是您仍然需要一堆代码if/else来调用它们(或者在它们内部,以确定是否应该执行某些操作),因此它没有多大帮助,除了可能更具可读性的代码外。然后,您的假设框架的用户也无法扩展。
scriptin

1
您能更清楚地说明为什么它更容易测试吗?将case语句(或if / then)重构为多态方法(基于策略的示例)的示例很容易测试。refactoring.com/catalog/replaceConditionalWithPolymorphism.html如果我知道要测试的所有条件,请为每个条件编写一个测试。如果我有策略,则必须实例化并为每个策略执行一个。策略方法如何更容易测试?当您重构策略时,我们不是在谈论复杂的嵌套if。
Fuhrmanator

5

如果仅在if / then情况下编写代码,使用策略模式为什么有好处?

有时您应该只使用if / then。这是简单易懂的代码。

简单的if / then代码的两个关键问题是它可能违反开放封闭原则。如果您必须进入并添加或更改条件,则可以修改此代码。如果您希望拥有更多条件,则仅添加新策略会更简单/更干净/更不可能被破坏。

另一个问题是耦合。通过使用if / then,所有实现都与该实现相关联,从而使它们将来很难更改。通过使用该策略,唯一的耦合是与策略的接口。


修改if / then代码中的代码有什么问题?如果您决定更改其中一种算法的功能,是否还需要修改策略模式中的代码?
Armon Safai 2015年

@armonsafai-如果您修改策略,则只需测试策略。如果修改所有算法,则需要测试所有算法。更糟糕的是,如果添加新策略,则只需要测试该策略。如果添加新的条件,则需要测试所有条件。
Telastyn

4

if/then条件基于类型时,策略很有用,如http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html中所述

类型检查条件通常不会具有很高的圈复杂度,因此我不会说Strategy可以改善那里的情况。

GoF书第316页中介绍了该模式,解释了采用“策略”的主要原因:

在以下情况下使用策略模式

...

  • 一个类定义了许多行为,这些行为在其操作中显示为多个条件语句。代替许多条件,将相关的条件分支移到其自己的Strategy类中。

如其他答案中所述,如果适当地应用策略模式,则可以添加新的扩展名(具体策略),而不必修改其余代码。这就是所谓的开闭原则受保护的变化原则。当然,您仍然必须编写新的具体策略,并且客户端代码必须将这些策略识别为插件(这并非易事)。

对于if/then条件语句,必须更改包含条件逻辑的类的代码。如其他答案所述,当您不想增加复杂性来支持添加新功能(插件)而无需重新编译时,有时可以这样做。


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 2015年

@ArmonSafai单独的方法真的可以解决任何问题吗?我不这么认为。您正在将问题从一个地方转移到另一个地方,然后根据条件的结果来决定要调用的方法。再次,一个if/ elseif/ else状态。和以前一样,只是在不同的地方。
安迪

那么,如果/那么案件将是主要权利?您是否也必须将if / then案例也用于策略模式?
Armon Safai 2015年

1
@ArmonSafai不,你不会。您将idgetById方法中打开变量,这将返回特定的实现。每当您需要接口的实现时,您都将要求IoC容器将其交付给您。
安迪

1
@ArmonSafai您也可以有一个方法getSortByEnumType(SortEnum type)返回一个Sort接口的实现,有一个方法getSortType返回一个SortEnum变量并将一个集合作为参数,并且该getSortByEnumType方法将再次包含一个type参数开关,以返回正确的排序算法。如果需要添加新的排序算法,则只需编辑枚举和一个方法。而你被设置。
安迪

2

如果仅在if / then情况下编写代码,使用策略模式为什么有好处?

策略模式使您可以将算法(详细信息)与业务逻辑(高级策略)分开。这两件事混合在一起时不仅令人困惑,而且改变的原因也大不相同。

这里还有一个主要的团队可伸缩性因素。设想一个庞大的编程团队,其中许多人都在处理此会计程序包。如果计税算法全部在TaxPayer类或模块中,则合并冲突可能会发生。合并冲突非常耗时且易于出错。这段时间的浪费降低了团队的生产力,而不良合并带来的错误会损害客户的信誉。

另外,算法在运行时更改意味着什么?

一种在运行时更改的算法,其行为由配置或上下文确定。现有的if / then方法无法有效地启用此功能,因为它涉及重新加载现有的活动使用的类。使用策略模式,可以在使用时构造实现每种算法的策略对象。结果,可以对这些算法进行更改(错误修复或增强),并在运行时重新加载。该方法可用于实现连续可用性和零停机时间释放。


1

if/else本身没有错。在许多情况下,这if/else是表达逻辑的最简单,最易读的方式。因此,您描述的方法在许多情况下都是完全有效的。(它也是完全可测试的,因此这不是问题。)

但是在某些特殊情况下,策略模式可以提高整个代码的可维护性。例如:

  • 如果特定的税收计算算法可以彼此独立且与核心逻辑无关地进行更改。在这种情况下,最好将它们分为不同的类,因为更改将被本地化。
  • 如果将来可以在不更改核心逻辑的情况下添加新算法。
  • 如果两种算法之间的差异原因也影响到代码的其他部分。假设您根据纳税人的收入等级在两种算法之间进行选择。如果此收入等级还导致您在代码中的其他位置选择了不同的分支,则将实例一次与收入等级对应的策略实例化,然后在需要时进行调用比较干净,而不是在代码上分散多个if / else分支。

为了使策略模式有意义,核心逻辑和税费计算算法之间的接口应该比单个组件更稳定。如果需求变更很可能导致界面变更,那么策略模式实际上可能是一种责任。

一切都取决于“税收计算算法”是否可以与调用它的核心逻辑完全分开。与相比,策略模式会有一些开销if/else,因此您必须根据具体情况确定投资是否值得。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.