1) 模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern), 在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。 2) 简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤 3) 这种类型的设计模式属于行为型模式。
模板方法模式是基于继承的代码复用技术的。在模板方法模式中,我们可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中。也就是说我们需要声明一个抽象的父类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法让子类来实现剩余的逻辑,不同的子类可以以不同的方式来实现这些逻辑。
其实所谓模板就是一个方法,这个方法将算法的实现定义成了一组步骤,其中任何步骤都是可以抽象的,交由子类来负责实现。这样就可以保证算法的结构保持不变,同时由子类提供部分实现。
模板是一个方法,那么他与普通的方法存在什么不同呢?模板方法是定义在抽象类中,把基本操作方法组合在一起形成一个总算法或者一组步骤的方法。而普通的方法是实现各个步骤的方法,我们可以认为普通方法是模板方法的一个组成部分。
模式结构从上面的结构可以看出,模板方法模式就两个角色:
AbstractClass: 抽象类,类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现其它的抽象方法operationr2,3,4
ConcreteClass: 具体子类,实现抽象方法operationr2,3,4, 以完成算法中特点子类的步骤
其中抽象类提供一组算法和部分逻辑的实现,具体子类实现剩余逻辑。
模板方法场景具有统一的操作步骤或操作过程
存在多个具有同样操作步骤的应用场景,其中只有细节不一样。
以制作豆浆的程序为例 1) 制作豆浆的流程选材--->添加配料--->浸泡--->放到豆浆机打碎 2) 通过添加不同的配料,可以制作出不同口味的豆浆 3) 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
思路分析和图解(类图)
代码实现
package com.dongguo.template;
/**
* @author Dongguo
* @date 2021/8/22 0022-18:29
* @description: 抽象类,表示豆浆
*/
public abstract class SoyaMilk {
//模板方法, make , 模板方法可以做成final , 不让子类去覆盖.
final void make() {
select();
addCondiments();
soak();
beat();
}
//选材料
void select() {
System.out.println("第一步:选择好的新鲜黄豆");
}
//添加不同的配料, 抽象方法, 子类具体实现
abstract void addCondiments();
//浸泡
void soak() {
System.out.println("第三步, 黄豆和配料开始浸泡, 需要3 小时");
}
void beat() {
System.out.println("第四步:黄豆和配料放到豆浆机去打碎");
}
}
package com.dongguo.template;
/**
* @author Dongguo
* @date 2021/8/22 0022-18:30
* @description:
*/
public class PeanutSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println(" 加入上好的花生");
}
}
package com.dongguo.template;
/**
* @author Dongguo
* @date 2021/8/22 0022-18:30
* @description:
*/
public class RedBeanSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println(" 加入上好的红豆");
}
}
package com.dongguo.template;
/**
* @author Dongguo
* @date 2021/8/22 0022-18:29
* @description:
*/
public class Client {
public static void main(String[] args) {
//制作红豆豆浆
System.out.println("----制作红豆豆浆----");
SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
redBeanSoyaMilk.make();
System.out.println("----制作花生豆浆----");
SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
peanutSoyaMilk.make();
}
}
运行结果:
----制作红豆豆浆----
第一步:选择好的新鲜黄豆
加入上好的红豆
第三步, 黄豆和配料开始浸泡, 需要3 小时
第四步:黄豆和配料放到豆浆机去打碎
----制作花生豆浆----
第一步:选择好的新鲜黄豆
加入上好的花生
第三步, 黄豆和配料开始浸泡, 需要3 小时
第四步:黄豆和配料放到豆浆机去打碎
模板方法模式的钩子方法
1) 在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。 2) 还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配料,请使用钩子方法对前面的模板方法进行改造
package com.dongguo.template.improve;
/**
* @author Dongguo
* @date 2021/8/22 0022-18:34
* @description:
*/
public abstract class SoyaMilk {
//模板方法, make , 模板方法可以做成final , 不让子类去覆盖.
final void make() {
select();
if(customerWantCondiments()) {
addCondiments();
}
soak();
beat();
}
//选材料
void select() {
System.out.println("第一步:选择好的新鲜黄豆");
}
//添加不同的配料, 抽象方法, 子类具体实现
abstract void addCondiments();
//浸泡
void soak() {
System.out.println("第三步, 黄豆和配料开始浸泡, 需要3 小时");
}
void beat() {
System.out.println("第四步:黄豆和配料放到豆浆机去打碎");
}
//钩子方法,决定是否需要添加配料
boolean customerWantCondiments() {
return true;
}
}
package com.dongguo.template.improve;
/**
* @author Dongguo
* @date 2021/8/22 0022-18:30
* @description:
*/
public class PeanutSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println(" 加入上好的花生");
}
}
package com.dongguo.template.improve;
/**
* @author Dongguo
* @date 2021/8/22 0022-18:30
* @description:
*/
public class RedBeanSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println(" 加入上好的红豆");
}
}
增加一个制作纯豆浆
package com.dongguo.template.improve;
/**
* @author Dongguo
* @date 2021/8/22 0022-18:39
* @description:
*/
public class PureSoyaMilk extends SoyaMilk{
@Override
void addCondiments() {
//空实现
}
@Override
boolean customerWantCondiments() {
return false;
}
}
package com.dongguo.template.improve;
/**
* @author Dongguo
* @date 2021/8/22 0022-18:29
* @description:
*/
public class Client {
public static void main(String[] args) {
//制作红豆豆浆
System.out.println("----制作红豆豆浆----");
SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
redBeanSoyaMilk.make();
System.out.println("----制作花生豆浆----");
SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
peanutSoyaMilk.make();
System.out.println("----制作纯豆浆----");
SoyaMilk pureSoyaMilk = new PureSoyaMilk();
pureSoyaMilk.make();
}
}
运行结果:
----制作红豆豆浆----
第一步:选择好的新鲜黄豆
加入上好的红豆
第三步, 黄豆和配料开始浸泡, 需要3 小时
第四步:黄豆和配料放到豆浆机去打碎
----制作花生豆浆----
第一步:选择好的新鲜黄豆
加入上好的花生
第三步, 黄豆和配料开始浸泡, 需要3 小时
第四步:黄豆和配料放到豆浆机去打碎
----制作纯豆浆----
第一步:选择好的新鲜黄豆
第三步, 黄豆和配料开始浸泡, 需要3 小时
第四步:黄豆和配料放到豆浆机去打碎
模板方法模式在Spring 框架应用的源码分析
Spring IOC 容器初始化时运用到的模板方法模式
Spring Bean的初始化过程中,主要是org.springframework.context.support.AbstractApplicationContext#refresh
这个方法实现的。
其实refresh方法就是一个模板方法: 该方法里定义了一系列Bean初始化应该有的步骤,而把有些步骤留个了子类实现(抽象方法),有些步骤子类可选择性扩展(钩子方法)。
我们来简单看看这个方法的源码(本次只简单讨论其模版方法设计,不讨论其他具体实现细节,关注①②③④处即可):
/**
* AbstractApplicationContext#refresh() * ① Bean 初始化核心方法(这是一个模板方法) *
*/
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符
prepareRefresh();
// 注意:obtainFreshBeanFactory 方法内部调用了两个抽象方法 refreshBeanFactory() 和 getBeanFactory()
// ② 调用抽象方法(延迟到子类实现):将这两个方法的具体实现留给了子类控制
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean
prepareBeanFactory(beanFactory);
try {
// Bean 转成 BeanDefinition 后,初始化前
// ③ 钩子方法:子类可以选择覆盖扩展,添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事
postProcessBeanFactory(beanFactory);
// 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法
invokeBeanFactoryPostProcessors(beanFactory);
// 注册 BeanPostProcessor 的实现类
registerBeanPostProcessors(beanFactory);
// 初始化当前 ApplicationContext 的 MessageSource
initMessageSource();
// 初始化当前 ApplicationContext 的事件广播器
initApplicationEventMulticaster();
// 具体的子类可以在这里初始化一些特殊的 Bean
// ④ 钩子方法:子类可以选择性覆盖实现,初始化一些特殊的 Bean
onRefresh();
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
...
}
}
而模板方法refresh()中涉及的其他方法(如抽象方法、钩子方法)如下:
// AbstractApplicationContext#refresh()中的 obtainFreshBeanFactory()方法
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
...
return getBeanFactory();
}
// AbstractApplicationContext#refreshBeanFactory 抽象方法,子类必须覆盖
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
// AbstractApplicationContext#getBeanFactory 抽象方法,子类必须覆盖
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
// AbstractApplicationContext#postProcessBeanFactory 空方法,留给子类选择性覆盖
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
// AbstractApplicationContext#onRefresh 空方法,留给子类选择性覆盖
protected void onRefresh() throws BeansException {
For subclasses:do nothing by
default.
}
-
① refresh()
: 是一个模板方法,定义了一个Bean初始化流程的整体过程; -
② obtainFreshBeanFactory()
: 调用了两个抽象方法(refreshBeanFactory
和getBeanFactory
),使子类可以通过扩展这两个方法的方式,自定义子类的具体行为; -
③ postProcessBeanFactory(beanFactory)
和④ onRefresh()
: 是一个钩子方法,父类中是一个空方法,留个子类选择是否扩展实现;
1、模板方法模式在定义了一组算法,将具体的实现交由子类负责。
2、模板方法模式是一种代码复用的基本技术。
3、模板方法模式导致一种反向的控制结构,通过一个父类调用其子类的操作,通过对子类的扩展增加新的行为,符合“开闭原则”。
缺点每一个不同的实现都需要一个子类来实现,导致类的个数增加,是的系统更加庞大。
使用场景1、 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
2、 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
3、控制子类的扩展。
模式总结1、 模板方法模式定义了算法的步骤,将这些步骤的实现延迟到了子类。
2、 模板方法模式为我们提供了一种代码复用的重要技巧。
3、 模板方法模式的抽象类可以定义抽象方法、具体方法和钩子。
4、 为了防止子类改变算法的实现步骤,我们可以将模板方法声明为final。