一、定义
定义一个操作中的骨架,而将一些步骤延迟到子类中。模板方法使子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
二、概述
类中的方法用以表明该类的实例所具有的行为,一个类可以有许多方法,而且类中的实例方法也可以调用该类中的其他方法。在开发过程中,可能需要将类的许多方法集成到一个实例方法中,以此来表示一个算法的骨架。
比如车站安排上车都需要进行安检、验票、选择候车室等步骤。因此就可以抽象一个Station类包含safeExamine()、validateTicket()和choiceCarriage()三个方法表示乘车的步骤的抽象方法,而且该抽象类中还特别包含有ridingStep()方法,该方法顺序调用safeExamine()、validateTicket()和choiceCarriage(),因此Station的子类可以直接继承ridingStep()方法,但需要重写三个抽象方法。如Station的子类,火车站通过重写三个抽象方法可以给出自己的乘车方案。当实例化火车站时就可以调用ridingStep()方法来展示乘车的步骤。
模板模式包含两个角色:
1、抽象模板(Abstract Template):是一个抽象类,类中定义了若干表示算法步骤的抽象方法以及调用这些算法步骤的非抽象方法。
2、具体模板(Concrete Template):是抽象模板的子类。实现抽象模板的抽象方法。
三、举例:
显示某个目录下的全部文件名字,可以按文件的大小顺序或按最后修改时间顺序来显示所有的文件名:
<1> 抽象模板(Abstract Template)
是一个抽象类
public abstract 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.jave
public class ConcreteTemplate1 implements ConcreteTemplate{
ConcreteTemplate1(File dir){
super(dir);
}
//最后修改时间排序
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;
}
}
}
}
//打印
public void printFiles(){
for( int i = 0; i < allFiles.length; i++){
long time = allFiles[i].lastModified();
Date date = new Date(time);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String str = format.format(date);
String name = allFiles[i].getName();
int k = i + 1;
System.out.println(k + "" +name + "(" + str + ")");
}
}
}
ConcreteTemplate2.jave
public class ConcreteTemplate2 implements ConcreteTemplate{
ConcreteTemplate2(File dir){
super(dir);
}
//文件大小排序
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[j];
allFiles[j] = allFiles[i];
allFiles[i] = file;
}
}
}
};
//打印
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> 测试
在main函数中进行测试
public class Application{
public static void main(String args[]){
File dir = new File("d://");
AbstractTemplate template = new ConcreteTemplate1(dir);
System.out.println(dir.getPath() + "目录下的文件:");
template.showFileName();
template = new ConcreteTemplate2(dir);
System.out.println(dir.getPath() + "目录下的文件:");
template.showFileName();
}
}
四、钩子方法
钩子方法是抽象模板中定义的具体方法,但给出了空实现或默认的实现,并允许子类重写这个具体的方法。钩子方法的作用是对模板方法中的某些步骤进行“挂钩”,即允许具体模板对算法的不同点进行“挂钩”,已确定什么条件下执行模板方法中的哪些算法步骤。
还是以上面的例子介绍一下钩子的用法:
<1> 在AbstractTemplate抽象模板中定义一个钩子方法
public abstract AbstractTemplate{
File[] allFiles;
File dir;
AbstractTemplate(File dir){
this.dir = dir;
}
public final void showFileName(){
allFiles = dir.listFiles();
sort();
printFiles();
}
//定义钩子方法
public boolean isPrint(){
return true;
}
//排序
public abstract void sort();
//对printFiles方法进行挂钩
if(isPrint()){
public abstract void printFiles();
}
}
<2> 具体模板
如果具体模板不打算输出文件的名字,就可以重写钩子方法,将返回结果更改为false。
ConcreteTemplate1.jave
public class ConcreteTemplate1 implements ConcreteTemplate{
ConcreteTemplate1(File dir){
super(dir);
}
//最后修改时间排序
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;
}
}
}
};
//重写钩子方法
public boolean isPrint(){
return false;
}
}
<3> 测试
在main函数中进行测试
public class Application{
public static void main(String args[]){
File dir = new File("d://");
AbstractTemplate template = new ConcreteTemplate1(dir);
System.out.println(dir.getPath() + "目录下的文件:");
template.showFileName();
}
}
五、优点
<1> 提高代码复用性:将相同部分的代码放在抽象的父类中
<2> 提高了拓展性: 将不同的代码放入不同的子类中,通过对子类的扩展增加新的行为
<3> 实现了反向控制: 具体子类的基本方法将覆盖父类中定义的基本方法,子类的钩子方法也将覆盖父类的钩子方法,从而可以通过在子类中实现的钩子方法对父类方法的执行进行约束,实现子类对父类行为的反向控制。
六、应用场景
<1> 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
<2> 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
<3> 控制子类的扩展。