在我们实际开发中,如果一个方法极其复杂时,如果我们将所有的逻辑写在一个方法中,那维护起来就很困难,要替换某些步骤时都要重新写,这样代码的扩展性就很差,当遇到这种情况就要考虑今天的主角——模板方法模式。
一、概念理解
模板方法模式的概念很简单,在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
既然概念叫“骨架”,那想当然的就是定义一个抽象类,这是模板方法模式的第一个角色——抽象模板角色,要有延迟子类实现骨架方法,这是模板方法的第二个角色——具体模板角色。
抽象模板角色:定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤,定义并实现了一个模板方法。
具体模板角色:实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。每一个抽象模板角色都可以有任意多个具体模板角色与之对应。
二、案例实现
在我们的业务开发中往往都需要很多对象、很多方法,对象间也大都存在依赖关系,如果我们手动创建、管理对象就是一件极其困难的事。
如果我们使用工厂模式用于创建对象,使用一个容器用于管理对象,那么再使用起来就变得极其简单了。
在“这个过程”中创建对象就是一个很复杂的算法,而且创建对象的方式往往也不是单一的,我们要考虑能替换算法,这时候就可以使用模板方法模式。
假设创建对象有两种方式,一种是基于注解,一种是基于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鼓励我一下。这是芒果管理系统前端。
设计模式的学习要成体系,推荐你看我往期发布的设计模式文章。