最稳妥的模板方法实现是抽象类+final公共模板方法+abstract/protected钩子方法;关键在于算法骨架固定且步骤可替换,抽象类能同时约束流程顺序与强制实现差异点。

直接结论:用抽象类 + final 的公共模板方法 + abstract 或 protected 钩子方法,是最稳妥、最不易误用的写法。不是所有“先A后B”的代码都适合模板方法,关键在「算法骨架必须固定、步骤实现可替换」。
为什么必须用 abstract class 而不是 interface 或普通 class
接口无法定义具体流程逻辑,普通类又无法强制子类实现关键步骤。只有抽象类能同时做到:声明不可跳过的骨架(final public function templateMethod()),又要求子类补全差异点(abstract protected function doValidate())。如果误用 interface,你只能定义一堆方法签名,但没人能保证调用顺序;如果用普通父类,子类可能绕过骨架直接调用某一步,破坏一致性。
常见错误现象:Fatal error: Class ConcreteClass contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods——说明子类没实现全部 abstract 方法,但这是设计意图,不是 bug。
- 抽象方法必须用
abstract protected,不能是public,否则子类可能被外部直接调用,破坏封装 - 模板方法本身必须加
final,否则子类重写它就等于废弃了整个模式 - 别在抽象类里做
new PDO()或file_get_contents()这类副作用操作,否则骨架一跑就卡住,复用性归零
templateMethod() 里该放什么,不该放什么
它只负责编排顺序和兜底逻辑,比如统一异常捕获、日志起始标记、返回值包装。真正干活的步骤(查库、校验、发邮件)必须拆成独立方法交给子类去实现。
典型反例:把 $user = $this->findUser($id); 写死在 templateMethod() 里——这会让所有子类被迫走同一套 DB 查询逻辑,失去扩展意义。
- 推荐结构:
$this->validate(); $this->fetchData(); $this->process(); $this->output(); - 允许有默认实现的钩子,比如
protected function beforeProcess() {},子类按需重写,不强制 - 涉及依赖注入(如
MailerInterface)时,不要试图让抽象方法参数自动解析;改用构造器传入或 setter 注入,否则 Laravel/Symfony 的容器无法介入
如何识别一个场景真适合模板方法,而不是策略模式
看变化维度:如果「整个流程不变,只是某几步实现不同」,选模板方法;如果「流程本身会切换(比如支付走微信 or 支付宝 or PayPal)」,那更适合策略模式。两者常被混淆,但本质区别很清晰——模板方法靠继承约束顺序,策略模式靠组合替换整套行为。
真实使用场景举例:生成 PDF 报告 vs Excel 报告。共性是「加载数据 → 渲染模板 → 输出文件」,差异只在渲染和输出环节。这时抽象出 AbstractReportGenerator 就非常自然;但如果要支持「异步生成」和「实时流式生成」两种完全不同的执行模型,那就该拆成两个策略类,而非硬塞进一个模板骨架。
- 容易踩的坑:把「登录」和「注册」强行塞进同一个模板类——它们表面都有“校验→存库→发通知”,但实际流程分支多、失败路径不同,属于伪共性
- 性能影响:模板方法本身无额外开销,但若钩子方法里做了 N+1 查询或未加缓存,问题会被骨架放大
- Laravel 项目中慎用:框架本身大量依赖 DI 和契约,硬套模板方法可能和
ServiceContainer解耦思路冲突
用 Rector 自动识别并重构重复流程代码
当你发现多个类里都有相似的三段式方法(比如 handle() 总是包含 validate() → execute() → notify()),Rector 可以帮你批量提取骨架。它不靠字符串匹配,而是分析 AST 中的方法调用序列和参数一致性。
配置要点:withRules([SetTemplateMethodRule::class]),然后运行 vendor/bin/rector process src/ --dry-run 预览改动。注意它不会自动判断哪些步骤该抽象、哪些该保留默认实现,这部分仍需人工确认。
- Rector 识别失败的典型原因:子类方法名不统一(一个叫
doCheck(),另一个叫runValidation()) - 它无法处理含条件分支的复杂流程(比如部分子类跳过第 2 步),这种得先人工规整再交给工具
- 重构后务必检查所有子类的
__construct()是否仍兼容——新增抽象类可能引入新依赖
最难的从来不是写出 abstract class 和 final function,而是准确判断「哪部分真的不变」。很多所谓“骨架”,上线后三个月就被加了 if 分支、try/catch 或临时绕过逻辑,结果模板方法变成空壳,只剩一层继承噪音。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











