设计模式(十)之行为型模式--模板方法模式

一、行为型模式

  1. 模板方法模式
  2. 命令模式
  3. 迭代器模式
  4. 观察者模式
  5. 中介者模式
  6. 备忘录模式
  7. 解释器模式
  8. 状态模式
  9. 策略模式
  10. 职责链模式
  11. 访问者模式

二、模板方法模式

2.1 案例分析

2.1.1 豆浆制作问题

编写制作豆浆的程序,说明如下:

  1. 制作豆浆的流程:选材—>添加配料—>浸泡—>放到豆浆机打碎
  2. 通过添加不同的配料,可以制作出不同口味的豆浆
  3. 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
  4. 可使用模板方法模式完成(说明:因为模板方法模式,比较简单,很容易就想到这个方案,因此直接就可以使用,不再使用传统的方案来引出模板方法模式)

2.2 模板方法模式基本介绍

2.2.1 基本介绍

  1. 模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
  2. 简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以冲定义该算法的某些特定步骤。

2.2.2 原理类图

image-20210722230423631

2.2.3 案例类图

img

3、代码实例

3.1 SoyaMilk抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 抽象类,表示豆浆
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("第四步:黄豆和配料放到豆浆机去打碎 ");
}
}

3.2 RedBeanSoyaMilk类

1
2
3
4
5
6
public class RedBeanSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println("加入好的红豆 ");
}
}

3.3 PeanutSoyaMilk类

1
2
3
4
5
6
public class PeanutSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println("加入好的花生 ");
}
}

3.4 Client类

1
2
3
4
5
6
7
8
9
10
11
12
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();
}
}

结果:

1
2
3
4
5
6
7
8
9
10
-----制作红豆豆浆-------
第一步:选择好的新鲜黄豆
加入好的红豆
第三步,黄豆和配料开始浸泡,需要3小时
第四步:黄豆和配料放到豆浆机去打碎
------制作花生豆浆-------
第一步:选择好的新鲜黄豆
加入好的花生
第三步,黄豆和配料开始浸泡,需要3小时
第四步:黄豆和配料放到豆浆机去打碎

3.5 模板方法模式中的钩子方法

  1. 在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事情,子类可以视情况要不要覆盖它,该方法称为“钩子”
  2. 用上面做豆浆的例子来讲解,比如:我们还希望制作纯豆浆,不添加任何的配料,可以使用钩子方法对前面的模板方法进行改造

3.5.1 SoyaMilk类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 抽象类,表示豆浆
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;
}
}

3.5.2 RedBeanSoyaMilk类

1
2
3
4
5
6
public class RedBeanSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println("加入好的红豆 ");
}
}

3.5.3 PureSoyaMilk类

1
2
3
4
5
6
7
8
9
10
11
public class PureSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
//空实现
}

@Override
boolean customerWantCondiments() {
return false;
}
}

3.5.4 Client类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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("-------制作纯豆浆---------");
PureSoyaMilk pureSoyaMilk = new PureSoyaMilk();
pureSoyaMilk.make();
}
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-----制作红豆豆浆-------
第一步:选择好的新鲜黄豆
加入好的红豆
第三步,黄豆和配料开始浸泡,需要3小时
第四步:黄豆和配料放到豆浆机去打碎
------制作花生豆浆-------
第一步:选择好的新鲜黄豆
加入好的花生
第三步,黄豆和配料开始浸泡,需要3小时
第四步:黄豆和配料放到豆浆机去打碎
-------制作纯豆浆---------
第一步:选择好的新鲜黄豆
第三步,黄豆和配料开始浸泡,需要3小时
第四步:黄豆和配料放到豆浆机去打碎

4、模板方法模式在Spring框架应用的源码分析

Spring IOC容器初始化时运用到的模板方法模式

4.1 ConfigurableApplicationContext类

4.2 ConfigurableApplicationContext接口

1
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {}

4.3 AbstractApplicationContext类

实现ConfigurableApplicationContext接口

1
2
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {}

AbstractApplicationContext类中有refresh方法

1
2
3
4
5
6
7
8
9
10
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);

try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);

refresh方法中的obtainFreshBeanFactory方法

1
2
3
4
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
this.refreshBeanFactory();
return this.getBeanFactory();
}

refreshBeanFactory和getBeanFactory都是抽象方法

1
2
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

refresh方法中的postProcessBeanFactory方法是一个空实现,是一个钩子方法

1
2
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}

refresh方法中的onRefresh方法是一个空实现,是一个钩子方法

1
2
protected void onRefresh() throws BeansException {
}

4.4 GenericApplicationContext类

GenericApplicationContext继承AbstractApplicationContext

1
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {

getBeanFactory实现

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

refreshBeanFactory实现

1
2
3
4
5
6
7
protected final void refreshBeanFactory() throws IllegalStateException {
if (!this.refreshed.compareAndSet(false, true)) {
throw new IllegalStateException("GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
} else {
this.beanFactory.setSerializationId(this.getId());
}
}

4.5 类图理解

image-20210727004131005

5、模板方法模式的注意事项和细节

  1. 基本思想:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
  2. 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用
  3. 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由于类提供部分步骤的实现
  4. 不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
  5. 一般模板方法都加上final关键字,防止子类重写模板方法
  6. 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列的步骤,这一系列的步骤基本相同,但其个别步骤在是实现时可能不同,通常考虑模板方法模式来处理。
0%