模板模式
什么是模板模式
模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。
为什么要用模板模式
模板模式有两大作用:复用和扩展。其中,复用指的是,所有的子类可以复用父类中提供的模板方法的代码。扩展指的是,框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。
我们在【设计模式-观察者模式】一文中提到过,可以通过实现ApplicationListener来完成对Spring全局事件的监听,代码如下:
public class ZfDtpRunInfoCollectEventLocalFileListener implements ApplicationListener<ZfDtpRunInfoCollectEvent> {
@Override
public final void onApplicationEvent(ZfDtpRunInfoCollectEvent event) {
ZfDtpCollectTimeInfo zfDtpCollectTimeInfo = (ZfDtpCollectTimeInfo) event.getSource();
// 获取采集信息
String collectDay = zfDtpCollectTimeInfo.getCollectDay();
// 保存线程池状态
String localFilePath = CommonUtil.getFilePath(this.getLocalFilePathPrefix(), collectDay);
ArrayList<String> appendLines = new ArrayList<>();
appendLines.add(JSONUtil.toJsonStr(zfDtpCollectTimeInfo));
FileUtil.appendLines(appendLines, localFilePath, StandardCharsets.UTF_8);
}
}
可以看到上面的代码中,我们实现了ApplicationListener接口,而这个接口中的onApplicationEvent方法入参为你自己定义的一个事件,需要通过
ZfDtpCollectTimeInfo zfDtpCollectTimeInfo = (ZfDtpCollectTimeInfo) event.getSource();
来获取具体的信息,那么这一段代码实际上是每一个监听器都应该实现的,此时我们就可以通过模板模式来完成代码复用,代码示例如下:
// 抽象模板类
public abstract class ZfDtpAbstractRunInfoCollectEventListener implements ApplicationListener<ZfDtpRunInfoCollectEvent> {
@Override
public final void onApplicationEvent(ZfDtpRunInfoCollectEvent event) {
ZfDtpCollectTimeInfo zfDtpCollectTimeInfo = (ZfDtpCollectTimeInfo) event.getSource();
doCollect(zfDtpCollectTimeInfo);
}
protected abstract void doCollect(ZfDtpCollectTimeInfo zfDtpCollectTimeInfo);
}
// 具体实现类
public class ZfDtpRunInfoCollectEventLocalFileListener extends ZfDtpAbstractRunInfoCollectEventListener {
private ZfDtpProperties zfDtpProperties;
private String localFilePathPrefix;
@Override
public void doCollect(ZfDtpCollectTimeInfo zfDtpCollectTimeInfo) {
// 获取采集信息
String collectDay = zfDtpCollectTimeInfo.getCollectDay();
// 保存线程池状态
String localFilePath = CommonUtil.getFilePath(this.getLocalFilePathPrefix(), collectDay);
ArrayList<String> appendLines = new ArrayList<>();
appendLines.add(JSONUtil.toJsonStr(zfDtpCollectTimeInfo));
FileUtil.appendLines(appendLines, localFilePath, StandardCharsets.UTF_8);
}
}
可以看到,我们优化后的代码是需要集成抽象类ZfDtpAbstractRunInfoCollectEventListener并重写doCollect方法保存我们采集的线程池信息即可。
上面我们优化的代码其实同时兼备了模板模式的两个优点,一是代码复用,复用了将事件信息转换成我们需要的对象这一部分代码;二是易于拓展,只需要实现我们定义的抽象类,重写doCollect方法就能完成信息的保存。不需要考虑如何获取具体的事件与详细信息。
如何使用模板模式
我们上面的代码中其实是一个非常简单的模板模式,定义一个抽象父类,将不需要子类复写的方法加上final,将需要子类实现的方法定义为抽象方法,我们用代码示例来看下:
// 抽象父类
public abstract class AbstractTemplate {
public final void templateMethod(){
step1Method();
step2Method();
step3Method();
}
abstract void step1Method();
abstract void step2Method();
abstract void step3Method();
}
// 具象子类
public class ChildClass extends AbstractTemplate{
@Override
void step1Method() {
// 具体实现步骤一
}
@Override
void step2Method() {
// 具体实现步骤二
}
@Override
void step3Method() {
// 具体实现步骤三
}
}
// 使用
public class TestTemplate {
public static void main(String[] args) {
AbstractTemplate abstractTemplate = new ChildClass();
abstractTemplate.templateMethod();
}
}
总结
模板模式的用法和应用场景比较简单,我们在项目中应该比较常用,需要注意的是模板方法尽量使用final修饰避免子类重写导致模板失效。