模板方法模式必须用抽象类,因其能通过abstract方法强制子类实现关键步骤、final模板方法锁死流程顺序,而trait和普通类无法提供编译期约束与多态分发保障。

直接用抽象类 + final 模板方法 + abstract 步骤方法,是 PHP 里最干净、最可控的实现方式。别用普通父类加条件判断,那不是模板方法,是逻辑硬编码。
为什么必须用 abstract class 而不是 trait 或普通 class
模板方法的核心是「骨架不可变、步骤可替换」,这依赖运行时继承链和多态分发。trait 只能复用行为,不能约束子类必须实现某方法;普通 class 没有强制实现机制,子类漏写某个步骤也不会报错。
-
abstract类能用abstract方法强制子类覆盖关键步骤,PHP 解析期就报错,不等到运行时才发现逻辑缺失 - 子类必须
extends,确保调用$this->templateMethod()时走的是父类定义的顺序,不会被意外绕过 -
final public function templateMethod()是防篡改底线——子类连重写入口都没有,彻底锁死流程结构
模板方法里该放什么、不该放什么
模板方法本身只做三件事:按序调用步骤、处理公共异常兜底、返回统一结果。任何业务细节、外部依赖、耗时操作都得剥离出去。
- ✅ 允许:
$this->validateInput()、$this->doProcess()、$this->logResult()这类受控的 protected 方法调用 - ❌ 禁止:在
templateMethod()里直接写file_get_contents()、DB::table()->insert()或 new 一个第三方 SDK 实例 - ⚠️ 钩子方法(如
beforeExecute())要用protected+ 空实现,子类按需重写;但别让它承担主干职责,否则骨架就松了
Laravel 项目里要不要往 Command/Controller 塞模板方法
不要。框架已有更合适的扩展机制,硬套模板方法反而增加耦合和测试成本。
立即学习“PHP免费学习笔记(深入)”;
- Command 的
handle()是入口点,不是算法骨架——它本该调用ReportService、NotificationSender这类可独立测试的服务,而不是自己变成抽象基类 - 你没法在子类里自动注入
MailerInterface到abstract protected function sendEmail()的参数里,Laravel 容器不解析抽象方法参数 - 测试时,你得为每个子类单独 mock 步骤,而用 service + interface 方式,只需 mock 一次接口,所有用到它的地方都生效
- 真正适合模板方法的场景是:多个报表生成器、多种支付回调处理器、不同渠道的消息组装逻辑——它们有强共性流程,且不依赖 Laravel 生命周期
Rector 能不能自动帮你转成模板方法
可以识别重复流程并建议重构,但不能全自动落地。它能发现「多个类里都有 fetch→transform→output 三步」这类模式,生成抽象类草稿,但以下必须人工确认:
- 哪些步骤真该抽象(比如
fetch()),哪些只是命名相似但语义不同(比如一个transform()是数组映射,另一个是 XML 解析) - 钩子方法要不要加默认实现?Rector 不会猜业务意图
- 原有类是否已存在其他继承关系?PHP 不支持多继承,强行抽成 abstract class 可能破坏现有结构
- 最终生成的
final public function execute()是否覆盖了所有调用入口?漏掉一个 public 方法,骨架就形同虚设
最容易被忽略的一点:模板方法不是为“看起来整洁”而存在,它是为防止未来有人在子类里删掉某一步、或调换顺序而设的防线。所以 final 和 abstract 不是语法装饰,是契约声明——没这两样,就不算真正落地了模板方法模式。



















