模板方法
意图
模板方法是一种行为设计模式,它在超类中定义算法的框架,但允许子类在不改变算法结构的情况下重写算法的特定步骤。

问题
假设您正在创建一个分析公司文档的数据挖掘应用程序。用户以各种格式(PDF、DOC、CSV)向应用程序提供文档,应用程序试图以统一的格式从这些文档中提取有意义的数据。
该应用程序的第一个版本只能处理DOC文件。在接下来的版本中,它能够支持CSV文件。一个月后,你“教”它从PDF文件中提取数据。

数据挖掘类包含大量重复的代码。
在某些时候,您会注意到这三个类都有很多相似的代码。虽然在所有类中处理各种数据格式的代码完全不同,但用于数据处理和分析的代码几乎是相同的。去除代码重复,保持算法结构完整不是很好吗?
还有一个与使用这些类的客户端代码相关的问题。它有很多条件,根据处理对象的类选择适当的操作过程。如果这三个处理类都有一个公共接口或基类,那么就可以在客户端代码中消除条件,并在调用处理对象上的方法时使用多态性。
解决方案
模板方法模式建议您将算法分解为一系列步骤,将这些步骤转换为方法,并在单个方法中放置对这些方法的一系列调用模板方法。步骤可能是摘要
,或者有一些默认实现。为了使用该算法,客户机应该提供自己的子类,实现所有抽象步骤,并在需要时覆盖一些可选步骤(但不包括模板方法本身)。
让我们看看这将如何在我们的数据挖掘应用程序中发挥作用。我们可以为所有三种解析算法创建一个基类。该类定义了一个模板方法,该方法由对各种文档处理步骤的一系列调用组成。

Template方法将算法分解为步骤,允许子类重写这些步骤,但不重写实际的方法。
首先,我们可以声明所有步骤摘要
,强制子类为这些方法提供自己的实现。在我们的例子中,子类已经有了所有必要的实现,所以我们唯一需要做的就是调整方法的签名以匹配超类的方法。
现在,让我们看看我们可以做些什么来消除重复的代码。对于不同的数据格式,打开/关闭文件和提取/解析数据的代码似乎是不同的,所以没有必要修改这些方法。但是,其他步骤(如分析原始数据和编写报告)的实现非常相似,因此可以将其拉入基类,在基类中,子类可以共享该代码。
如你所见,我们有两种类型的步骤:
- 抽象的步骤必须由每个子类实现
- 可选步骤已经有一些默认实现,但如果需要,仍然可以重写
还有一种类型的步骤,叫做钩子.钩子是具有空主体的可选步骤。即使钩子没有被覆盖,模板方法也可以工作。通常,钩子被放置在算法的关键步骤之前和之后,为算法的子类提供额外的扩展点。
真实的模拟

典型的架构计划可以稍作修改,以更好地适应客户的需求。
模板法可以应用于大规模房屋施工。建造标准房屋的建筑计划可能包含几个扩展点,这些扩展点可以让潜在的业主调整最终房屋的一些细节。
每一个建筑步骤,如打地基、搭框架、砌墙、安装水管和水电线路等,都可以稍微改变一下,使最终的房子与其他房子略有不同。
结构


的抽象类声明作为算法步骤的方法,以及按特定顺序调用这些方法的实际模板方法。这些步骤要么是宣布的
摘要
或者有一些默认实现。具体类可以重写所有步骤,但不能重写模板方法本身。
伪代码
在本例中,模板方法在一个简单的策略电子游戏中,模式为人工智能的各个分支提供了一个“骨架”。

一个简单的AI类电子游戏。
游戏中的所有种族都拥有几乎相同类型的单位和建筑。因此,你可以为各种种族重用相同的AI结构,同时能够覆盖一些细节。通过这种方法,你可以覆盖兽人的AI,使其更具攻击性,使人类更具防御性,并使怪物无法建造任何东西。在游戏中添加一个新种族需要创建一个新的AI子类,并覆盖基本AI类中声明的默认方法。
//抽象类定义了一个模板方法,该方法包含由调用组成的算法框架,通常是对//抽象基元操作的调用。具体的子类实现//这些操作,但保留模板方法本身。模板方法定义了一个算法的框架。method turn() is collectResources() buildStructures() buildUnits() attack() //有些步骤可以在基类中实现。method collectResources() is foreach (s in this.builtStructures) do .collect() //其中一些可能被定义为抽象。//一个类可以有多个模板方法。方法attack() is enemy = closestEnemy() if (enemy == null) sendScouts(map.center) else sendWarriors(enemy.position)抽象方法sendScouts(position)抽象方法sendWarriors(position) //具体类必须实现基类的所有抽象操作,但不能重写模板方法//本身。类OrcsAI扩展GameAI的方法buildStructures()是如果(有一些资源)那么//建立农场,然后兵营,然后据点。方法buildUnits()是if(有足够的资源),那么if(没有侦察)//构建peon,将其添加到侦察组。else //创建咕噜,添加到战士组。 // ... method sendScouts(position) is if (scouts.length > 0) then // Send scouts to position. method sendWarriors(position) is if (warriors.length > 5) then // Send warriors to position. // Subclasses can also override some operations with a default // implementation. class MonstersAI extends GameAI is method collectResources() is // Monsters don't collect resources. method buildStructures() is // Monsters don't build structures. method buildUnits() is // Monsters don't build units.
适用性
当您希望让客户端仅扩展算法的特定步骤,而不是扩展整个算法或其结构时,请使用Template Method模式。
模板方法允许您将一个整体算法转换为一系列单独的步骤,这些步骤可以很容易地由子类扩展,同时保持父类中定义的结构完整。
当您有几个类,它们包含几乎相同的算法,只有一些小差异时,请使用该模式。因此,当算法发生变化时,您可能需要修改所有类。
当您将这样的算法转换为模板方法时,还可以将具有类似实现的步骤拉入超类,从而消除代码重复。子类之间不同的代码可以保留在子类中。
如何实施
分析目标算法,看看是否可以将其分解为步骤。考虑哪些步骤对所有子类都是通用的,哪些步骤总是惟一的。
创建抽象基类并声明模板方法和一组表示算法步骤的抽象方法。通过执行相应的步骤,在模板方法中勾画出算法的结构。考虑制作模板方法
最后
以防止子类重写它。如果所有步骤最后都是抽象的,那也没关系。但是,有些步骤可能会受益于默认实现。子类不必实现这些方法。
考虑在算法的关键步骤之间添加钩子。
对于算法的每个变体,创建一个新的具体子类。它必须实现所有抽象步骤,但是五月还要重写一些可选选项。
利与弊
- 您可以让客户端只覆盖大型算法的某些部分,使它们不受发生在算法其他部分的更改的影响。
- 您可以将重复的代码拉到超类中。
- 有些客户端可能会受到算法框架的限制。
- 你可能会违反利斯科夫替换原理通过子类抑制默认步骤实现。
- 模板方法的步骤越多,维护起来就越困难。