设计模式(四):模板方法模式

概述

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。

模板方法模式旨在为一些方法的主体部分提供骨架,将具体细节上的一些实现延迟到他的实现类。

JDBC 运用了模板模式。JDK 在实现 List 接口的过程中, AbstractCollection 和 AbstractList 也使用了模板模式。

一、简单实现

举个我在做项目的时候遇到的例子:

假设我们有一个简单的针对 Demo 类的文件导出类,他提供了 Excel 文档的基本导出功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DemoExcelExporter {
// 导出excel
public void export() {
List<Demo> list = getData();
for(Demo d : list) {
handle(d);
}
excel(list);
}

// 1.获取要到导出数据集
public List<Demo> getData() {}
// 2.处理数据集
public void handle(Demo demo) {}
// 3.导出的数据集为excel文件
public void excel(List<Demo> list) {}

}

现在我们需要为 Demo2 和 Demo3 这两个类也添加一个导出 Excel 文档的功能,最简单是方式就是复制黏贴,把 Demo 换成 Demo2 和 Demo3。但是这显然不是我们想要的,根据模板模式,我们可以设置一个 ExcelExporter 抽象类作为模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class ExcelExporter {
// 导出excel
public void export() {
List<T> list = getData();
for(T t : list) {
handle(t);
}
excel(list);
}

// 1.获取要到导出数据集
public List<T> getData() {}

// 2.处理数据集,改为抽象方法,由子类去实现
public abstract void handle(T demo);

// 3.导出的数据集为excel文件
public void excel(List<T> list) {}

}

然后由子类去继承这个模板:

1
2
3
4
5
6
7
8
9
10
// Demo类的ExcelExporter
public class DemoExcelExporter extends ExcelExporter<Demo> {
@Override
public void handle(Demo demo) {
// Demo的处理逻辑
}
}

// 调用
new DemoExcelExporter().export();

其他两个类也分别创建自己的 ExcelExporter 类,通过继承模板,他们都直接获得了已经实现了大部分的逻辑的export()方法了,只需要自己再根据需求实现一下 handle方法即可。

模板方法模式改进的ExcelExporter

二、模板的继承

模板模式的强大之处在于,模板和模板之间可以通过继承来进一步增强现有的方法。

比如我们现在希望 Demo3 不仅要导出 Excel 模板,还要导出美化排版,我们可以在 ExcelExporter 的基础上继续新增模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class BeautifyExportExporter<T> extends  ExcelExporter<T>{

// 重写了excel方法,在导出前可以先设置一些样式
@Override
public void excel(List<T> list) {
settingStyle();
super.excel(list);
}

// 自定义要设置的样式
public abstract void settingStyle();

}

现在 Demo3 不再继承 ExcelExporter ,而是去继承 BeautifyExporter:

1
2
3
4
5
6
7
8
9
10
11
12
public class Demo3ExcelExporter extends BeautifyExporter<Demo3> {

@Override
public void handle(Demo3 demo) {
// Demo的处理逻辑
}

@Override
public void settingStyle() {
// 设置样式
}
}

现在,BeautifyExporter 对 ExcelExporter 定义的逻辑进行了一定的调整,添加了可以设置样式的抽象方法,Demo3ExcelExporter 通过继承了 BeautifyExporter 获得了经过调整之后的 ExcelExporter 的逻辑和方法。

调整之后的 ExcelExporter 的逻辑

三、模板模式的特点

1.可以修改实现仍然复用逻辑

实际上,大多数时候我们会选择抽取工具类,比如上述的第一个例子,我们可以把 Demo 换成泛型,这样 Demo2 和 Demo3 也可以复用了。

但是有一个关键的问题:Demo2 和 Demo3 并不是同一个类,他们字段不同,导出中间有些的处理过程也不同,尽管他们的整体过程大体相同,但是有些地方的处理过程是不一样的。比如说 getData()excel() 这三个类都可用,但是 Demo 的 handle() 方法却不能复用在 Demo2 和 Demo3 。

我们可以妥协一下,只把 getData()excel() 提取成公共类,但是实际上整个导出过程的三个方法是连贯的,只提取两个方法,尽管export()中调用的顺序是定死的,最后实现的时候还是需要重复写无数个相同逻辑的export(),也就是说,我们只复用的方法,而没有复用逻辑

所以根据上述的分析,我们可以看出,模板模式最大的优点在于使子类可以不改变一个算法的结构即可重定义该算法的某些步骤,这使得模板模式可以复用代码逻辑结构。因为模板父类已经定义好了整个方法执行流程,构建好了骨架和一些关键的步骤,所以子类只需要实现一些关键的步骤,并且根据需求去重写一些父类方法以调整逻辑。

2.扩展方便

相对接口而言,如果接口是简答题,那么抽象类就是填空题。由于模板方法模式使用的是抽象类,业务的实现因此可以是“分步”的,整个功能是可以一层一层完善并且加强,或者做一些调整,这使得一套代码和逻辑可以在各个地方分层复用。

比如下图就是一个例子。

image-20201120180913712
0%