一、模板方法模式的内容
模板模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。模板方法是一种类行为型模式。
模板方法模式是基于继承的代码复用基本技术,模板方法模式的结构和用法也是面向对象设计的核心之一。在模板方法模式中,可以将相同的代码放在父类中,而将不同的方法实现放在不同的子类中。
在模板方法模式中,我们需要准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来让子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现,这就是模板方法模式的用意。模板方法模式体现了面向对象的诸多重要思想,是一种使用频率较高的模式。
模板方法模式是我们在编程过程中经常使用的一种设计模式。父类设计了几个子类共同的方法,然后父类有具体方法,抽象方法,和钩子方法(可以是具体方法也可以是抽象方法)。在使用过程中,子类根据不同的情况,重写不同的方法即可。子类可以通过钩子方法方向控制父类的行为。
模板方法模式和策略模式非常的相似,只不过模板方法模式的目的是对整个算法的构造过程的分解,而模板方法模式时对不同算法的选择过程。利用模板方法模式,你可以让一个算法实现不同的功能,而利用策略模式,我们可以选择不同的算法。
二、 模版方法模式的结构
模版方法模式的静态结构如下图所示。
这里涉及到两个角色:
- 抽象模版(AbstractClass)角色有如下的责任:
定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤。
定义并实现了一个模版方法。这个模版方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。
- 具体模版(ConcreteClass)角色有如下的责任:
实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。
每一个抽象模版角色都可以有任意多个具体模版角色与之对应,而每一个具体模版角色都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。
三、模板方法模式分析
- 模板方法模式是一种类的行为型模式,在它的结构图中只有类之间的继承关系,没有对象关联关系。
- 在模板方法模式的使用过程中,要求开发抽象类和开发具体子类的设计师之间进行协作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步骤。实现这些具体逻辑步骤的方法称为基本方法(Primitive Method),而将这些基本法方法汇总起来的方法称为模板方法(Template Method),模板方法模式的名字从此而来。
- 模板方法:一个模板方法是定义在抽象类中的、把基本操作方法组合在一起形成一个总算法或一个总行为的方法。
- 基本方法:基本方法是实现算法各个步骤的方法,是模板方法的组成部分。
- 抽象方法(Abstract Method)
- 具体方法(Concrete Method)
- 钩子方法(Hook Method):“挂钩”方法和空方法
- 在模板方法模式中,由于面向对象的多态性,子类对象在运行时将覆盖父类对象,子类中定义的方法也将覆盖父类中定义的方法,因此程序在运行时,具体子类的基本方法将覆盖父类中定义的基本方法,子类的钩子方法也将覆盖父类的钩子方法,从而可以通过在子类中实现的钩子方法对父类方法的执行进行约束,实现子类对父类行为的反向控制。
- 为了防止改变模板方法中的算法,可以将模板方法声明为final。
- 策略模式和模板方法模式都封装了算法,一个用了组合,一个用了继承。策略模式定义了算法头,客户可以轻易的使用不同的算法,而模板方法模式定义了算法的大纲,由其子类定义某些步骤的内容,算法的结构是无法改变的。
- 工厂方法是模板方法的一种特殊变体。
四、模板方法设计模式示例代码
public abstract class HookDemo
{
public abstract void getData();
public void convertData()
{
System.out.println("通用的数据转换操作。");
}
public abstract void displayData();
public void process()
{
getData();
if(isValid())
{
convertData();
}
displayData();
}
public boolean isValid()
{
return true;
}
}
class SubHookDemo extends HookDemo
{
public void getData()
{
System.out.println("从XML配置文件中获取数据。");
}
public void displayData()
{
System.out.println("以柱状图显示数据。");
}
public boolean isValid()
{
return false;
}
}
class Client
{
public static void main(String a[])
{
HookDemo hd;
hd=new SubHookDemo();
hd.process();
}
}
abstract class DBOperator
{
public abstract void connDB();
public void openDB()
{
System.out.println("打开数据库");
}
public void useDB()
{
System.out.println("使用数据库");
}
public void closeDB()
{
System.out.println("关闭数据库");
}
public void process()
{
connDB();
openDB();
useDB();
closeDB();
}
}
class DBOperatorSubA extends DBOperator
{
public void connDB()
{
System.out.println("使用JDBC-ODBC桥接连接数据库");
}
}
class DBOperatorSubB extends DBOperator
{
public void connDB()
{
System.out.println("使用连接池连接数据库");
}
}
class Client
{
public static void main(String a[])
{
DBOperator db1;
db1=new DBOperatorSubA();
db1.process();
System.out.println("---------------------------------------");
db1=new DBOperatorSubB();
db1.process();
}
}
五、模式扩展
- 模板方法模式鼓励我们恰当使用继承,此模式可以用来改写一些拥有相同功能的相关类,将可复用的一般性的行为代码移到父类里面,而将特殊化的行为代码移到子类里面。这也进一步说明,虽然继承复用存在一些问题,但是在某些情况下还是可以给开发人员带来方便,模板方法模式就是体现继承优势的模式之一。
- 在模板方法模式中,子类不显式调用父类的方法,而是通过覆盖父类的方法来实现某些具体的业务逻辑,父类控制对子类的调用,这种机制被称为好莱坞原则(Hollywood Principle),好莱坞原则的定义为:“不要给我们打电话,我们会给你打电话(Don‘t call us, we’ll call you)”。
- 在模板方法模式中,好莱坞原则体现在:子类不需要调用父类,而通过父类来调用子类,将某些步骤的实现写在子类中,由父类来控制整个过程。
- 好莱坞原则告诉我们,将决策权放在最高层模块中,以便决定如何以及何时调用底层模块。
- 钩子方法的引入使得子类可以控制父类的行为。
- 最简单的钩子方法就是空方法,也可以在钩子方法中定义一个默认的实现,如果子类不覆盖钩子方法,则执行父类的默认实现代码。
- 比较复杂一点的钩子方法可以对其他方法进行约束,这种钩子方法通常返回一个boolean类型,即返回true或false,用来判断是否执行某一个基本方法。
六、模板方法模式优缺点
模板方法模式的优点- 模板方法模式在一个类中形式化地定义算法,而由它的子类实现细节的处理。
- 模板方法模式是一种代码复用的基本技术。
- 模板方法模式导致一种反向的控制结构,通过一个父类调用其子类的操作,通过对子类的扩展增加新的行为,符合“开闭原则”。
- 每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,但是更加符合“单一职责原则”,使得类的内聚性得以提高。
七、模板方法模式适用环境
在以下情况下可以使用模板方法模式:- 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
- 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
- 对一些复杂的算法进行分割,将其算法中固定不变的部分设计为模板方法和父类具体方法,而一些可以改变的细节由其子类来实现。
- 控制子类的扩展。
八、模板方法模式应用
(1) 模板方法模式广泛应用于框架设计(如Spring,Struts等)中,以确保父类控制处理流程的逻辑顺序(如框架的初始化)。(2) Java单元测试工具JUnit中的TestCase类的设计:
public void runBare() throws Throwable {
setUp();
try {
runTest();
}
finally {
tearDown();
}
}
Template method (recognizeable by behavioral methods which already have a "default" behaviour definied by an abstract type)
- All non-abstract methods of
java.io.InputStream
,java.io.OutputStream
,java.io.Reader
andjava.io.Writer
. - All non-abstract methods of
java.util.AbstractList
,java.util.AbstractSet
andjava.util.AbstractMap
. -
javax.servlet.http.HttpServlet
, all thedoXXX()
methods by default sends a HTTP 405 "Method Not Allowed" error to the response. You're free to implement none or any of them.
九、参考资料
- http://blog.csdn.net/rocket5725/article/details/4327387
- http://stackoverflow.com/questions/1673841/examples-of-gof-design-patterns
- 《设计模式》刘伟主编清华大学出版社
- 《Head First 设计模式》