目录
一、定义
模板方法模式,其定义为:定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
简单理解就是为其他子类提供一个算法框架,模板方法模式属于行为型模式,是借助于继承来实现的。
模板方法模式的通用类图如下所示:
角色分析:
- 抽象模板(Abstract Template)角色:定义了模板方法以及整个实现的大体步骤,具体的子类需要实现其中的抽象方法;(抽象基类定义算法框架);
- 模板子类: 实现了抽象模板类,并实现了其中的抽象方法;
- 客户端:调用模板子类中的模板方法即可完成很多一致流程的功能;
二、模板方法模式
下面我们通过汽车模型的例子说明模板方法模式如何应用。
汽车模型一般包含以下一些方法:
- 启动汽车;
- 停止汽车;
- 打喇叭;
- 汽车引擎发出声音;
- 汽车跑出来;
我们先按照一般的经验设计类图,如下:
我们先写一个抽象类,然后两个不同型号的汽车的模型实现类,通过简单的继承实现业务要求。
具体代码如下:
抽象汽车模型类:
/**
* 抽象汽车模型类
*/
public abstract class CarModel {
/**
* 启动汽车
*/
public abstract void start();
/**
* 停止汽车
*/
public abstract void stop();
/**
* 打喇叭
*/
public abstract void alarm();
/**
* 汽车引擎发出声音
*/
public abstract void engineBoom();
/**
* 汽车跑出来
*/
public abstract void run();
}
在抽象类中,我们定义了汽车模型都必须具有的特质:能够发动、停止、喇叭会响、引擎发出声音、而且还可以停止。
具体汽车模型类:
/**
* 奔驰汽车模型
*/
public class BenzCarModel extends CarModel {
@Override
public void start() {
System.out.println("奔驰汽车启动...");
}
@Override
public void stop() {
System.out.println("奔驰汽车停车...");
}
@Override
public void alarm() {
System.out.println("奔驰汽车打喇叭...");
}
@Override
public void engineBoom() {
System.out.println("奔驰汽车引擎发出声音...");
}
@Override
public void run() {
//先发动汽车
this.start();
//引擎发出声音
this.engineBoom();
//打喇叭
this.alarm();
//停止汽车
this.stop();
}
}
/**
* 宝马汽车模型
*/
public class BmwCarModel extends CarModel {
@Override
public void start() {
System.out.println("宝马汽车启动...");
}
@Override
public void stop() {
System.out.println("宝马汽车停车...");
}
@Override
public void alarm() {
System.out.println("宝马汽车打喇叭...");
}
@Override
public void engineBoom() {
System.out.println("宝马汽车引擎发出声音...");
}
@Override
public void run() {
//先发动汽车
this.start();
//引擎发出声音
this.engineBoom();
//打喇叭
this.alarm();
//停止汽车
this.stop();
}
}
我们注意一下run()方法,这是一个汇总的方法,程序写到这里,我们其实也可以发现问题所在了,两个具体模型的run()方法是完全一样的,那这个run()方法不应该在具体实现类中,而应该出现抽象类中,抽象是所有子类的共性封装。
下面我们修改一下,修改后的类图如下:
注意,抽象类中的run()方法由抽象方法变更为实现方法,其代码如下:
/**
* 抽象汽车模型类
*/
public abstract class CarModel {
/**
* 启动汽车
*/
protected abstract void start();
/**
* 停止汽车
*/
protected abstract void stop();
/**
* 打喇叭
*/
protected abstract void alarm();
/**
* 汽车引擎发出声音
*/
protected abstract void engineBoom();
/**
* 汽车跑出来
*/
public final void run() {
//先发动汽车
this.start();
//引擎发出声音
this.engineBoom();
//打喇叭
this.alarm();
//停止汽车
this.stop();
}
}
然后两个具体的汽车模型类就不需要实现run()方法了,只需要把原先的run()方法删除即可。
/**
* 奔驰汽车模型
*/
public class BenzCarModel extends CarModel {
@Override
protected void start() {
System.out.println("奔驰汽车启动...");
}
@Override
protected void stop() {
System.out.println("奔驰汽车停车...");
}
@Override
protected void alarm() {
System.out.println("奔驰汽车打喇叭...");
}
@Override
protected void engineBoom() {
System.out.println("奔驰汽车引擎发出声音...");
}
}
/**
* 宝马汽车模型
*/
public class BmwCarModel extends CarModel {
@Override
protected void start() {
System.out.println("宝马汽车启动...");
}
@Override
protected void stop() {
System.out.println("宝马汽车停车...");
}
@Override
protected void alarm() {
System.out.println("宝马汽车打喇叭...");
}
@Override
protected void engineBoom() {
System.out.println("宝马汽车引擎发出声音...");
}
}
场景类:
public class Client {
public static void main(String[] args) {
CarModel carModel = new BenzCarModel();
carModel.run();
}
}
运行结果如下所示:
奔驰汽车启动...
奔驰汽车引擎发出声音...
奔驰汽车打喇叭...
奔驰汽车停车...
注意点:
1、为了防止恶意的操作,一般模板方法都加上final关键字,不允许被重写;
2、抽象模板这种总的基本方法尽量设计成protected类型,符合迪米特法则,不需要暴露的属性和方法尽量不要设置为protected类型。实现类若非必要,尽量不要扩大父类中的访问权限。
三、模板方法模式的扩展
前面我们已经实现了模板方法模式,但是假如哪一天,模型需要发生调整,就是汽车模型的喇叭我想它响就响,想一下,我们该如何控制呢?
稍稍思考一下,解决方法有了,先画出类图:
类图改动比较小,我们在抽象类中增加一个方法:isAlarm(),确定每个模型是否需要声音,由各个实现类重写该方法。代码如下:
/**
* 抽象汽车模型类
*/
public abstract class CarModel {
/**
* 启动汽车
*/
protected abstract void start();
/**
* 停止汽车
*/
protected abstract void stop();
/**
* 打喇叭
*/
protected abstract void alarm();
/**
* 汽车引擎发出声音
*/
protected abstract void engineBoom();
/**
* 汽车跑出来
*/
public final void run() {
//先发动汽车
this.start();
//引擎发出声音
this.engineBoom();
//使用钩子方法控制喇叭是否响起来
if (isAlarm()) {
//打喇叭
this.alarm();
}
//停止汽车
this.stop();
}
/**
* 钩子方法,默认喇叭会响
* @return
*/
protected boolean isAlarm() {
return true;
}
}
在抽象类中,isAlarm()方法是一个实现方法,其作用是模板方法根据其返回值决定是否要喇叭,子类可以重写该返回值。如下:奔驰汽车模型我不想让它喇叭想起来
/**
* 奔驰汽车模型
*/
public class BenzCarModel extends CarModel {
@Override
protected void start() {
System.out.println("奔驰汽车启动...");
}
@Override
protected void stop() {
System.out.println("奔驰汽车停车...");
}
@Override
protected void alarm() {
System.out.println("奔驰汽车打喇叭...");
}
@Override
protected void engineBoom() {
System.out.println("奔驰汽车引擎发出声音...");
}
@Override
protected boolean isAlarm() {
//奔驰车不想让喇叭响起来
return false;
}
}
扩展后的场景类:
public class Client {
public static void main(String[] args) {
CarModel carModel = new BenzCarModel();
carModel.run();
System.out.println("===========");
CarModel carModel1 = new BmwCarModel();
carModel1.run();
}
}
运行结果如下:
奔驰汽车启动...
奔驰汽车引擎发出声音...
奔驰汽车停车...
===========
宝马汽车启动...
宝马汽车引擎发出声音...
宝马汽车打喇叭...
宝马汽车停车...
我们可以看到,奔驰汽车喇叭是没有响起来的。其实isAlarm()叫做钩子方法,有了钩子方法模板方法模式才算完美。
四、应用分析
在Spring IOC容器初始化的源码中就使用到了模板方法模式,下面我们大概看一下其中关键部分的源码:
首先看一下ConfigurableApplicationContext类,它是一个接口,其中的refresh()其实就是模板方法。
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
//模板方法
void refresh() throws BeansException, IllegalStateException;
}
接着我们看一下实现 ConfigurableApplicationContext接口的其中一个类AbstractApplicationContext类,它实现了抽象模板类中的模板方法refresh()方法。其中的this.postProcessBeanFactory(beanFactory);和this.onRefresh();两个方法是钩子方法,都只是默认空实现,具体交由AbstractApplicationContext的子类去覆盖。下面是AbstractApplicationContext类相关源码:
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
//模板方法
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
//obtainFreshBeanFactory()中调用该类中的两个抽象方法refreshBeanFactory()和getBeanFactory()
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
//钩子方法
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
//钩子方法
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
this.refreshBeanFactory();
return this.getBeanFactory();
}
/**
* 下面两个抽象方法将会由AbstractApplicationContext的子类进行实现
*/
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
//钩子方法,让子类去覆盖它
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
//钩子方法,让子类去覆盖它
protected void onRefresh() throws BeansException {
}
//...省略
}
五、总结
模板方法模式的优点:
- 封装不变的部分,扩展可变的部分
把认为是不变的部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展。
- 提取公共代码,便于维护
- 模板方法模板封装了整个流程的骨架,使得所有的子类都调用模板方法完成功能,模板方法模式将一些步骤延迟到子类中去实现;
- 可以使用钩子函数,抽象模板类提供一个空的或者默认的实现。子类重写该方法,可以自行决定是否挂钩以及如何挂钩;
模板方法模式的缺点:
- 当类的功能越来越多,抽象类的管理和扩展就会变得比较困难;
模板方法模式的使用场景:
- 需要将各个子类共同的代码部分抽取出来到公共父类中时,可以考虑实现模板方法模式,约定好整个骨架;
- 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现;
- 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类,然后通过钩子函数约束其行为;
参考:设计模式之禅