硬编码逻辑必须抽离,因if-else中直接调用操作会导致主流程被频繁修改;命令模式将“做什么”封装为可替换、可记录、可回滚的对象,command接口须定义execute()和undo(),依赖需构造时注入并校验,invoker只依赖接口不硬编码类名,命令实例应一次执行即弃用,避免状态泄漏。

硬编码逻辑为什么必须抽离?
当 if-else 块里直接调用 sendEmail()、logToFile()、updateCache(),且分支越来越多时,每次加个新操作就得改主流程——这不是扩展,是破坏。命令模式不解决“要不要做”,而是把“做什么”变成可替换、可记录、可回滚的对象。
Command 接口定义要极简,但必须含执行与撤销
PHP 中接口不能带默认实现,所以 execute() 和 undo() 都得强制声明。别为了省事只留 execute(),否则后续加事务、重试、补偿时会卡死在调用方。
常见错误:把参数塞进构造函数后不做校验,导致 execute() 运行时报 Undefined property 或 Call to a member function on null。
-
Command接口只定义execute(): void和undo(): void - 每个具体命令类(如
SendEmailCommand)在__construct()中接收必要依赖(如$mailer、$user),并做非空/类型断言 - 避免在
execute()里 new 其他服务——依赖应由容器或调用方注入
Invoker 不该知道具体 Command 类名
硬编码的典型残留:在订单处理逻辑里写 new SendEmailCommand(...) 或 new UpdateInventoryCommand(...)。这又绕回去了。
正确做法是让 Invoker 持有 Command 接口实例,由上层决定传哪个。比如 Laravel 的事件监听器、Symfony 的 Messenger 处理器,本质都是 Invoker。
- Invoker 类只依赖
Command接口,不 import 任何具体命令类 - 命令实例化交给工厂或 DI 容器,例如:
$command = $container->get(SendEmailCommand::class) - 如果需动态选命令(如根据
$action = 'refund'),用简单映射数组:$map = ['refund' => RefundCommand::class],再通过容器解析,而非new $class
PHP 中命令对象要小心生命周期和状态泄漏
命令不是无状态函数,它可能持有数据库连接、临时文件句柄、或未清理的缓存引用。尤其在 CLI 脚本或长生命周期 Swoole 进程中,重复复用同一命令实例会导致 Cannot use object of type ... as array 或数据污染。
- 命令对象设计为「一次执行,即弃用」,不要在 Invoker 中复用实例
- 避免在命令属性中存大对象(如整个
$request或$pdo),改用 ID 或轻量 DTO 传参 - 若需共享上下文(如事务 ID、trace_id),用独立的
Context对象传入execute(Context $ctx),而非挂到命令属性上
execute() 里写了个 if ($this->type === 'sms')。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











