模板方法和策略模式之间有什么区别?

211

请问有人能解释一下模板方法模式和策略模式的区别吗?

据我所知,它们在99%的情况下是相同的 - 唯一的区别在于模板方法模式使用抽象类作为基类,而策略模式则使用接口,由每个具体的策略类实现。

然而,在客户端看来,它们以完全相同的方式被调用 - 这样正确吗?


2
这篇SO的帖子对于同样的问题有更好的答案:https://dev59.com/a3RB5IYBdhLWcg3w7riV - RoundPi
22
gob00st提供的链接是关于战略和桥牌之间区别的问题,与这个问题无关。请注意,这不是对这个问题的回答。 - bluekeys
19个回答

184

模板模式是用于某个操作具有一些不变行为(可以根据其他可变原始行为来定义)的情况。抽象类定义了不变行为,而实现类定义了相关的方法。

在策略模式中,行为实现是独立的——每个实现类都定义了行为,它们之间没有共享的代码。这两种模式都是行为模式,因此客户端以相同的方式使用它们。通常,策略模式只有一个公共方法——execute() 方法,而模板模式可能会定义一组公共方法以及一组支持私有原语,子类必须实现这些原语。

这两种模式可以很容易地结合使用。您可能会使用策略模式来实现一组属于使用模板模式实现的策略族的多个实现。


这听起来对我来说是正确的,但是为什么维基百科提到“策略模式是为了在运行时选择算法的行为”?它也可以用于在编译时选择算法的行为,就像模板方法一样。我有什么遗漏吗? - BornToCode
2
@BornToCode 我认为他们所说的是在运行时选择特定的策略。例如,有几种数值方法可以找到方程的根。根据问题域或数据,您可能会选择牛顿-拉弗森、欧拉或其他策略来解决方程。每个策略都是一个部分,而解决方程只是更大算法的一部分,该算法基于问题的某些质量选择要使用的策略。 - tvanfosson
是的,但策略模式不应该仅用于这些情况,对吧?我的意思是,如果我只需要在编译时选择算法的行为,我是否仍应该使用策略模式,或者它并不是用来这样使用的? - BornToCode
1
@BornToCode 我认为当选择是动态的时候,策略最有用。模板基本上是一种为已知的不同相关行为建立的方式。您将使用某些策略(尽管不一定是策略模式)来选择要使用哪个模板行为。例如,产品继承-您将创建一个基本产品,为不同的产品添加功能。选择要实例化的产品类型(类)可能取决于从哪些表/视图加载它。策略模式在这里并没有真正发挥作用。 - tvanfosson
2
@BornToCode,这不是二选一的事情,而是“yes-and”的态度。如果合适就应用模式,如果有用就结合模式。 - tvanfosson
显示剩余2条评论

161
两者的主要区别在于选择具体算法的时间不同。
使用模板方法模式时,这种选择发生在编译时,通过子类化模板来实现。每个子类通过实现模板的抽象方法提供不同的具体算法。当客户端调用模板的外部接口方法时,模板根据需要调用其抽象方法(内部接口)来调用算法。
class ConcreteAlgorithm : AbstractTemplate
{
    void DoAlgorithm(int datum) {...}
}

class AbstractTemplate
{
    void run(int datum) { DoAlgorithm(datum); }

    virtual void DoAlgorithm() = 0; // abstract
}

相比之下,策略模式允许通过封装在运行时选择算法。具体的算法由单独的类或函数实现,并作为参数传递给策略的构造函数或setter方法。根据程序的状态或输入,该参数选择哪种算法可以动态变化。

class ConcreteAlgorithm : IAlgorithm
{
    void DoAlgorithm(int datum) {...}
}

class Strategy
{
    Strategy(IAlgorithm algo) {...}

    void run(int datum) { this->algo.DoAlgorithm(datum); }
}

总结:

  • 模板方法模式: 通过子类化编译时选择算法
  • 策略模式: 通过包含运行时选择算法

