概述
通俗点讲,模板方法模式就是在一个模板方法中,按既定顺序执行一组步骤(方法),并将部分步骤(抽象方法)延迟到子类实现。
该模式把抽象父类设计为一个模板,所有子类都必须按照模板设定的骨架去按顺序执行方法。抽象类负责实现骨架中的公用方法(通用),子类负责实现必须改变的抽象方法(细节)。
作用
所有继承了抽象类的子类都会按照父类骨架编码。通用方法由父类实现,减少代码冗余。
应用场景
把定制系统项目开发很粗暴地看为一下流程:
需求定制 —— 系统框架设计 —— 业务结构设计 —— 编码 ——测试。
场景分析:不知道是不是所有定制开发都是这样的流程的,反正我这的新项目就是在从旧项目copy一份,然后按照新的业务需求来改!(ps:业务需求大致相近)。这样可以减少大量的开发成本
现在把项目看作一个抽象类。各个环节(步骤)是一个个的方法。现在需要分析哪些步骤是所有项目都相同的,把相同实现的抽取成公用方法。不同的为抽象方法,由各个具体项目子类实现。
需求定制(requireDesign):变动。不解释。
系统框架(systemFrame):不变动。所有项目统一框架很省钱!!!
业务结构设计(vocationDesign):可能变动。有些项目变动,有些直接搬。
编码(code):变动。每个项目都必定会改业务逻辑。
测试(test):不变动。就几个内部人员用的系统,大概只有功能测试。
场景分析
systemFrame()、test()所有项目是通用的,由父类统一实现
requireDesign()、code()所有项目都要重新实现,所以交由子类实现。
说到这里可能会疑惑:vocationDesign()方法应该怎样处理? 这里,交由父类实现一般的通用方法,原因是后面可以由“钩子”判断是否需要调用,钩子 放在下面作解释。
类图
代码
//项目基础抽象类
public abstract class Project {
//由各个子类项目负责实现
abstract public void requireDesign();
abstract public void code();
final public void systemFrame() {
System.out.println("通用系统框架...");
}
final public void vocationDesign() {
System.out.println("通用业务设计...");
}
final public void test() {
System.out.println("通用测试方式...");
}
//定义一个模板方法,限定所有步骤的顺序
final public void execute() {
this.requireDesign();
this.systemFrame();
this.vocationDesign();
this.code();
this.test();
}
}
//定制化的N个项目之一
public class AppleProject extends Project {
@Override
public void requireDesign() {
System.out.println("我是Apple项目,我有不同的业务需求");
}
@Override
public void code() {
System.out.println("每次code都不一样,我是apple");
}
}
//Main方法
public static void main(String[] args) {
Project project = new AppleProject();
project.execute();
}
输出
钩子
不知道大家看到这个模式时,有没联想到Java实例化对象的基础方法执行顺序。
创建:执行构造方法;
回收:执行finalize方法;
还有刚学Java Web时,继承了HttpServlet后,除了service()编码外,还可以在初始化init(),销毁destroy()中编码。
在开始分析时,vocationDesign方法是应该要按照实际情况灵活变化的。
上面代码明显不符合要求,模板应该设计得更灵活一些。
这时就应该使用“钩子”。
引用《Head First》原话:
钩子是一种被声明在抽象类中的方法,但只有空得或者默认的实现。
钩子的存在可以让子类有能力对算法的不同点进行挂钩。
要不要挂钩,由子类自行决定。
钩子有下面几种用法:
1.空方法: 在模板步骤的前后加入空方法,由子类自由选择是否需要覆写。
2.用作条件判断:在父类的步骤上加入if条件判断(钩子返回true/false),子类可以选择覆写钩子,跳过某些“不必要”的步骤。
旧模板与新模板对比:
修改后的代码
public abstract class Project {
//由各个子类项目负责实现
abstract public void requireDesign();
abstract public void code();
//钩子
public void init() {}
final public void systemFrame() {
System.out.println("通用系统框架...");
}
final public void vocationDesign() {
System.out.println("通用业务设计...");
}
final public void test() {
System.out.println("通用测试方式...");
}
//空钩子
public void destroy() {}
//用作判断的钩子,默认返回true
public boolean isChange() {
return true;
}
//空钩子
public void customDesgin() {}
//定义一个模板方法,限定所有步骤的顺序
final public void execute() {
this.init();//空钩子
this.requireDesign();
this.systemFrame();
if(isChange()) {
this.vocationDesign(); //默认使用通用方法
}else {
this.customDesgin(); //由子类覆写
}
this.code();
this.test();
this.destroy(); //空钩子
}
}
public class AppleProject extends Project {
@Override
public void init() {
System.out.println("这是一个可有可无的初始化方法init()");
}
@Override
public boolean isChange() {
System.out.println("在这里判断执行通用方法还是自定义方法!!!");
return false;
}
@Override
public void requireDesign() {
System.out.println("我是Apple需求人员,我负责写不同的业务需求");
}
@Override
public void code() {
System.out.println("每次code都不一样,我是apple");
}
@Override
public void customDesgin() {
System.out.println("放弃原业务设计,自定义新的");
}
@Override
public void destroy() {
System.out.println("这是一个可有可无的销毁方法destroy()");
}
}
输出结果
使用钩子的好处很明显
就是可以使模板在不影响主流程的情况下,尽量把系统设计得更加灵活。
由子类自己按照实际需求来选择要不要扩展部分功能。
总结
1.最基础的系统。模板类实现了系统的基本功能,属于“(框架)”,引用一句古语就是“麻雀虽小,五脏俱全”。
2.代码复用。子类可以使用模板的基础功能,也可以用钩子扩展部分功能。
3.保证系统稳定。所有子类都必须按照模板的骨架去设计。
最后,引用《Head First》的要点
1.“模板方法”定义了算法的步骤,把这些步骤的实际延迟到子类。
2.模板方法模式为我们提供了一种代码复用的重要技巧。
3.模板方法的抽象类可以定义具体方法、抽象方法和钩子。
4.抽象方法由子类实现。
5.钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以选择要不要去覆盖它。
6.为了防止子类改变模板方法中的算法,可以将模板方法声明为final。
7.策略模式和模板方法模式都封装算法,一个用组合,一个用继承。
8.工厂方法是模板方法的一种特殊版本。