Skip to content

模板方法模式

模板方法模式用于定义一个业务流程的固定骨架,把流程中可变化的步骤延迟到子类或回调实现。它适合“流程顺序稳定,但某些步骤因业务类型不同而变化”的场景。

常见场景:订单导入、文件解析、支付前后处理、审批流节点处理、数据采集任务、Spring 中的 JdbcTemplateRestTemplate、各种 Template 类。

为什么需要模板方法

假设医疗资产平台要导入不同来源的资产文件:Excel、CSV、第三方接口。流程大体相同:

  1. 读取原始数据。
  2. 校验字段。
  3. 转换成资产对象。
  4. 保存数据库。
  5. 记录导入日志。

如果每种导入都复制一遍流程,会出现:

问题后果
流程复制修复日志或事务问题要改多处
步骤顺序不一致有的先保存后校验,产生脏数据
公共异常处理分散失败日志、告警、回滚行为不统一
扩展点不清楚新增导入类型不知道该改哪里

模板方法把稳定流程放到父类,把变化步骤留给子类。

总体流程

mermaid
flowchart TD
    A["调用模板方法 importData"] --> B["读取原始数据"]
    B --> C["校验数据"]
    C --> D["转换为业务对象"]
    D --> E["保存数据库"]
    E --> F["记录导入结果"]

稳定的是步骤顺序,变化的是每一步的实现。

角色

角色作用
AbstractClass定义模板方法和公共流程
ConcreteClass实现可变步骤
Hook Method可选钩子,用于扩展流程中的可选行为

Java Demo:资产导入模板

抽象模板:

java
public abstract class AssetImportTemplate<T> {
    public final ImportResult importData(String source) {
        List<T> rows = read(source);
        validate(rows);
        List<Asset> assets = convert(rows);
        beforeSave(assets);
        save(assets);
        return afterSave(assets);
    }

    protected abstract List<T> read(String source);

    protected abstract void validate(List<T> rows);

    protected abstract List<Asset> convert(List<T> rows);

    protected void beforeSave(List<Asset> assets) {
        // 钩子方法:默认不做事,子类可覆盖
    }

    protected ImportResult afterSave(List<Asset> assets) {
        return new ImportResult(assets.size());
    }

    private void save(List<Asset> assets) {
        // 真实项目中这里可以调用 Repository / Mapper
        System.out.println("save assets: " + assets.size());
    }
}

Excel 导入:

java
public class ExcelAssetImport extends AssetImportTemplate<ExcelRow> {
    @Override
    protected List<ExcelRow> read(String source) {
        return ExcelReader.read(source);
    }

    @Override
    protected void validate(List<ExcelRow> rows) {
        for (ExcelRow row : rows) {
            if (row.assetCode() == null || row.assetCode().isBlank()) {
                throw new IllegalArgumentException("资产编码不能为空");
            }
        }
    }

    @Override
    protected List<Asset> convert(List<ExcelRow> rows) {
        return rows.stream()
                .map(row -> new Asset(row.assetCode(), row.assetName()))
                .toList();
    }
}

调用:

java
AssetImportTemplate<ExcelRow> importer = new ExcelAssetImport();
ImportResult result = importer.importData("asset.xlsx");

关键点:importDatafinal 固定流程,防止子类随意改变顺序。

Spring 中的模板方法

Spring 里很多 Template 类都体现了类似思想:

固定流程变化点
JdbcTemplate获取连接、创建语句、执行 SQL、释放资源SQL 和结果映射
TransactionTemplate开启事务、执行回调、提交/回滚业务回调
RestTemplate构造请求、执行 HTTP、转换响应URL、请求体、响应类型

JdbcTemplate 的简化理解:

mermaid
flowchart TD
    A["调用 query"] --> B["获取 Connection"]
    B --> C["创建 PreparedStatement"]
    C --> D["设置参数并执行 SQL"]
    D --> E["RowMapper 映射结果"]
    E --> F["释放数据库资源"]

开发者只提供 SQL 和 RowMapper,资源管理由模板统一处理。

和策略模式区别

对比模板方法策略模式
关注点固定流程骨架,开放部分步骤多种算法或规则互换
变化方式子类覆盖步骤或钩子注入不同策略实现
调用顺序父类控制调用方或上下文控制
常见场景导入流程、任务流程、Template 类支付方式、折扣规则、解析器

模板方法强调“流程稳定”,策略模式强调“算法可替换”。

商业场景

医疗数据采集任务可以用模板方法:

  1. 拉取数据。
  2. 校验数据。
  3. 去重。
  4. 入库。
  5. 上报采集结果。

不同医院、不同接口的拉取和字段转换不同,但采集主流程相同。模板方法可以避免每个采集器都手写一遍日志、异常、统计和保存流程。

常见坑

问题后果建议
父类流程过大子类难理解,继承层级僵硬模板只固定真正稳定的流程
抽象步骤太多新增子类实现成本高必要步骤抽象,可选步骤用钩子
子类依赖父类细节父类一改子类全坏只暴露清晰扩展点
为了复用强行继承继承关系不自然考虑组合、策略或责任链

面试标准回答

text
模板方法模式是在父类中定义一个固定算法骨架,把部分变化步骤交给子类实现。它适合流程顺序稳定但个别步骤变化的场景,比如导入、任务处理、JdbcTemplate。优点是统一流程、减少重复、控制执行顺序;缺点是继承关系会增加耦合,父类流程过大时不易扩展。和策略模式相比,模板方法固定流程,策略模式替换算法。

本章小结

模板方法的核心不是继承本身,而是“固定流程 + 开放步骤”。当流程真的稳定时,它能让新增业务只关注变化步骤;当流程还不稳定时,过早抽模板会让代码僵硬。