模板模式
文章目录
- 模板模式
- 什么是模板模式
- 为什么要用模板模式
- 如何使用模板模式
- 总结
什么是模板模式
模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。
为什么要用模板模式
模板模式有两大作用:复用和扩展。其中,复用指的是,所有的子类可以复用父类中提供的模板方法的代码。扩展指的是,框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。
我们在【设计模式-观察者模式】一文中提到过,可以通过实现ApplicationListener来完成对Spring全局事件的监听,代码如下:
public class ZfDtpRunInfoCollectEventLocalFileListener implements ApplicationListener{ @Override public final void onApplicationEvent(ZfDtpRunInfoCollectEvent event) { ZfDtpCollectTimeInfo zfDtpCollectTimeInfo = (ZfDtpCollectTimeInfo) event.getSource(); // 获取采集信息 String collectDay = zfDtpCollectTimeInfo.getCollectDay(); // 保存线程池状态 String localFilePath = CommonUtil.getFilePath(this.getLocalFilePathPrefix(), collectDay); ArrayList 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{ @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 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修饰避免子类重写导致模板失效。