模板方法模式通过抽象类定义算法骨架,用final模板方法强制流程稳定、abstract方法确保子类补全关键行为,钩子方法支持可选扩展,适用于流程固定而步骤实现多变的场景。
在抽象类中定义算法骨架,是模板方法模式最典型、最可靠的落地方式。它不靠约定,而靠语言机制强制流程稳定、步骤可插拔。
核心结构:抽象类 + final 模板方法 + abstract 钩子
必须用抽象类,因为只有它能同时做到两件事:声明不可跳过的执行顺序,又强制子类补全关键行为。接口不能写流程逻辑,普通类又无法约束实现。
- 模板方法本身加 final,比如
public final function execute(),防止子类绕过骨架直接调用某一步 - 可变步骤声明为 abstract protected,例如
abstract protected function validate();,确保子类必须提供具体逻辑 - 钩子方法(如
protected function afterProcess() {})留空或给默认实现,子类按需覆盖,不强制
模板方法里只编排,不干活
它只负责“什么时候调什么”,不负责“怎么调”或“调什么具体内容”。真正有业务含义的操作,必须拆成独立方法交给子类。
- ✅ 正确:在
execute()中依次调用$this->validate(); $this->fetch(); $this->save(); - ❌ 错误:把
$user = new User(); $user->name = 'test';这类具体构造逻辑写死在模板方法里 - 兜底逻辑可以放,比如统一 try-catch、日志标记开始/结束、返回值标准化包装
识别真·适合场景:流程不变,细节可换
不是所有“先A后B”的代码都该上模板方法。关键看变化维度是否落在步骤实现上,而不是整个流程切换。
- 适合:用户注册流程总是「校验→存库→发邮件→记录日志」,只是不同业务对「校验规则」「邮件模板」「日志字段」有差异
- 不适合:支付环节要在微信、支付宝、PayPal 之间动态切换——这属于流程级替换,更适合策略模式
- 一个信号:多个子类的
execute()方法体几乎一样,仅中间几行不同,就是模板方法的明确提示
避坑要点:访问控制与副作用隔离
细节松动,容易让整个骨架失效。几个硬性约束不能妥协:
- abstract 方法必须是 protected,不是 public;否则外部可直接调用,破坏封装和流程完整性
- 抽象类里避免写
new PDO()、file_get_contents()这类带副作用的操作,否则骨架一跑就卡住,无法复用 - 依赖对象(如 MailerInterface)通过构造器或 setter 注入,别指望抽象方法参数自动解析,容器进不来
- 子类未实现全部 abstract 方法会报 fatal error——这不是 bug,是设计在起作用










