模板方法模式
# 一、概述
模板方法(Template Method)模式在超类中定义了一个算法的框架,允许子类在不修改结构的前提下重写算法的特定步骤。
模板方法特别适合一些整体流程相似,但是具体实现细节有差异的情况,可以避免写很多重复代码。
# 1.1 解决了什么问题
假设有一个文件上传的需求,客户可以上传指定 ID 类型的 CSV 文件,ID 类型包括手机号、身份证号、QQ 号以及 Email,同一 CSV 文件中的 ID 都是相同类型的。
处理文件上传的流程是这样的:
- 解析文件内容
- 校验 ID 字段是否合法
- 获取数据库连接
- 向数据库中插入数据
- 关闭数据库连接
因为此需求中虽然 ID 类型不同,但是处理流程是类似的,而且很多步骤是可以重用的,所以没必要针对每个 ID 类型定义一个类。
# 1.2 解决方案
在模板方法模式中,会抽取代码中的关键步骤为一个个方法,并定义一个模板方法,并在模板方法中根据数据处理流程挨个调用关键步骤。对于那些可重用的步骤,可以直接在模板类中实现,对于一些需要个性化处理的步骤,子类可以重写,但是整体的处理流程子类不会修改。
# 二、实现方式
# 2.1 角色
- Abstract Class:声明作为算法步骤的各个方法,以及一次调用各个步骤方法的模板方法。对于可直接重用的步骤,可以提供默认实现,对于需要个性化处理的步骤,可以提供抽象方法。
- Concrete Class:继承自 Abstract Class,可以重写所有步骤,但是不能重写模板方法,所以建议将模板方法定义为 final。
# 2.2 代码
定义 Abstract Class,其中upload
方法为模板方法,其内部定义了数据的整体处理流程,不允许子类对齐修改。除模板方法之外的其它方法子类是可以重写的。
/**
* 以下代码为了说明案例问题简单示例,不考虑数据库访问细节、异常情况、大数据量情况下的内存溢出等
*/
public abstract class AbstractParser {
String filePath;
Connection connection;
AbstractParser(String filePath) {
this.filePath = filePath;
}
/**
* 模板方法
*
* @throws Exception
*/
public final void upload() throws Exception {
List<String> fileData = getFileData(filePath);
boolean checkResult = checkData(fileData);
if (checkResult) {
getDBConnection();
insertData(fileData);
closeDBConnection();
}
}
public List<String> getFileData(String filePath) throws IOException {
FileInputStream inputStream = new FileInputStream(filePath);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
List<String> data = new ArrayList<>();
String line;
while ((line = bufferedReader.readLine()) != null) {
data.add(line);
}
inputStream.close();
bufferedReader.close();
return data;
}
public abstract boolean checkData(List<String> data);
public void getDBConnection() throws SQLException, ClassNotFoundException {
// String url = "";
// Class.forName("com.mysql.jdbc.Driver");
// connection = DriverManager.getConnection(url);
}
public void insertData(List<String> data) {
for (String id : data) {
System.out.println(id);
}
}
public void closeDBConnection() {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
继承自 Abstract Class 的 Concrete Class:
public class MobileParser extends AbstractParser {
MobileParser(String filePath) {
super(filePath);
}
@Override
public List<String> getFileData(String filePath) throws IOException {
List<String> data = new ArrayList<>();
data.add("18888888888");
data.add("13888888888");
data.add("13988888888");
return data;
}
@Override
public boolean checkData(List<String> data) {
for (String mobile : data) {
if (mobile.length() != 11) {
return false;
}
}
return true;
}
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class QQParser extends AbstractParser {
QQParser(String filePath) {
super(filePath);
}
@Override
public List<String> getFileData(String filePath) throws IOException {
List<String> data = new ArrayList<>();
data.add("888888");
data.add("123456");
data.add("9999999");
return data;
}
@Override
public boolean checkData(List<String> data) {
for (String qq : data) {
// 规则仅为示例,真实情况肯定没这么简单
if (qq.length() < 6 || qq.length() > 11) {
return false;
}
}
return true;
}
}
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
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
使用示例:
public class TemplateMethodTest {
public static void main(String[] args) throws Exception {
MobileParser mobileParser = new MobileParser("xxx");
mobileParser.upload();
QQParser qqParser = new QQParser("xxx");
qqParser.upload();
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
18888888888
13888888888
13988888888
888888
123456
9999999
1
2
3
4
5
6
2
3
4
5
6
# 三、源码中的应用
- java.util.Collections#sort()
- java.io.InputStream#skip()
- java.io.InputStream#read()
- java.util.AbstractList#indexOf()
上次更新: 2023/11/01, 03:11:44