如何把重复流程代码重构成模板方法模式?
简化版
重构成模板方法模式的步骤是:先找多个类中重复的流程骨架,再区分固定步骤和变化步骤,把固定流程上移到抽象父类,把变化步骤抽成 protected abstract 或钩子方法,模板入口用 final 固定顺序,最后补子类契约测试。不要只因为代码重复就抽模板,流程顺序稳定、变化点清晰才适合。
详细版
假设 CSV、Excel、JSON 三种导入类都有类似流程:
校验文件 -> 解析数据 -> 校验数据 -> 保存数据 -> 记录日志
只有“解析数据”的方式不同。此时可以抽出:
abstract class AbstractImporter {
public final void importFile(File file) {
validateFile(file);
List<Row> rows = parse(file);
validateRows(rows);
save(rows);
audit(file, rows);
}
protected abstract List<Row> parse(File file);
}
CSV、Excel、JSON 子类只实现 parse。重构后的收益是流程一致、重复代码减少、关键步骤不会漏。
记忆钩子:重构模板方法不是抽公共代码,而是抽稳定流程。
完整版教学
面试提示:重构题要先找重复流程骨架,再抽出稳定步骤和变化步骤,别一上来就继承。
一、先找重复流程而不是重复语句
模板方法适合重复流程:
A -> B -> C -> D
不是看到两行代码相同就抽父类。流程顺序稳定,才值得用模板方法。
例如导入、导出、批处理、报表生成、爬取任务都常见重复流程。
二、标记固定步骤和变化步骤
可以列一张表:
| 步骤 | 是否固定 | 处理方式 |
|---|---|---|
| 校验文件 | 固定 | 父类 private |
| 解析数据 | 变化 | 子类 abstract |
| 校验数据 | 固定 | 父类 private |
| 保存数据 | 固定或可变 | 看业务 |
| 后置通知 | 可选 | hook |
这个过程比直接写代码更重要。
三、把固定流程上移到父类
父类控制顺序:
public abstract class AbstractImportTemplate {
public final ImportResult importFile(File file) {
checkFile(file);
List<Row> rows = parse(file);
checkRows(rows);
ImportResult result = save(rows);
afterImport(result);
return result;
}
}
模板入口命名要表达业务动作,不要叫过于抽象的 execute,除非上下文非常明确。
四、把变化点设计成最小扩展面
只把确实变化的部分留给子类:
protected abstract List<Row> parse(File file);
protected void afterImport(ImportResult result) {
// default no-op
}
不要一次开放太多 protected 方法,否则父类很快失控。
五、迁移子类时保持行为一致
重构前后要保证外部行为不变。可以先写 characterization test:
given same csv file
old importer result == new importer result
然后逐个迁移子类,避免一次改太多。
六、重构后要删除重复入口
如果子类仍然保留各自入口:
csvImport.importCsv();
excelImport.importExcel();
外部可能继续绕过父类。最好统一公共入口,保留适配方法也要委托到模板入口。
七、不适合重构成模板的情况
不适合的信号:
- 流程顺序经常变;
- 每个子类都要覆盖大部分步骤;
- 子类之间不是同一类业务;
- 需要运行时切换算法;
- 组合比继承更自然。
这些场景可考虑策略、组合或流程编排。
八、常见误区与追问
- 误区:只要代码重复就抽模板方法。 关键看流程骨架是否稳定,而不是局部语句重复。
- 误区:父类越抽象越好。 父类应表达清晰业务流程,过度抽象会变难懂。
- 误区:所有差异都用钩子解决。 差异太多说明不适合一个模板。
- 误区:重构后不用测旧行为。 模板方法会影响执行顺序,必须回归关键路径。
- 追问:如何识别变化点? 对比多个实现,找顺序相同但实现不同的步骤。
- 追问:重构过程中如何降低风险? 先测试旧行为,再抽父类,逐个迁移子类,小步提交。
- 追问:如果只有两个类重复要不要抽? 看未来扩展和流程稳定度;重复少且变化少时可以先不抽。
九、加强记忆
- 抽模板方法先找稳定流程。
- 固定步骤上移父类,变化步骤留给子类。
- 模板入口 final 固定顺序。
- 扩展点越少越稳。
- 重构前后要用测试锁住行为。
- 差异太多时不要硬抽模板。