在我们实际开发中,如果一个方法极其复杂时,如果我们将所有的逻辑写在一个方法中,那维护起来就很困难,要替换某些步骤时都要重新写,这样代码的扩展性就很差,当遇到这种情况就要考虑今天的主角——模板方法模式。
一、概念理解
模板方法模式的概念很简单,在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
既然概念叫“骨架”,那想当然的就是定义一个抽象类,这是模板方法模式的第一个角色——抽象模板角色,要有延迟子类实现骨架方法,这是模板方法的第二个角色——具体模板角色。
抽象模板角色:定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤,定义并实现了一个模板方法。
具体模板角色:实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。每一个抽象模板角色都可以有任意多个具体模板角色与之对应。
二、案例实现
在我们的业务开发中往往都需要很多对象、很多方法,对象间也大都存在依赖关系,如果我们手动创建、管理对象就是一件极其困难的事。
如果我们使用工厂模式用于创建对象,使用一个容器用于管理对象,那么再使用起来就变得极其简单了。
在“这个过程”中创建对象就是一个很复杂的算法,而且创建对象的方式往往也不是单一的,我们要考虑能替换算法,这时候就可以使用模板方法模式。
假设创建对象有两种方式,一种是基于注解,一种是基于xml,我们就将该方法定义为一个模板方法,基于注解和基于xml让子类去实现。
我们用refresh()方法代表这个复杂的过程,在这个过程中应该包括:
①开始工作前的预处理;
②创建管理对象的容器(模板方法,基于注解和基于XML交给子类实现);
③模板方法(交给子类,方便扩展);
④其他方法(容器刷新后、国际化、应用监听、发布事件,等等等一堆事)。
我们基于模板方法模式我们实现简单的demo。
抽象模板角色:
我们在抽象模板角色中实现部分逻辑,而创建对象的容器obtainFreshBeanFactory()方法交给子类实现,onRefresh()空方法交给子类实现便于扩展。
/** * 抽象模板角色 * @author tcy * @Date 28-09-2022 */ public abstract class AbstractApplicationContext { /** * 案例中容器和对象的创建全过程 */ public void refresh(){ this.prepareRefresh(); this.obtainFreshBeanFactory(); this.onRefresh(); } protected void prepareRefresh(){ System.out.println("我用于开始工作前的预处理..."); } protected void obtainFreshBeanFactory(){ System.out.println("我用于创建默认管理对象的容器..."); } /** * 模板方法,子类去实现[springboot实现了他,感兴趣具体可以研究一下] */ protected void onRefresh() { } protected void otherMethod(){ System.out.println("容器刷新后、国际化、应用监听、发布事件,等等等,一堆事"); } }
具体模板角色-基于注解创建管理对象的容器
/** * 具体模板角色-基于注解 * @author tcy * @Date 28-09-2022 */ public class ApplicationContextAnnotation extends AbstractApplicationContext { @Override protected void obtainFreshBeanFactory() { System.out.println("这是基于注解的创建对象容器..."); } }
具体模板角色-基于xml创建管理对象的容器
/** * 具体模板角色-基于xml * @author tcy * @Date 28-09-2022 */ public class ApplicationContextXml extends AbstractApplicationContext { @Override protected void obtainFreshBeanFactory() { System.out.println("这是xml的创建对象容器..."); } }
客户端-模拟容器启动过程:
/** * 容器启动过程 * @author tcy * @Date 28-09-2022 */ public class Client { public static void main(String[] args) { //基于xml方式 ApplicationContextXml applicationContextXml = new ApplicationContextXml(); applicationContextXml.refresh(); //基于注解方式 ApplicationContextAnnotation annotation=new ApplicationContextAnnotation(); annotation.refresh(); } }
我用于开始工作前的预处理... 这是xml的创建对象容器... 容器刷新后、国际化、应用监听、发布事件,等等等,一堆事 我用于开始工作前的预处理... 这是基于注解的创建对象容器... 容器刷新后、国际化、应用监听、发布事件,等等等,一堆事
对Spring源码稍微有点了解的同学大概已经知道,我们案例实现的正是简易版的Spring的Refresh()方法,Refresh()方法是Spring最核心的方法,Spring良好的扩展性正是离不开模板方法模式的运用。下图为Spring核心Refresh()方法的执行大流程注释。
在我们案例中的onRefresh()的空方法,实际中Springboot就是实现了这个空方法,onRefresh()方法调用了Tomcat的jar包启动,这也是Springboot不需要手动注入Tomcat的原因。
相信通过这个案例的理解,大部分同学不仅能很好的理解模板方法模式,想必对Spring的启动过程也有了一个大概的了解。
三、总结
模板方法应用场景太普遍了,在实际开发中有多个子类共有的方法,并且逻辑相同,可以考虑使用模板方法模式。当面对重要、复杂的算法,也可以把核心算法设计为模板方法模式,相关细节则由各个子类实现。
模板方法优点突出:封装不变部分,扩展可变部分;提取公共代码,便于维护;行为由父类控制,子类实现。
模板方法的缺点很明显,当方法实现过多的时候,每一个不同的实现都需要一个子类来实现,这必然导致类的个数增加,使得系统变得更加庞大。
个人独立开发的应用框架芒果管理系统后端,支持前后端代码生成、支持字段注解等实用开发功能已全部开源,感兴趣的同学可以点个star鼓励我一下。这是芒果管理系统前端。
设计模式的学习要成体系,推荐你看我往期发布的设计模式文章。