设计模式——模板方法模式的思考

最近在看源码时频繁接触到了设计模式,由于对设计模式理解的不深入,导致看源码时会比较吃力。设计模式相关专题是笔者在阅读《研磨设计模式》和《设计模式的艺术之道》两本书之后总结和思考,另外,笔者会结合大量的实践来阐述设计模式。
模式方法模式作为设计模式专题第一篇,是因为笔者在开发过程中,接触并使用了模板方法模式。首先看一下模板方法的定义

定义一个操作中的算法和骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

模板方法模式属于类行为型模式的一种,它是结构最简单的行为型模式,在其结构中只存在父类和子类的继承关系,通过模板方法模式,可以把一些复杂的流程封装到一系列基本的方法中,在抽象父类中提供一个称之为模板方法的方法来定义这些方法的执行步骤,子类可以覆盖这些步骤,从而使得相同的方法可以得到不同的结果。
举个例子来说,比如我们到银行取钱,一般流程都是拿号、柜台办理业务、服务评价这几个步骤,拿号和服务评价这两个步骤都是固定的,但是很多到银行办理的业务都是不同的,比如有取钱、汇款、贷款等,下面贴出例子的模式结构图:

在Spring中的应用

由于模板方法模式拓展性很强,所以在Spring中应用广泛,我们先看一下Spring IOC容器初始化时运用到的模板方法模式。
首先看ConfigurableApplicationContext接口的refresh方法

1
2
3
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
void refresh() throws BeansException, IllegalStateException;
}

抽象类AbstractApplicationContext实现了上面的接口,主要实现了模板方法refresh的逻辑,这个方法是各种IOC容器初始化的入口

1
2
3
4
5
6
7
8
9
10
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
...
}
}

我们详细看一下在refresh方法里面调用的obtainFreshBeanFactory方法,可以看到该方法里面调用AbstractApplicationContext类的getBeanFactory和refreshBeanFactory两个抽象方法,这两个方法并没有实现,取哪种BeanFactory交给了具体的子类来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}

protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;

@Override
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

我们只看一下实现getBeanFactory方法的子类,首先看一下AbstractRefreshableApplicationContext类

1
2
3
4
5
6
7
8
9
10
@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
synchronized (this.beanFactoryMonitor) {
if (this.beanFactory == null) {
throw new IllegalStateException("BeanFactory not initialized or already closed - " +
"call 'refresh' before accessing beans via the ApplicationContext");
}
return this.beanFactory;
}
}

再看一下GenericApplicationContext类

1
2
3
4
@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory;
}

总结一下,Spring正是通过模板方法模式来选择初始化BeanFactory,下面是上面相关类的UML图:

钩子方法
在AbstractApplicationContext类中postProcessBeanFactoryonRefresh都是空实现方法,这类方法在模板方法模式中称为钩子方法,钩子方法一般是对接口中或抽象方法的空实现。在实际应用中,比如说一个接口有多个方法,你只想用其中一个方法,可以先用抽象类来实现接口,需要的方法设置成抽象方法,其它方法(钩子方法)设置为空实现。

自身实践

最近在公司做过的一个项目中使用到了模板方法模式,这个项目是对Canel(mysql数据库订阅工具)发出来的kafka message消费,并根据已配置的业务来进行Redis存储、HBase存储、接口请求、消息转发等操作,其中获取配置、填充参数、消息过滤等这些步骤都是通用的,故定义在BaseHandler中,而每个操作具体处理(`handler``方法)是不一样的,所有handler方法定义在不同子类中,UML如下,具体逻辑就不具体阐述了。

使用场景

模板方法使用场景很多,概况来说就是对于一个复杂的业务可以抽象成多个步骤,可以把可复用的步骤抽离出来定义到父类中,把可变的步骤延迟到子类实现。

总结

模板方法模式本质就是固定算法的骨架,即先把不变的部分抽离出来定义到父类中,把可变的部分延迟到子类中实现,通过子类来拓展现有的功能,从这一点上看和策略模式很相近,但是策略更强调的是对某个步骤的封装,最好的就是模板方法结合策略模式使用,先用模板方法模式定义出算法的步骤,而对可变的步骤如策略模式一样,通过子类来拓展功能。