模板方法模式
业务需求:汽车厂造悍马
假设我们是一个汽车公司,现在有客户来了,要求我们造悍马! 既然上级下来命令那就造呗,但是造悍马你得告诉我们汽车有什么功能啊,客户说了:“能启动车,能停止车,能响,能跑。”好,功能出来了,开始造汽车了。类图如下:
package com.chb.l.TemplateDesignPattern;
/**
一辆车的功能
start
engineboom
run
alarm
stop
*/
public abstract class HummerModel {
/**
* 启动
*/
public abstract void start();
/**
* 引擎轰鸣
*/
public abstract void engineBoom();
/**
* 行驶
*/
public abstract void run();
/**
* 遇到情况鸣笛
*/
public abstract void alarm();
/**
* 停车
*/
public abstract void stop();
}
二、高级定制嘛,每种型号的肯定有所不同
2.1、H320悍马
package com.chb.l.TemplateDesignPattern;
/**
230系列车型
*/
public class Hummer320 extends HummerModel{
@Override
public void start() {
System.out.print("H320发动。。。");
}
@Override
public void engineBoom() {
System.out.print("H320引擎发出轰鸣声 轰轰轰。。。");
}
@Override
public void run() {
this.start();
this.engineBoom();
System.out.print("跑起来了,");
this.alarm();
this.stop();
}
@Override
public void alarm() {
System.out.print("H320:妈的,嘀嘀嘀。。。好狗不挡道,快让开");
}
@Override
public void stop() {
System.out.println("H320:前方遇到紧急情况,滋滋滋...,总算停下来了。");
}
}
2.2、H523悍马
package com.chb.l.TemplateDesignPattern;
/**
230系列车型
*/
public class Hummer523 extends HummerModel{
@Override
public void start() {
System.out.print("H523发动。。。");
}
@Override
public void engineBoom() {
System.out.print("H523引擎发出轰鸣声 轰轰轰。。。");
}
@Override
public void run() {
this.start();
this.engineBoom();
System.out.print("跑起来了,");
this.alarm();
this.stop();
}
@Override
public void alarm() {
System.out.print("H523:妈的,嘀嘀嘀。。。好狗不挡道,快让开");
}
@Override
public void stop() {
System.out.println("H523:前方遇到紧急情况,滋滋滋...,总算停下来了。");
}
}
查看两种型号悍马,我们可以发现,两者具有一个共性方法run(), 那么这个共性方法应该出现在抽象类中,而不是实现类中,抽象是所有子类的共性封装
共性方法
@Override
public void run() {
this.start();
this.engineBoom();
System.out.print("跑起来了,");
this.alarm();
this.stop();
}
注意 在软件开发过程中,如果相同的一段代码复制过两次,就需要对设计产生怀疑, 架构师要明确地说明为什么相同的逻辑要出现两次或更多次,我们将共性方法抽取到抽象类中
package com.chb.l.TemplateDesignPattern;
/**
一辆车的功能
start
engineboom
run
alarm
stop
*/
public abstract class HummerModel {
/**
* 启动
*/
public abstract void start();
/**
* 引擎轰鸣
*/
public abstract void engineBoom();
/**
* 行驶,共性方法抽取到抽象类,子类不用重新实现。
*/
public void run() {
this.start();
this.engineBoom();
System.out.print("跑起来了,");
this.alarm();
this.stop();
}
/**
* 遇到情况鸣笛
*/
public abstract void alarm();
/**
* 停车
*/
public abstract void stop();
}
测试
public class Test {
public static void main(String[] args) {
HummerModel hm320 = new Hummer320();
hm320.run();
}
}
输出结果:
H320发动。。。H320引擎发出轰鸣声 轰轰轰。。。跑起来了,H320:妈的,嘀嘀嘀。。。好狗不挡道,快让开H320:前方遇到紧急情况,滋滋滋...,总算停下来了。
模板方法
这个共性方法run()就是模板方法,不管车子是什么型号, 一定是启动,引擎发动,可以跑了,遇到问题鸣笛,遇到紧急情况刹车。这个流程是不变的,即run()的实现不变,所以称为模板方法。
注意 为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。 抽象模板中的基本方法尽量设计为protected类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为protected类型。实现类若非必要,尽量不要扩大父类中的访问权限。 模板方法模式的使用场景● 多个子类有公有的方法,并且逻辑基本相同时。 ● 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个 子类实现。 ● 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通 过钩子函数(见“模板方法模式的扩展”)约束其行为。
三、模板方法模式的扩展目前为止,这两个模型都稳定地运行,突然有一天,领导急匆匆地找到了我:“看你怎么设计的,车子一启动,喇叭就狂响,吵死人了!客户提出H1型号的悍马喇叭想让它响就响,H2型号的喇叭不要有声音,赶快修改一下。”
怎么做呢整个流程不变, 只需改变可以控制鸣笛,添加一个控制因素来选择是否鸣笛 添加一个控制方法isAlarm()来控制是否鸣笛。
/**
* 钩子方法,默认是会鸣笛
* @return
*/
protected boolean isAlarm() {
return true;
}
修改模板方法
/**
* 行驶
*/
public final void run() {
this.start();
this.engineBoom();
System.out.print("跑起来了,");
if (isAlarm()) {//想叫它鸣笛就鸣笛
this.alarm();
}
this.stop();
}
抽象类
package com.chb.l.TemplateDesignPattern;
/**
一辆车的功能
start
engineboom
run
alarm
stop
*/
public abstract class HummerModel {
/**
* 启动
*/
protected abstract void start();
/**
* 引擎轰鸣
*/
protected abstract void engineBoom();
/**
* 行驶
*/
public final void run() {
this.start();
this.engineBoom();
System.out.print("跑起来了,");
if (isAlarm()) {//想叫它鸣笛就鸣笛
this.alarm();
}
this.stop();
}
/**
* 遇到情况鸣笛
*/
protected abstract void alarm();
/**
* 停车
*/
protected abstract void stop();
/**
* 钩子方法,默认是会鸣笛
* @return
*/
protected boolean isAlarm() {
return true;
}
}
H320重构
package com.chb.l.TemplateDesignPattern;
/**
230系列车型
*/
public class Hummer320 extends HummerModel{
//设置一个变量供客户控制钩子方法
private boolean alarmFlag = true;
/**
* 要不要响喇叭,是由客户来决定的
* @param alarmFlag
*/
public void setAlarmFlag(boolean alarmFlag) {
this.alarmFlag = alarmFlag;
}
protected boolean isAlarm() {//将外部的参数,通过钩子方法传入公共方法run(),决定是否鸣笛。
return this.alarmFlag;
};
@Override
protected void start() {
System.out.print("H320发动。。。");
}
@Override
protected void engineBoom() {
System.out.print("H320引擎发出轰鸣声 轰轰轰。。。");
}
@Override
protected void alarm() {
System.out.print("H320:妈的,嘀嘀嘀。。。好狗不挡道,快让开");
}
@Override
protected void stop() {
System.out.println("H320:前方遇到紧急情况,滋滋滋...,总算停下来了。");
}
}
H320型号的悍马是由客户自己控制是否要响喇叭,也就是说外界条件改变,影响到模板方法的执行。在我们的抽象类中isAlarm的返回值就是影响了模板方法的执行结果,该方法就叫做钩子方法(Hook Method)。有了钩子方法模板方法模式才算完美,大家 可以想想,由子类的一个方法返回值决定公共部分的执行结果,是不是很有吸引力呀!