讲解
冲咖啡和泡茶的流程一模一样:烧水、冲泡、倒入杯中、加调料——变的只是「冲什么」和「加什么」。聪明的店家会把流程写成固定 SOP,店员只填两个空。模板方法模式就是这样:在一个方法里定义算法的骨架,把某些步骤延迟到子类中,让子类不改变算法结构即可重定义某些特定步骤。
这是 23 种模式里和「继承」绑定最深的一个:抽象类里那个固定的骨架方法(模板方法)依次调用若干步骤;步骤分三种——抽象方法(子类必须实现,比如 parse)、具体方法(父类已有默认实现)、钩子方法(hook,父类给空默认实现,子类可选覆盖,比如「是否加调料」)。模板方法本身最好声明为不可覆写(TypeScript 里靠约定 + readonly 语义,Java 里用 final),保证骨架不被子类篡改。
它体现的原则有个响亮的名字:好莱坞原则——「别打电话给我们,我们会打给你」。框架(父类)掌握控制权,在合适时机回调你的代码(子类);控制流是反转的。数据导入、骨架测试、CI 流水线(拉代码 → 装依赖 → 构建 → 部署)、游戏主循环,都是模板方法的天然形状。
和策略模式的分工:策略用组合换整个算法(整体替换);模板方法用继承改算法的某些步骤(骨架不变、环节可换)。流程的整体形状稳定、只有环节变化时用模板方法;整个算法都可以换时用策略。
示例
数据导入流水线:验证 → 解析 → 汇总的骨架固定,CSV 和 JSON 各自实现解析环节:
import assert from 'node:assert/strict';
interface RawRecord {
name: string;
score: number;
}
// 抽象类:run 是模板方法,骨架顺序不可变
abstract class DataImporter {
run(raw: string): string {
if (raw.trim().length === 0) {
throw new Error('输入为空');
}
const records = this.parse(raw); // 抽象步骤:子类实现
const passed = records.filter((r) => r.score >= 60); // 通用步骤:父类实现
return '共 ' + records.length + ' 条,及格 ' + passed.length + ' 条';
}
protected abstract parse(raw: string): RawRecord[];
}
class CsvImporter extends DataImporter {
protected parse(raw: string): RawRecord[] {
return raw
.trim()
.split('\n')
.map((line) => {
const [name, score] = line.split(',');
return { name, score: Number(score) };
});
}
}
class JsonImporter extends DataImporter {
protected parse(raw: string): RawRecord[] {
return JSON.parse(raw) as RawRecord[];
}
}
const csv = new CsvImporter().run('小林,88\n小陈,55\n小周,72');
assert.equal(csv, '共 3 条,及格 2 条');
const json = new JsonImporter().run('[{"name":"小林","score":90}]');
assert.equal(json, '共 1 条,及格 1 条');
console.log('CSV 导入:' + csv);
console.log('JSON 导入:' + json);
注意控制权的方向:子类从来不调用 run 以外的流程,反而自己的 parse 是「被」父类的 run 调用的——好莱坞原则的直观体验。新增 XMLImporter 时只要实现 parse 一个方法,验证和汇总自动继承;而骨架本身(先验证后解析)在父类只有一份,改流程不用碰任何子类。
常见坑
- 子类覆写模板方法本身:骨架被子类改掉,「固定流程」名存实亡;团队约定 + code review 守住,模板方法只被子类「使用」不被「改写」。
- 抽象步骤过多:一个模板方法里挖了七八个抽象方法,子类实现到怀疑人生;步骤多时考虑拆成策略组合,或分层模板。
- 钩子滥用成隐式开关:一堆空钩子让子类的行为依赖「覆写了哪几个」的隐式组合,可读性崩塌;钩子要有明确语义和文档。
- 父类依赖子类的具体类型:父类里 instanceof 某个子类做分支,层次彻底颠倒;父类只能依赖抽象步骤。
- 流程本身也在变还硬用模板方法:连骨架顺序都因场景而异的流程,该用建造者或流水线组合,模板方法的前提是骨架稳定。
小结
模板方法在父类固化算法骨架,子类填充可变步骤,控制权在父类(好莱坞原则);骨架稳定、环节可变是它的适用签名。下一章是 GoF 里公认的硬骨头:访问者模式。