模板方法模式
模板方法模式用于定义一个业务流程的固定骨架,把流程中可变化的步骤延迟到子类或回调实现。它适合“流程顺序稳定,但某些步骤因业务类型不同而变化”的场景。
常见场景:订单导入、文件解析、支付前后处理、审批流节点处理、数据采集任务、Spring 中的 JdbcTemplate、RestTemplate、各种 Template 类。
为什么需要模板方法
假设医疗资产平台要导入不同来源的资产文件:Excel、CSV、第三方接口。流程大体相同:
- 读取原始数据。
- 校验字段。
- 转换成资产对象。
- 保存数据库。
- 记录导入日志。
如果每种导入都复制一遍流程,会出现:
| 问题 | 后果 |
|---|---|
| 流程复制 | 修复日志或事务问题要改多处 |
| 步骤顺序不一致 | 有的先保存后校验,产生脏数据 |
| 公共异常处理分散 | 失败日志、告警、回滚行为不统一 |
| 扩展点不清楚 | 新增导入类型不知道该改哪里 |
模板方法把稳定流程放到父类,把变化步骤留给子类。
总体流程
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");关键点:importData 用 final 固定流程,防止子类随意改变顺序。
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 类 | 支付方式、折扣规则、解析器 |
模板方法强调“流程稳定”,策略模式强调“算法可替换”。
商业场景
医疗数据采集任务可以用模板方法:
- 拉取数据。
- 校验数据。
- 去重。
- 入库。
- 上报采集结果。
不同医院、不同接口的拉取和字段转换不同,但采集主流程相同。模板方法可以避免每个采集器都手写一遍日志、异常、统计和保存流程。
常见坑
| 问题 | 后果 | 建议 |
|---|---|---|
| 父类流程过大 | 子类难理解,继承层级僵硬 | 模板只固定真正稳定的流程 |
| 抽象步骤太多 | 新增子类实现成本高 | 必要步骤抽象,可选步骤用钩子 |
| 子类依赖父类细节 | 父类一改子类全坏 | 只暴露清晰扩展点 |
| 为了复用强行继承 | 继承关系不自然 | 考虑组合、策略或责任链 |
面试标准回答
text
模板方法模式是在父类中定义一个固定算法骨架,把部分变化步骤交给子类实现。它适合流程顺序稳定但个别步骤变化的场景,比如导入、任务处理、JdbcTemplate。优点是统一流程、减少重复、控制执行顺序;缺点是继承关系会增加耦合,父类流程过大时不易扩展。和策略模式相比,模板方法固定流程,策略模式替换算法。本章小结
模板方法的核心不是继承本身,而是“固定流程 + 开放步骤”。当流程真的稳定时,它能让新增业务只关注变化步骤;当流程还不稳定时,过早抽模板会让代码僵硬。