62
两种模式都支持在运行时选择要使用的算法(对于模板方法,您可以执行类似 if (config.useAlgoA) impl = new AlgoA() else impl = new AlgoB() 的操作),因此这个答案是不正确的。 - Borek Bernard
16
当然,你可以那样做,但这样就不再使用模板方法模式了。实际上,这几乎就是创建策略实例的代码所看起来的样子! - thehouse
25
我认为这个答案虽然不完全错误,但是错失了真正差异所在的重点。@tvanfosson的回答更好。 - Doc Brown
1
@Karoly Nyisztor,它们都可以“替换行为”和“提供扩展点”。无论是行为还是扩展,真正取决于您应用给定模式的上下文。您也可以将模板方法模式的每个子类称为“策略”,或者将策略模式中的每个策略类称为“扩展”,这只是措辞问题。事实是它们做相同的事情,除了这个答案提到的区别。所以这是正确的答案。 - Andy
4
两种模式都可以采用相同的具体算法。通过调用new ConcreteAlgorithm1()new ConcreteAlgorithm2()来进行选择。显然,选择是在运行时进行的(在编译时选择算法会意味着硬编码)。两者之间的主要区别在于具体算法的实现方式。它是作为子类实现还是作为单独的接口?前者是模板方法模式,后者是策略模式。这个区别可以概括为组合(composition)与继承(inheritance),这是GoF书中的一个常见主题。 - jaco0646
显示剩余3条评论

32

Difference between Strategy and Template Method Pattern Strategy vs Template method


相似之处

策略模式和模板方法模式在许多方面有很多相似之处。两种模式都可以用于满足开闭原则,并使软件模块易于扩展而不改变其代码。两种模式都表示将通用功能与该功能的详细实现分离。然而,它们在提供的粒度方面略有不同。


区别

在研究这两种模式时,我观察到了一些不同之处:

  1. 在Strategy模式中,客户端和策略之间的耦合较松散,而在Template Method中,这两个模块耦合更紧密。
  2. 在Strategy模式中,通常使用接口,尽管根据情况也可以使用抽象类,不使用具体类;而在Template method中,通常使用抽象类或具体类,不使用接口。
  3. 在Strategy模式中,通常将类的整个行为表示为接口;而Template Method用于减少代码重复性,将样板代码定义在基础框架或抽象类中。在Template Method中,甚至可以有具有默认实现的具体类。
  4. 简单来说,在Strategy模式中可以更改整个策略(算法),但在Template method中只有一些部分(算法的部分)会更改,其余部分保持不变。在Template Method中,不变步骤是在抽象基类中实现的,而可变步骤则提供了默认实现或根本没有实现。在Template method中,组件设计者规定了算法的必要步骤和步骤的顺序,但允许组件客户端扩展或替换其中的某些步骤。

图片来自bitesized博客。


能否使用模板来处理步骤/流程,并使用策略替换特定的步骤/流程? - BI Dude
2
@BIDude,如果您想要交换某些步骤并强制执行其他步骤,请使用模板。如果要允许交换所有步骤,请使用策略。 - Yogesh Umesh Vaity

31

我认为这两种模式的类图显示了它们之间的差异。

策略模式
将算法封装在一个类中
图片链接 图片描述

模板方法模式
将算法的确切步骤推迟到子类中执行
图片链接 图片描述


27
你可能指的是模板方法模式。你是正确的,它们具有非常相似的需求。 我认为在需要定义一些步骤供子类重写以改变某些细节的“模板”算法的情况下,最好使用模板方法。 对于策略模式,您需要创建一个接口,并使用委托而不是继承。我认为这是一种更强大的模式,也许更符合依赖倒置原则(DIP)。它更强大,因为您明确地定义了策略的新抽象 - 一种做某事的方式,这不适用于模板方法。因此,如果这种抽象有意义,请使用它。但是,在简单情况下,使用模板方法可能会给您提供更简单的设计,这也很重要。 考虑哪些词更合适:您是否拥有模板算法?还是这里的关键是您拥有策略的抽象 - 一种新的做事方式?

模板方法的例子:

Application.main()
{
Init();
Run();
Done();
}

