概念:实现一些操作时,整体步骤很固定,但是其中一小部分是变化的,这时候就可以使用模板方法模式,将容易变化的部分抽离出来,供子类实现。
特点:
1.封装不变部分,扩展可变部分
2.提取公共部分代码,便于维护
3.行为由父类控制,子类实现
缺点:
我们一般的设计习惯是抽象类负责声明最抽象、最一般的事物属性和方法,实现类来完成具体事物属性和方法,但是模板方法模式却反过来了,抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响了父类的结果,在复杂的项目会带来代码阅读的难度,会让新手产生不适感。
应用场景:servlet
1.多个子类有公有的方法,并且逻辑基本相同时。重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
2.重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数约束其行为。
注意:抽象模板中的基本方法尽量设计为protected类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为protected类型。实现类若非必要,尽量不要扩大父类中的访问权限。为了防止恶意操作,模板方法一般设置为final。
代码:
public abstract class HummerModel {
/*
* 首先,这个模型要能够被发动起来,别管是手摇发动,还是电力发动,反正
* 是要能够发动起来,那这个实现要在实现类里了
*/
public abstract void start();
//能发动,还要能停下来,那才是真本事
public abstract void stop();
//喇叭会出声音,是滴滴叫,还是哔哔叫
public abstract void alarm();
//引擎会轰隆隆地响,不响那是假的
public abstract void engineBoom();
//那模型应该会跑吧,别管是人推的,还是电力驱动的,总之要会跑
//开动起来
final public void run(){
//先发动汽车
this.start();
//引擎开始轰鸣
this.engineBoom();
//然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭
this.alarm();
//到达目的地就停车
this.stop();
}
}
public class HummerH1Model extends HummerModel {
//H2型号的悍马车鸣笛
public void alarm() {
System.out.println("悍马H1鸣笛...");
}
//引擎轰鸣声
public void engineBoom() {
System.out.println("悍马H1引擎声音是这样在...");
}
//汽车发动
public void start() {
System.out.println("悍马H1发动...");
}
//停车
public void stop() {
System.out.println("悍马H1停车...");
}
}
public class HummerH2Model extends HummerModel {
//H2型号的悍马车鸣笛
public void alarm() {
System.out.println("悍马H2鸣笛...");
}
//引擎轰鸣声
public void engineBoom() {
System.out.println("悍马H2引擎声音是这样在...");
}
//汽车发动
public void start() {
System.out.println("悍马H2发动...");
}
//停车
public void stop() {
System.out.println("悍马H2停车...");
}
}
public class Client {
public static void main(String[] args) {
HummerH1Model h1Model = new HummerH1Model();
HummerH2Model h2Model = new HummerH2Model();
h1Model.run();
h2Model.run();
}
}
结果:
悍马H1发动...
悍马H1引擎声音是这样在...
悍马H1鸣笛...
悍马H1停车...
悍马H2发动...
悍马H2引擎声音是这样在...
悍马H2鸣笛...
悍马H2停车...
通过上述代码,可以看出,我们首先定义一个抽象的模板方法类,然后将不变的方法实现了也就是run()方法,将容易变化的方法定义成抽象方法,然后由子类去实现这些易变的操作,就是其他常规的抽象方法如start()方法等都在具体的子类实现,子类的实现不同,会导致父类的执行结果不同。
扩展:
如果此时来了一个新需求,模型1的喇叭我要手动控制,模型2的喇叭我不要了,那么我们可以修改代码:
public abstract class HummerModel {
/*
* 首先,这个模型要能够被发动起来,别管是手摇发动,还是电力发动,反正
* 是要能够发动起来,那这个实现要在实现类里了
*/
protected abstract void start();
//能发动,那还要能停下来,那才是真本事
protected abstract void stop();
//喇叭会出声音,是滴滴叫,还是哔哔叫
protected abstract void alarm();
//引擎会轰隆隆的响,不响那是假的
protected abstract void engineBoom();
//那模型应该会跑吧,别管是人推的,还是电力驱动,总之要会跑
final public void run() {
//先发动汽车
this.start();
//引擎开始轰鸣
this.engineBoom();
//要让它叫的就是就叫,喇嘛不想让它响就不响
if(this.isAlarm()){
this.alarm();
}
//到达目的地就停车
this.stop();
}
//钩子方法,默认喇叭是会响的
protected boolean isAlarm(){
return true;
}
}
public class HummerH1Model extends HummerModel {
private boolean alarmFlag = true; //要相响喇叭
//H2型号的悍马车鸣笛
public void alarm() {
System.out.println("悍马H1鸣笛...");
}
//引擎轰鸣声
public void engineBoom() {
System.out.println("悍马H1引擎声音是这样在...");
}
//汽车发动
public void start() {
System.out.println("悍马H1发动...");
}
//停车
public void stop() {
System.out.println("悍马H1停车...");
}
protected boolean isAlarm(){
return this.alarmFlag;
}
//要不要响喇叭,由客户决定
public void setAlarm(boolean isAlarm){
this.alarmFlag = isAlarm;
}
}
public class HummerH2Model extends HummerModel {
//H2型号的悍马车鸣笛
public void alarm() {
System.out.println("悍马H2鸣笛...");
}
//引擎轰鸣声
public void engineBoom() {
System.out.println("悍马H2引擎声音是这样在...");
}
//汽车发动
public void start() {
System.out.println("悍马H2发动...");
}
//停车
public void stop() {
System.out.println("悍马H2停车...");
}
//模型2关闭喇叭功能
protected boolean isAlarm(){
return false;
}
}
public class Client {
public static void main(String[] args) throws IOException {
System.out.println("-------H1型号悍马--------");
System.out.println("H1型号的悍马是否需要喇叭声响?0-不需要1-需要");
String type=(new BufferedReader(new InputStreamReader(System.in))).readLine();
HummerH1Model h1=new HummerH1Model();
if(type.equals("0")){
h1.setAlarm(false);
}
h1.run();
System.out.println("-------H2型号悍马--------");
HummerH2Model h2=new HummerH2Model();
h2.run();
}
}
结果1:
-------H1型号悍马--------
H1型号的悍马是否需要喇叭声响?0-不需要1-需要
0
悍马H1发动...
悍马H1引擎声音是这样在...
悍马H1停车...
-------H2型号悍马--------
悍马H2发动...
悍马H2引擎声音是这样在...
悍马H2停车...
--------------------------------------------------------------------------------------------------------------------------------
结果2:
H1型号的悍马是否需要喇叭声响?0-不需要1-需要
1
悍马H1发动...
悍马H1引擎声音是这样在...
悍马H1鸣笛...
悍马H1停车...
-------H2型号悍马--------
悍马H2发动...
悍马H2引擎声音是这样在...
悍马H2停车...
这样我们就解决了这个新需求,在抽象类中isAlarm的返回值就影响了模板方法的执行结果,该方法就叫做钩子方法。由子类的一个返回值决定了公共部分的执行结果,我们可以按照自己的需要去控制功能的关闭,有了钩子函数的模板方法才算是完美的模板方法。
-----------------------例子来源于《设计模式之禅》