大数据知识体系
首页
数据结构与算法
  • JVM
  • Java
  • Scala
  • Python
设计模式
  • MySQL
  • Redis
  • HDFS
  • HBase
  • ClickHouse
  • ElasticSearch
  • Iceberg
  • Hudi
  • Spark
  • Flink
  • Hive
  • Yarn
  • Zookeeper
  • Maven
  • Git
  • 数据仓库
  • 用户画像
  • 指标体系
数据治理
关于
首页
数据结构与算法
  • JVM
  • Java
  • Scala
  • Python
设计模式
  • MySQL
  • Redis
  • HDFS
  • HBase
  • ClickHouse
  • ElasticSearch
  • Iceberg
  • Hudi
  • Spark
  • Flink
  • Hive
  • Yarn
  • Zookeeper
  • Maven
  • Git
  • 数据仓库
  • 用户画像
  • 指标体系
数据治理
关于
  • 设计模式概述
  • 创建型模式

    • 单例模式
    • 简单工厂模式
    • 工厂方法模式
    • 抽象工厂模式
    • 建造者模式
    • 原型模式
  • 结构型模式

    • 适配器模式
    • 装饰器模式
    • 代理模式
    • 外观模式
    • 桥接模式
    • 组合模式
    • 享元模式
  • 行为型模式

    • 策略模式
    • 模板方法模式
      • 一、概述
        • 1.1 解决了什么问题
        • 1.2 解决方案
      • 二、实现方式
        • 2.1 角色
        • 2.2 代码
      • 三、源码中的应用
    • 观察者模式
    • 迭代器模式
    • 责任链模式
    • 命令模式
    • 备忘录模式
    • 状态模式
    • 访问者模式
    • 中介者模式
    • 解释器模式
  • 设计模式
  • 行为型模式
Will
2022-03-29
目录

模板方法模式

# 一、概述

模板方法(Template Method)模式在超类中定义了一个算法的框架,允许子类在不修改结构的前提下重写算法的特定步骤。

模板方法特别适合一些整体流程相似,但是具体实现细节有差异的情况,可以避免写很多重复代码。

# 1.1 解决了什么问题

假设有一个文件上传的需求,客户可以上传指定 ID 类型的 CSV 文件,ID 类型包括手机号、身份证号、QQ 号以及 Email,同一 CSV 文件中的 ID 都是相同类型的。

处理文件上传的流程是这样的:

  1. 解析文件内容
  2. 校验 ID 字段是否合法
  3. 获取数据库连接
  4. 向数据库中插入数据
  5. 关闭数据库连接

因为此需求中虽然 ID 类型不同,但是处理流程是类似的,而且很多步骤是可以重用的,所以没必要针对每个 ID 类型定义一个类。

# 1.2 解决方案

在模板方法模式中,会抽取代码中的关键步骤为一个个方法,并定义一个模板方法,并在模板方法中根据数据处理流程挨个调用关键步骤。对于那些可重用的步骤,可以直接在模板类中实现,对于一些需要个性化处理的步骤,子类可以重写,但是整体的处理流程子类不会修改。

# 二、实现方式

# 2.1 角色

  1. Abstract Class:声明作为算法步骤的各个方法,以及一次调用各个步骤方法的模板方法。对于可直接重用的步骤,可以提供默认实现,对于需要个性化处理的步骤,可以提供抽象方法。
  2. 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

继承自 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
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

使用示例:

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
18888888888
13888888888
13988888888
888888
123456
9999999
1
2
3
4
5
6

# 三、源码中的应用

  1. java.util.Collections#sort()
  2. java.io.InputStream#skip()
  3. java.io.InputStream#read()
  4. java.util.AbstractList#indexOf()
上次更新: 2023/11/01, 03:11:44

← 策略模式 观察者模式→

Theme by Vdoing | Copyright © 2022-2023 Will 蜀ICP备2022002285号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式