模板设计模式
1)基本定义
定义:在一个抽象类中公开定义执行它的方法的方式/模板,子类可以重写方法的实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型设计模式。
意图:定义一个操作种的算法骨架,将一些具体步骤的实现延迟到子类中
解决问题:一些方法通用,却在每一个子类重写这些方法
关键代码:算法骨架在抽象类实现(有时使用final修饰以禁止子类重写以防止恶意修改),一些具体步骤在子类类实现
优点:封装不变部分、扩展可变部分;提取公共代码、便于维护;行为由父类统一控制,子类实现
缺点:每一个不同的实现都需要一个子类实现,导致类数量增加,系统变得庞大
场景:有多个子类共有的方法,逻辑相同;重要的、复杂的方法,可以考虑作为模板方法
设计模式类图
2)例子
以悍马车模型为例,类图如下:
实现代码如下:
/** 悍马车模型 **/
public abstract class HummerModel {
/** 启动 **/
public abstract void start();
/** 停止 **/
public abstract void stop();
/** 喇叭鸣叫 **/
public abstract void alarm();
/** 引擎轰鸣 **/
public abstract void engineBoom();
/** 汽车跑起来 **/
public abstract void run();
}
public class HM1 extends HummerModel{
@Override
public void start() {
System.out.println("HM1 start");
}
@Override
public void stop() {
System.out.println("HM1 stop");
}
@Override
public void alarm() {
System.out.println("HM1 alarm");
}
@Override
public void engineBoom() {
System.out.println("HM1 engineBoom");
}
@Override
public void run() {
System.out.println("HM1 run");
alarm();
start();
engineBoom();
stop();
}
}
public class HM2 extends HummerModel{
@Override
public void start() {
System.out.println("HM2 start");
}
@Override
public void stop() {
System.out.println("HM2 stop");
}
@Override
public void alarm() {
System.out.println("HM2 alarm");
}
@Override
public void engineBoom() {
System.out.println("HM2 engineBoom");
}
@Override
public void run() {
System.out.println("HM2 run");
alarm();
start();
engineBoom();
stop();
}
}
观察上面代码,发现明显的问题,让汽车跑起来的方法run()所有的车型应该是一样的,应该在抽象类中,修改类图如下:
新的实现代码如下:
/** 悍马车模型 **/
public abstract class HummerModel {
/** 启动 **/
public abstract void start();
/** 停止 **/
public abstract void stop();
/** 喇叭鸣叫 **/
public abstract void alarm();
/** 引擎轰鸣 **/
public abstract void engineBoom();
/** 汽车跑起来 **/
public final void run() {
System.out.println("HM2 run");
alarm();
start();
engineBoom();
stop();
}
}
public class HM1 extends HummerModel{
@Override
public void start() {
System.out.println("HM1 start");
}
@Override
public void stop() {
System.out.println("HM1 stop");
}
@Override
public void alarm() {
System.out.println("HM1 alarm");
}
@Override
public void engineBoom() {
System.out.println("HM1 engineBoom");
}
}
public class HM2 extends HummerModel{
@Override
public void start() {
System.out.println("HM2 start");
}
@Override
public void stop() {
System.out.println("HM2 stop");
}
@Override
public void alarm() {
System.out.println("HM2 alarm");
}
@Override
public void engineBoom() {
System.out.println("HM2 engineBoom");
}
}
调用如下:
public class HMTest {
public static void main(String[] args) {
HummerModel h1 = new HM1();
h1.run();
HummerModel h2 = new HM2();
h2.run();
}
}
仔细思考上面的实现,会发现以下问题:
1)客户要关心模型的启动,停止,鸣笛,引擎声音吗?他只要在run的过程中,听到或看到就成了,暴露那么多方法干嘛呢?把抽象类上的四个方法设置为protected访问权限
2)run方法设置成final,子类不可修改,但不是所有的车型都是想鸣喇叭,且客户更想在想要鸣时就鸣,由自己决定,这就可以使用钩子方法。
修改类图如下:
实现代码如下:
/** 悍马车模型 **/
public abstract class HummerModel {
/** 启动 **/
protected abstract void start();
/** 停止 **/
protected abstract void stop();
/** 喇叭鸣叫 **/
protected abstract void alarm();
/** 引擎轰鸣 **/
protected abstract void engineBoom();
/** 汽车跑起来 **/
public final void run() {
System.out.println("HM2 run");
if(isAlarm())
alarm();
start();
engineBoom();
stop();
}
/** 钩子方法,是否鸣笛 ,默认实现**/
protected boolean isAlarm() {
return true;
}
}
public class HM1 extends HummerModel{
@Override
protected void start() {
System.out.println("HM1 start");
}
@Override
protected void stop() {
System.out.println("HM1 stop");
}
@Override
protected void alarm() {
System.out.println("HM1 alarm");
}
@Override
protected void engineBoom() {
System.out.println("HM1 engineBoom");
}
private boolean isAlarm = false;
/** 子类自己提供设置是否鸣笛的接口 **/
public void setIsAlarm(boolean isAlarm) {
this.isAlarm = isAlarm;
}
/** 重写钩子方法,是否鸣笛 **/
protected boolean isAlarm() {
return isAlarm;
}
}
public class HM2 extends HummerModel{
@Override
protected void start() {
System.out.println("HM2 start");
}
@Override
protected void stop() {
System.out.println("HM2 stop");
}
@Override
protected void alarm() {
System.out.println("HM2 alarm");
}
@Override
protected void engineBoom() {
System.out.println("HM2 engineBoom");
}
/** 不提供钩子方法重写,不提供设置是否鸣笛的接口 **/
}
调用如下:
public class HMTest {
public static void main(String[] args) {
HM1 h1 = new HM1();
h1.setIsAlarm(true);//这里setIsAlarm方法不在父类中,只可以使用子类对象调用
h1.run();
HummerModel h2 = new HM2();
h2.run();
}
}
注意:callback和“钩子”是两个完全不同的概念,callback是指:由我们自己实现的,但是预留给系统调用的函数,我们自己是没有机会调用的,但是我们知道系统在什么情况下会调用该方法。而“钩子”是指:声明在抽象类中的方法,只有空的或默认的实现,通常应用在模板设计模式中,让子类可以对算法的不同点进行选择或挂钩,要不要挂钩由子类决定。
由例子可以看出,模板设计核心原理是通过继承、重写、实现来实现父类调用子类方法。