1.1概述
定义了一个操作中算法的骨架,而将一些步骤延迟到子类中。模板方法使子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。这就是模板方法的定义。
类中的方法用以表明该类的实例所具有的行为,一个类可以有许多方法,而且类中的实例方法也可以调用该类中的其他若干个方法。在编写类的时候,可能需要将类的许多方法集成到一个实例方法中,即用一个实例方法封装若干个方法的调用,以表现一个算法的骨架,也就是说,调用该实例方法相当于按着一定顺序执行若干个方法。
例如,各类客运车站在安排乘客上车时都进行安全检查、验证车票、选择车体类型三个步骤(具体如下图一所示)。因此在一个抽象类Station中包含安全检查方法(safetyExamine)、验证车票方法(validateTicket)、选择车体类型方法(choiceCarriageType)表示乘车步骤的抽象方法,而且该抽象类中还包含有ridingStep()方法,该方法顺序调用安全检查方法、验证车票方法、选择车体类型方法,也就是说抽象类Station使用ridingStep()方法封装了乘车步骤。ridingStep()方法所调用的safetyExamine()、validateTicket()、choiceCarriageType()方法都是抽象方法,因此Station的子类:RailwayStation(火车站)在实现safetyExamine()、validateTicket()、choiceCarriageType()方法时,分别给出了自己的安全检查方式、检票车票方式和所选车体类型。当Station类声明的变量存放它的子类RailwayStation实例的引用后,该变量就可以调用ridingStep()方法展示乘客的乘车步骤。具体类关系图如下图一所示:
图一:乘车步骤
模板方法是关于怎样将若干个方法集成到一个方法中,以便形成一个解决问题的算法骨架。模板方法模式的关键是在一个抽象类中定义一个算法的骨架,即将若干个方法集成到一个方法中,并称该方法为一个模板方法,或简称为模板。模板方法所调用的其他方法通常称为抽象的方法,这些抽象方法相当于算法骨架中的各个步骤,这些步骤可以由子类去完成。
1.2模式的结构
模板方法模式包括以下两种角色:
(1)抽象模板(Abstract Template):抽象模板是一个抽象类。抽象模板定义了若干个方法以表示算法的各个步骤,这些方法中有抽象方法也有非抽象方法,其中的抽象方法称为原语操作(Primitive Operation)。重要的一点是,抽象模板中还定义了一个称之为模板方法的方法,该方法不仅包含有抽象模板中表示算法步骤的方法调用,而且也可以包含有定义在抽象模板中的其他对象的方法调用,即模板方法定义了算法的骨架。
(2)具体模板(Concrete Template):具体模板是抽象模板的子类,实现抽象模板中的原语操作。
模板方法模式结构的类图如下图二所示:
图二:模板方法模式的类图
1.3模板方法模式的优点
(1)可以通过在抽象模板定义模板方法给出成熟的算法步骤,同时又不限制步骤的细节,具体模板实现细节不会改变整个算法的框架。
(2)在抽象模板模式中,可以通过钩子方法对某些步骤进行挂钩,具体模板通过钩子可以选择算法骨架中的某些步骤。
1.4适合使用模板方法模式的情景
(1)设计者需要给出一个算法的固定步骤,并将某些步骤的具体实现留给子类来实现。
(2)需要对代码进行重构,将各个子类公共行为提取出来集中到一个共同的父类中以避免代码重复。
1.5模板方法模式的使用
以下通过一个简单的问题来描述怎样使用模板方法模式,这个简单的问题是:显示某个目录下全部文件的名字,比如可以按照文件的大小顺序、按最后修改的时间顺序或按文件名字的字典顺序来显示某个目录下全部文件的名字。
首先看一下本实例构建框架具体类和1.2模式的结构中类图的对应关系,如下图所示:
(1)抽象模板(Abstract Template)
本问题中,抽象模板(Abstract Template)角色是AbstractTemplate类。抽象模板中的模板方法是showFileName();抽象模板中表示具体步骤的方法是sort()和printFiles()方法,二者都是原语操作。具体代码如下:
package com.liuzhen.eighteen_template; import java.io.*; public abstract class AbstractTemplate { File[] allFiles; File dir; AbstractTemplate(File dir){ this.dir = dir; } public final void showFileName(){ allFiles = dir.listFiles(); sort(); printFiles(); } public abstract void sort(); public abstract void printFiles(); }
(2)具体模板(Concrete Template)
对于本问题,具体模板是ConcreteTemplate1和ConcreteTemplate2类。ConcreteTemplate1中的sort()方法把目录下的文件名字按其所引用文件被最后修改的时间顺序排列;ConcreteTemplate2中的sort()方法把目录下的文件名字按照其所引用文件的大小顺序排列。其具体代码如下:
ConcreteTemplate1.java
package com.liuzhen.eighteen_template; import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; public class ConcreteTemplate1 extends AbstractTemplate { ConcreteTemplate1(File dir) { super(dir); } @Override public void sort() { for(int i = 0;i < allFiles.length;i++){ for(int j = i+1;j < allFiles.length;j++){ if(allFiles[j].lastModified() < allFiles[i].lastModified()){ File file = allFiles[j]; allFiles[j] = allFiles[i]; allFiles[i] = file; } } } } @Override public void printFiles() { for(int i = 0;i < allFiles.length;i++){ long time = allFiles[i].lastModified(); Date date = new Date(time); SimpleDateFormat matter = new SimpleDateFormat("yyyy-mm-dd hh:mm:ss"); String str = matter.format(date); String name = allFiles[i].getName(); int k = i+1; System.out.println(k+" "+name+"("+str+")"); } } }
ConcreteTemplate2.java
package com.liuzhen.eighteen_template; import java.io.File; public class ConcreteTemplate2 extends AbstractTemplate { ConcreteTemplate2(File dir) { super(dir); } @Override public void sort() { for(int i = 0;i < allFiles.length;i++){ for(int j = i+1;j < allFiles.length;j++){ if(allFiles[j].length() < allFiles[i].length()){ File file = allFiles[i]; allFiles[i] = allFiles[j]; allFiles[j] = file; } } } } @Override public void printFiles() { for(int i = 0;i < allFiles.length;i++){ long fileSize = allFiles[i].length(); String name = allFiles[i].getName(); int k = i+1; System.out.println(k+" "+name+"("+fileSize+" 字节)"); } } }
(3)具体使用
通过EighteenApplication类来具体实现上述相关类和接口,来实现状态模式的运用,其代码如下:
package com.liuzhen.eighteen_template; import java.io.File; public class EighteenApplication { public static void main(String[] args){ File dir = new File("E:/KuGou"); AbstractTemplate template = new ConcreteTemplate1(dir); System.out.println(dir.getPath()+"目录下的文件:"); template.showFileName(); System.out.println(); template = new ConcreteTemplate2(dir); System.out.println(dir.getPath()+"目录下的文件:"); template.showFileName(); } }
运行结果:
E:KuGou目录下的文件: 1 罗凯楠 - 咱们屯里的人.mp3(2015-29-11 11:29:37) 2 唐家大小姐 - 超越时空的思念(中文演唱).mp3(2016-50-17 10:50:35) 3 陈悦 - 乱红 - 钢琴版伴奏.mp3(2016-12-16 05:12:42) 4 Cache(2017-55-23 05:55:03) 5 Temp(2017-09-23 07:09:17) 6 Lyric(2017-09-23 07:09:18) E:KuGou目录下的文件: 1 Cache(4096 字节) 2 Lyric(4096 字节) 3 Temp(81920 字节) 4 唐家大小姐 - 超越时空的思念(中文演唱).mp3(2887753 字节) 5 陈悦 - 乱红 - 钢琴版伴奏.mp3(4965609 字节) 6 罗凯楠 - 咱们屯里的人.mp3(5480489 字节)
参考资料:
1.Java设计模式/耿祥义,张跃平著.——北京:清华大学出版社,2009.5