在这里,您继承应用程序并替换init、run和done上要执行的确切操作。

策略示例:

array.sort (IComparer<T> comparer)

在这里,当编写比较器时,你不需要继承自数组。数组将比较算法委托给比较器。


23

继承与聚合(is-a与has-a)是实现同一目标的两种方法。

这个问题展示了一些选择上的权衡:继承与聚合


16

这两种模式非常相似,客户端代码使用方式也很相似。与上面最流行的答案所说的不同的是,它们都允许在运行时选择算法

两者之间的区别在于,策略模式允许不同的实现以完全不同的方式来实现所需的结果,而 模板方法模式 指定一个总体算法(即 "模板" 方法),用于实现结果——只留给特定实现(子类)某些模板方法的详细信息。这是通过使模板方法调用一个或多个抽象方法来完成的,这些抽象方法被子类覆盖(即实现),而模板方法本身既不是抽象的,也不被子类覆盖。

客户端代码使用指向具体子类实例的抽象类类型的引用/指针调用模板方法,这可以在运行时确定,就像使用策略模式一样。


9

模板方法:

  1. 它基于继承
  2. 定义了算法的框架,子类无法更改
  3. 只有某些操作可以在子类中被覆盖
  4. 父类完全控制算法
  5. 绑定在编译时完成

策略:

  1. 它基于委托/组合
  2. 通过修改方法行为来改变对象的内部结构
  3. 用于在算法族之间切换
  4. 它通过完全更改对象的行为来在运行时更改对象的行为
  5. 绑定在运行时完成

请查看模板方法策略文章以获得更好的理解。

相关帖子:

JDK中的模板设计模式,找不到定义要按顺序执行的方法集的方法

策略模式的真实世界例子


4
不,它们不一定以同样的方式使用。“模板方法”模式是一种为将来的实现者提供“指导”的方法。你告诉他们,“所有人对象必须有社会安全号码”(这是一个微不足道的例子,但它确实传达了正确的思想)。
“策略”模式允许切换多个可能的实现。它通常不是通过继承实现的,而是通过让调用者传递所需的实现来实现的。例如,ShippingCalculator 可能会提供计算税费的几种不同方式之一(如 NoSalesTax 实现和 PercentageBasedSalesTax 实现)
因此,有时候客户端实际上会告诉对象要使用哪种策略。如:
myShippingCalculator.CalculateTaxes(myCaliforniaSalesTaxImpl);

但是对于基于模板方法的对象,客户端永远不会这样做。事实上,客户端甚至可能不知道一个对象是基于模板方法的。在模板方法模式中,那些抽象方法甚至可能是受保护的,这种情况下客户端甚至不知道它们的存在。


4
我建议您阅读this文章,它通过实际案例解释了其中的差异。 文章摘录 "可以看出,实现类也依赖于模板方法类。这种依赖关系会导致在想要更改算法步骤时必须更改模板方法。另一方面,策略完全封装了算法。它使实现类能够完全定义算法。因此,如果有任何更改,就不需要更改以前编写的类的代码。这是我选择策略设计类的主要原因。
模板方法的一个特点是模板方法控制算法。这在其他情况下可能是好事,但在我的问题中,这限制了我设计类的能力。另一方面,策略不控制算法的步骤,这使我能够添加完全不同的转换方法。因此,在我的情况下,策略对我有帮助。
策略的一个缺点是存在过多的代码冗余和较少的代码共享。正如本文所示例子中明显的那样,我不得不在四个类中反复重复相同的代码。因此,很难维护,因为如果系统的实现(例如所有都通用的步骤4)发生更改,则必须在所有5个类中更新。另一方面,在模板方法中,我只能更改超类,这些更改会反映到子类中。因此,模板方法在类之间提供了非常低的冗余和高度的代码共享。
策略还允许在运行时更改算法。在模板方法中,必须重新初始化对象。策略的这个特性提供了大量的灵活性。从设计角度来看,应该优先选择组合而不是继承。因此,使用策略模式也成为开发的首选。"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接