模板方法和策略模式有啥区别?
Posted
技术标签:
【中文标题】模板方法和策略模式有啥区别?【英文标题】:What is the difference between the template method and the strategy patterns?模板方法和策略模式有什么区别? 【发布时间】:2010-10-14 17:34:50 【问题描述】:谁能给我解释一下模板方法模式和策略模式有什么区别?
据我所知,它们 99% 相同——唯一的区别是 模板方法模式有一个抽象类作为基础 类,而策略类使用已实现的接口 按每个具体的策略类。
然而,就客户端而言,它们的消费方式完全相同——这是否正确?
【问题讨论】:
SO 中的这篇文章对同一问题有更好的答案:***.com/questions/464524/… gob00st 链接到的问题是策略和桥梁之间的区别。这根本不是这个问题的答案。 【参考方案1】:当特定操作具有一些可以根据其他变化的原始行为定义的不变行为时,使用模板模式。抽象类定义了不变的行为,而实现类定义了依赖的方法。
在一个策略中,行为实现是独立的——每个实现类都定义了行为,并且它们之间没有共享代码。两者都是行为模式,因此,客户以几乎相同的方式消费。通常策略有一个公共方法——execute()
方法,而模板可以定义一组公共方法以及一组支持子类必须实现的私有原语。
这两种模式可以很容易地一起使用。您可能有一个策略模式,其中多个实现属于使用模板模式实现的一系列策略。
【讨论】:
这听起来对我来说是正确的,但是为什么WikiPedia 提到“策略模式是在运行时选择算法的行为”?它也可以用于在编译时选择算法的行为,就像模板方法一样?我错过了什么吗? @BornToCode 我假设他们在谈论的是在运行时选择特定策略。例如,有几种方法可以在数值上找到方程的根。根据问题域或数据,您可能会选择 Newton-Raphson、Euler 或其他一些求解方程的策略。每一个都是一种策略。较大的算法,其中求解方程是其中的一部分,根据问题的某些质量选择采用的策略。 是的,但策略模式不应该只用于这些情况吗?我的意思是,如果我只需要在编译时选择算法的行为,我还是应该使用策略模式,还是不应该那样使用? @BornToCode I would say that a strategy is most useful when the choice is dynamic.模板基本上是一种为已知建立不同的相关行为的方法。您将使用一些策略(尽管不一定是策略模式)来选择要采用的模板化行为。例如,产品继承——您将创建一个基础产品,为不同的产品添加功能。选择要实例化的产品类型(类)可能取决于从中加载的表/视图。策略模式并没有真正发挥作用。 @BornToCode 这不是非此即彼的事情,它是肯定的。在合适的地方应用模式,在有用的地方组合模式。【参考方案2】:两者的主要区别在于具体算法的选择。
使用模板方法模式,这发生在编译时,通过子类化模板。每个子类通过实现模板的抽象方法来提供不同的具体算法。当客户端调用模板外部接口的方法时,模板会根据需要调用其抽象方法(其内部接口)来调用算法。
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);
总结:
模板方法模式:编译时通过子类化选择算法 策略模式:运行时算法选择由包含【讨论】:
两种模式都支持运行时选择所使用的算法(对于模板方法,您可以执行类似if (config.useAlgoA) impl = new AlgoA() else impl = new AlgoB()
的操作),因此此答案不正确。
当然你可以这样做,但是你没有使用模板模式。事实上,这几乎就是创建 Strategy 实例的代码的样子!
-1,我认为这个答案(尽管并非完全错误)忽略了真正差异所在。 @tvanfosson 的回答要好得多。
@Karoly Nyisztor 他们都可以“替换行为”和“提供扩展点”。无论是行为还是扩展,这实际上取决于您应用给定模式的上下文。您也可以将模板方法模式的每个子类称为“策略”,或者将策略模式中的每个策略类称为“扩展”,这只是措辞。事实是,除了这个答案提到的差异之外,他们做同样的事情。所以这是正确的答案。
两种模式的具体算法选择方式相同。通过调用new ConcreteAlgorithm1()
与new ConcreteAlgorithm2()
进行选择。显然,选择发生在运行时(在编译时做出算法选择意味着对其进行硬编码)。两者的主要区别在于具体算法是如何实现的。它是作为子类实现的还是作为单独的接口实现的?前者是模板。后者是一种策略。区别可以概括为组合与继承,这是 GoF 书中的一个共同主题。【参考方案3】:
我认为这两种模式的类图显示了差异。
策略 将算法封装在一个类中Link to image
模板法 将算法的确切步骤推迟到子类Link to Image
【讨论】:
【参考方案4】:您可能是指模板方法模式。 你是对的,它们满足非常相似的需求。 如果您有一个“模板”算法具有定义的步骤,其中子类覆盖这些步骤以更改一些细节,我会说最好使用模板方法。 在策略的情况下,您需要创建一个接口,而不是继承,您使用的是委托。我会说这是一个更强大的模式,根据 DIP 可能更好 - 依赖倒置原则。它更强大,因为你清楚地定义了一个新的策略抽象——一种做某事的方式,它不适用于模板方法。所以,如果这个抽象是有意义的——使用它。但是,使用模板方法可能会在简单的情况下为您提供更简单的设计,这也很重要。 考虑哪些词更合适:你有模板算法吗?或者这里的关键是你有一个抽象的策略——做某事的新方法
模板方法示例:
Application.main()
Init();
Run();
Done();
在这里,您从应用程序继承并替换将在 init、run 和 done 上完成的确切操作。
策略示例:
array.sort (IComparer<T> comparer)
在这里,在编写比较器时,您不会从数组继承。 Array 将比较算法委托给比较器。
【讨论】:
我认为这是一个很好的答案【参考方案5】:相似之处
策略和模板方法模式之间有很多相似之处。策略和模板方法模式都可以用于满足开闭原则,使软件模块易于扩展而无需更改其代码。这两种模式都代表了通用功能与该功能的详细实现的分离。但是,它们在提供的粒度方面略有不同。
区别
以下是我在研究这两种模式时观察到的一些差异:
-
在Strategy中,客户端和策略之间的耦合更多
松散,而在模板方法中,两个模块更紧密
耦合。
在策略中,虽然抽象类可以,但主要使用接口
也可以根据情况使用,具体类不
使用,而在模板方法中主要是抽象类或具体
使用类,不使用接口。
在策略模式中,通常类的整个行为是
用接口表示,另一方面,使用模板方法
用于减少代码重复,样板代码定义在
基础框架或抽象类。在模板方法中,甚至可以有一个具体的类
使用默认实现。
简单来说,你可以改变整个策略(算法)在
策略模式,然而,在模板方法中,只有一些东西
更改(部分算法),其余部分保持不变。在模板方法中,不变的步骤在抽象基类中实现,而
变体步骤要么被赋予默认实现,要么没有
实施。在 Template 方法中,组件设计器
规定了算法所需的步骤,以及算法的排序
步骤,但允许组件客户端扩展或替换一些
这些步骤的数量。
图片取自bitesized博客。
【讨论】:
【参考方案6】:继承与聚合(is-a 与 has-a)。这是实现同一目标的两种方法。
这个问题显示了选择之间的一些权衡:Inheritance vs. Aggregation
【讨论】:
【参考方案7】:两者非常相似,并且都以相似的方式被客户端代码使用。与上面最受欢迎的答案不同,两者都允许在运行时选择算法。
两者之间的区别在于,策略模式允许不同的实现使用完全不同的方式来达到预期的结果,模板方法模式指定了一个总体用于实现结果的算法(“模板”方法)——留给特定实现(子类)的唯一选择是所述模板方法的某些细节。这是通过让模板方法调用一个或多个由子类覆盖(即实现)的 abstract 方法来完成的,这与模板方法本身不是抽象的和不会被子类覆盖。
客户端代码使用抽象类类型的引用/指针调用模板方法,该引用/指针指向具体子类之一的实例,可以在运行时确定,就像使用策略模式时一样。
【讨论】:
【参考方案8】:模板方法:
-
它基于继承
定义不能被子类改变的算法骨架。在子类中只能覆盖某些操作
父类完全控制算法,仅在某些步骤与具体类不同
绑定在编译时完成
Template_method 结构:
策略:
-
它基于委托/组合
它通过修改方法行为来改变对象的内部结构
它用于在算法系列之间切换
它通过在运行时完全用另一种算法替换一种算法来改变对象在运行时的行为
绑定在运行时完成
Strategy结构:
查看Template method 和Strategy 文章以获得更好的理解。
相关帖子:
Template design pattern in JDK, could not find a method defining set of methods to be executed in order
Real World Example of the Strategy Pattern
【讨论】:
【参考方案9】:我建议你阅读this 文章。它解释了一个真实案例的差异。
引用文章
"正如我们所见,实现类也依赖于模板 方法类。这种依赖会导致更改模板方法,如果 想改变算法的一些步骤。在另一 side strategy 完全封装了算法。它给出了 实现类以完全定义算法。因此,如果 任何更改都需要更改以前的代码 书面课程。这是我选择策略的主要原因 设计课程。
模板方法的一个特点是模板方法控制 算法。在其他情况下这可能是一件好事,但在我的 问题限制了我设计课程。在另一 侧策略不控制算法的步骤,它使 我添加完全不同的转换方法。因此在我的情况下 策略帮助我实施。
策略的一个缺点是有太多的代码冗余和 减少代码共享。正如在这个例子中很明显 文章我必须再次在四个类中重复相同的代码 再次。因此很难维护,因为如果实施 我们的系统,例如所有共同的步骤 4 被更改,然后我 将不得不在所有 5 个类中更新它。另一方面,在 模板方法,我只能更改超类并且更改是 反映到子类中。因此模板方法给出了一个非常 低冗余量和大量代码共享 类。
策略还允许在运行时更改算法。在模板中 方法一将不得不重新初始化对象。这个特点 策略提供了很大的灵活性。从设计角度 观点一个人必须更喜欢组合而不是继承。因此使用 战略模式也成为发展的首要选择。”
【讨论】:
【参考方案10】:不,它们不一定以相同的方式消耗。 “模板方法”模式是一种为未来的实现者提供“指导”的方式。您是在告诉他们,“所有 Person 对象都必须有一个社会安全号码”(这是一个微不足道的例子,但它正确地理解了这个想法)。
策略模式允许切换多个可能的实现。它不是(通常)通过继承实现的,而是让调用者传入所需的实现。一个示例可能是允许为 ShippingCalculator 提供几种不同的税收计算方式之一(NoSalesTax 实现,可能还有 PercentageBasedSalesTax 实现)。
因此,有时,客户端实际上会告诉对象使用哪种策略。如
myShippingCalculator.CalculateTaxes(myCaliforniaSalesTaxImpl);
但是对于基于模板方法的对象,客户端永远不会这样做。事实上,客户甚至可能不知道一个对象是基于模板方法的。模板方法模式中的那些抽象方法甚至可能受到保护,在这种情况下,客户端甚至都不知道它们的存在。
【讨论】:
【参考方案11】:在策略模式中,子类运行并控制算法。这里的代码在子类中重复。该算法的知识以及如何实现它分布在许多类中。
在模板模式中,基类有算法。它最大化子类之间的重用。由于算法位于一个地方,基类保护它。
【讨论】:
【参考方案12】:模板模式类似于策略模式。这两种模式在范围和方法上有所不同。
策略用于允许调用者改变整个算法,例如如何计算不同类型的税,而模板方法用于改变算法中的步骤。因此,策略的粒度更粗。模板允许在后续操作中进行更细粒度的控制,同时允许这些细节的实现发生变化。
另一个主要区别是策略使用委托,而模板方法使用继承。在 Strategy 中,算法被委托给另一个 xxxStrategy 类,主题将引用该类,但使用 Template 您可以继承基类并覆盖方法以进行更改。
来自http://cyruscrypt.blogspot.com/2005/07/template-vs-strategy-patterns.html
【讨论】:
【参考方案13】:模板模式:
模板方法是让子类重新定义算法的某些步骤,而不改变基类中定义的算法的主要结构和步骤。 模板模式通常使用继承,因此可以在基类中提供算法的通用实现,如果需要,子类可能会选择覆盖它。
public abstract class RobotTemplate
/* This method can be overridden by a subclass if required */
public void start()
System.out.println("Starting....");
/* This method can be overridden by a subclass if required */
public void getParts()
System.out.println("Getting parts....");
/* This method can be overridden by a subclass if required */
public void assemble()
System.out.println("Assembling....");
/* This method can be overridden by a subclass if required */
public void test()
System.out.println("Testing....");
/* This method can be overridden by a subclass if required */
public void stop()
System.out.println("Stopping....");
/*
* Template algorithm method made up of multiple steps, whose structure and
* order of steps will not be changed by subclasses.
*/
public final void go()
start();
getParts();
assemble();
test();
stop();
/* Concrete subclass overrides template step methods as required for its use */
public class CookieRobot extends RobotTemplate
private String name;
public CookieRobot(String n)
name = n;
@Override
public void getParts()
System.out.println("Getting a flour and sugar....");
@Override
public void assemble()
System.out.println("Baking a cookie....");
@Override
public void test()
System.out.println("Crunching a cookie....");
public String getName()
return name;
请注意,在上面的代码中,go() 算法步骤将始终相同,但子类可能会为执行特定步骤定义不同的配方。
策略模式:
策略模式是让客户端在运行时选择具体的算法实现。所有的算法都是孤立和独立的,但是实现了一个通用的接口,并且没有在算法中定义特定步骤的概念。
/**
* This Strategy interface is implemented by all concrete objects representing an
* algorithm(strategy), which lets us define a family of algorithms.
*/
public interface Logging
void write(String message);
/**
* Concrete strategy class representing a particular algorithm.
*/
public class ConsoleLogging implements Logging
@Override
public void write(String message)
System.out.println(message);
/**
* Concrete strategy class representing a particular algorithm.
*/
public class FileLogging implements Logging
private final File toWrite;
public FileLogging(final File toWrite)
this.toWrite = toWrite;
@Override
public void write(String message)
try
final FileWriter fos = new FileWriter(toWrite);
fos.write(message);
fos.close();
catch (IOException e)
System.out.println(e);
如需完整源代码,请查看我的 github repository。
【讨论】:
【参考方案14】:支持合成。 为您提供在运行时更改对象行为的灵活性。 客户端代码与解决方案/算法代码之间的耦合更少。策略设计模式
继承优于组合 在您的基类中定义算法。可以在子类中自定义各个算法。模板方法设计模式
【讨论】:
【参考方案15】:策略作为接口公开,模板方法作为抽象类公开。这通常在框架中大量使用。 例如 Spring框架的MessageSource类是一个解析消息的策略接口。客户端使用此接口的特定实现(策略)。
以及同一个接口AbstractMessageSource的抽象实现,它具有解析消息的通用实现,并暴露了resolveCode()抽象方法,以便子类可以按照自己的方式实现它们。 AbstractMessageSource 是模板方法的一个例子。
http://docs.spring.io/spring/docs/4.1.7.RELEASE/javadoc-api/org/springframework/context/support/AbstractMessageSource.html
【讨论】:
【参考方案16】:在此设计模式的模板方法中,一个或多个算法步骤可以被子类覆盖以允许不同的行为,同时确保仍然遵循总体算法(Wiki)。
模式名称模板方法意味着它是什么。假设我们有一个方法 CalculateSomething() 并且我们想要模板化这个方法。这个方法将在基类中声明一个非虚方法。说方法看起来像这样。
CalculateSomething()
int i = 0;
i = Step1(i);
i++;
if (i> 10) i = 5;
i = Step2(i);
return i;
Step1和Step2方法的实现可以由派生类给出。
在策略模式中,base 没有提供实现(这就是为什么 base 在类图中实际上是一个接口的原因)
经典的例子是排序。根据需要排序的对象数量,创建相应的算法类(合并、冒泡、快速等),并将整个算法封装在每个类中。
现在我们可以将排序实现为模板方法吗?当然可以,但是您不会发现太多/任何共性被抽象出来并放置在基本实现中。所以它违背了模板方法模式的目的。
【讨论】:
【参考方案17】:我认为主要区别在于,对于模板,您需要一个算法来做某事,但是假设在该算法的中间,您想要运行不同的行为,以便您可以发送接口的实现以使该算法在运行时。
但是使用策略,您实际上有完全不同的算法执行,而不仅仅是算法的变体,然后您选择运行哪个算法,但是模板您只有一个带有变体的算法。
最后,您可以根据需要实施并将模板用作策略,反之亦然,但我看到了不同。
【讨论】:
【参考方案18】:模板方法模式擅长阐明整体算法步骤,而策略模式适合灵活性和可复用性,因此如果需要,可以将策略组合在一起,例如:jdk8中的许多功能接口如Comparator.reversed().thenComparing(Comparator)
是一个角色战略。
模板方法模式是焦点更高的内聚,但策略模式与上下文对象松散耦合以分离关注点。
策略很容易维护,因为上下文不知道具体的策略,只要上下文中的主要算法发生变化,就不会影响策略。另一方面,如果您在抽象模板类中更改了算法的骨架,可能会影响其子类的升级。
【讨论】:
【参考方案19】:它们都是不同的技术来达到相同的结果,所以问题是什么时候使用。
如果您正在使用无法访问源代码的框架或库,并且想要更改类的某些行为,那么您必须使用模板方法。这意味着简单的继承。 如果您正在开发一个类,并且很明显某些部分的逻辑需要以不同的方式实现以处理各种情况,请采用策略模式。策略比模板方法更可靠。它涵盖了依赖倒置和打开/关闭原则。所以它是可扩展的,也很容易测试。 如果您正在开发一个类,并且您不知道将来会发生什么变化,请尽可能将您的逻辑划分为单独的和单一的负责函数。只是。 (既不是模板方法也不是策略)。【讨论】:
以上是关于模板方法和策略模式有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章