意图
Template Method 时行为模式的一种,让你定义一个算法的骨架,允许子类重新定义在不改变结构的情况下重新定义算法的某些步骤。
问题
假设你正在写一个挖掘办公文档数据的应用程序。用户想要输入各种格式的文件(PDF,DOC,CSV),程序输出给他们有用的格式化数据。
第一个版本你仅支持DOC文件。下一个版本支持CSV格式文件。一个月后,你又增加了从PDF中分析数据的功能。
这时候,你注意到这三种转换算法有很多相似之处。他们除了处理不同的文件格式外,对数据的萃取和分析都是一致的。这点对减少重复代码和算法独立很有帮助。
客户端代码使用这些算法还有另一个问题。选择合适的行为的许多条件都需要依赖选择的算法。如果这三个转换类都遵循一个通用接口或者一个基类,这些条件就可以用多态消灭掉。
解决
Template Method模式建议把一个算法分成几个步骤,把步骤封装到方法中并在一个“template”方法中挨个调用。
子类可以复写特定的步骤,但是无法修改模板方法,保持算法的结构不变。
在我们的应用中,我们为所有的转换算法创建一个通用的基类。模版方法的关键步骤看起来像这样子:
1 | method templateMethod() is |
首先,你要把模版方法声明为abstract
的,让所有的步骤都需要子类实现。然后,在子类的所有方法都实现后,再把子类共有的部分放到基类中,并使这些步骤(方法)成为可选的复写方法。
在我们的例子中,打开和关闭文档的操作对三种文件方式都不同,所以这点不需要默认实现。但是其他的步骤,比如,转换和分析,可以放到基类中,使得子类间共享这些代码。
还有另外一种步骤,叫做钩子。钩子时可选的步骤,他默认是空的。因此,即使一个模板没有腹泻够爱方法,它依然可以工作。通常,钩子被用来再算法开始之前或者结束之后给子类提供机会来做一些事情。
真实世界的类比
大众住房建设
建筑商使用模板方法来进行大规模住房建设。有一个标准的建筑模板来描述施工步骤:打地基,筑桩,垒墙,走水电等。
但是,尽管标准化,建筑商可以稍微改变每一步,使一个房子有点不同(即添加更多的窗口,用不同的颜色涂墙…)
结构
Abstract class声明了算法的每个步骤,以及用来统筹调用每个步骤的模版方法。这些步骤可以被声明为
abstract
或者有默认的实现。Concrete class实现模版方法中定义的每个抽象步骤,但是它也可以复写抽象类中的默认实现。具体的实现类不能自己复写模板方法。
适用性
当子类能够扩展基本算法而不能修改他的结构时。
模版方法模式把算法分割成几个定义在基类中的个性化的步骤,可以被子类轻松扩展,并保持算法的结构。
当你需要几个类来做相似的事情但是又有一些不同时时。当你修改其中一个类时,你必须同时修改其他的类。
模版方法模式把相似的算法步骤抽取到基类中。代码的不同部分保留在各个子类中。
如何实现
分析算法,看他们是否可以被分解成几个步骤。这些步骤中那些事所有子类公用的,哪些事子类唯一的。
创建抽象基类,并且声明模版方法。列出这些算法的结构,把他们声明为
abstract
。考虑哪些方法应当被声明为final
来阻止子类的重写。如果所有的步骤都是抽象的那就好了。然而,一些步骤适合有默认实现。子类必须实现哪些抽象的方法。
考虑在合适的步骤之间增加钩子,以及在模板方法的开始和结束之间添加钩子。
算法的每个变种都从抽象类继承。变种必须实现所有必要的步骤,但是可以选择性重写可选步骤。
优点
- 帮助消除重复代码。
缺点
你将受限于现有算法的架构。
如果通过子类复写默认的步骤实现就违背了里氏替换原则。
模板方法往往难以保持更多的步骤。
和其他模式的关系
Factory Method是专业化的Template Method。另一方面,Factory Method通常作为大型Template Method的一个步骤。
Template Method采用继承方式来扩展不同的类取修改算法。Strategy采用修改委托对象行为的方式来改变对象的行为。Template Method工作在类级别。Strategy允许你更改单个对象的行为。
小结
Java核心库中有些例子:
java.io.InputStream,java.io.OutputStream,java.io.Reader和java.io.Writer中所有非抽象的方法。
java.util.AbstractList,java.util.AbstractSet和java.util.AbstractMap中所有非抽象的方法。
javax.servlet.http.HttpServlet中所有的doXXX()方法都默认响应一个HTTP 405 “Method Not Allowed”错误。你可以自由复写他们。
参考
翻译整理自:https://refactoring.guru/design-patterns/template-method