尧图精选

DesignPatternsPHP 命令模式(Command Pattern)实战:用 execute/undo 解耦请求发送者与执行者

🕒 发布时间:2026/10/1 9:54:13 📁 来源:尧图网络
示例工程教程【免费下载链接】DesignPatternsPHPSample code for several design patterns in PHP 8.x项目地址https://gitcode.com/gh_mirrors/de/DesignPatternsPHP点击查看免费下载本篇技术指南以 DesignPatternsPHP 仓库中Behavioral/Command模块的官方文档为主体结合 PHP 8.x 源码与 PHPUnit 测试完整讲解命令模式的目的封装调用与解耦、核心角色Invoker、Receiver、Command、UndoableCommand的协作关系、可撤销undo扩展能力以及文本编辑器、大型 CLI 工具等真实应用场景。读完本文你将掌握如何用命令对象把客户端的请求与接收者的具体业务彻底解耦并实现可撤销、可聚合的命令栈。一、模式目的封装调用实现解耦命令模式Command Pattern的核心目的可以概括为两句话封装调用encapsulate invocation与解耦decoupling。在传统写法中调用方直接依赖具体业务对象的方法请求的发起方与执行方紧紧耦合在一起而命令模式在中间插入了一层命令对象将调用某个方法这个动作本身封装成一个对象。从仓库文档 Behavioral/Command/README.rst 的定义看该模式围绕三个角色展开Invoker调用者只负责持有命令并触发执行Receiver接收者真正执行业务逻辑的对象Command命令把对 Receiver 的方法调用委托出去并向外部暴露统一的方法execute()。其结果是Invoker 只需要知道调用execute()就能处理客户端的命令而 Receiver 与 Invoker 之间完全解耦。命令模式在设计层面还提供了第二个维度——undo()用于撤销execute()产生的效果同时命令对象可以聚合aggregate通过组合composition over inheritance以极少的复制粘贴拼出更复杂的命令。二、三个核心角色与统一 execute 契约1. Command 接口命令的统一契约仓库中所有命令的顶层抽象是 Command.php这是一个只含一个方法的接口?php declare(strict_types1); namespace DesignPatterns\Behavioral\Command; interface Command { /** * this is the most important method in the Command pattern, * The Receiver goes in the constructor. */ public function execute(); }execute()是整个命令模式最重要的方法。源码注释特别指出Receiver 通过构造函数注入命令对象——具体命令在构造时接收依赖执行时把工作委托给它。2. UndoableCommand 接口可撤销扩展为了支持撤销能力仓库提供了 UndoableCommand.php它在Command基础上扩展了undo()方法?php declare(strict_types1); namespace DesignPatterns\Behavioral\Command; interface UndoableCommand extends Command { /** * This method is used to undo change made by command execution */ public function undo(); }这是一个典型的接口继承设计并不是所有命令都需要撤销例如只读查询因此把撤销能力单独抽成一个子接口需要回滚的命令才实现它。这是组合优于继承思想在接口层面的体现。3. Receiver具体业务服务的封装Receiver.php 是一个有自己契约的具体服务类维护两个私有状态private bool $enableDate false;——是否在输出中附加日期private array $output [];——历史输出内容列表。它提供四个方法方法作用write(string $str)写入内容若enableDate为 true则在字符串末尾追加[Y-m-d]格式的当前日期getOutput(): string用换行符join所有历史输出enableDate()开启日期显示disableDate()关闭日期显示注意write()中的日期拼接逻辑$str . [ . date(Y-m-d) . ];这是后面AddMessageDateCommand撤销功能的基础——undo()的本质就是调用disableDate()关闭开关让后续输出恢复原样。三、具体命令与 Invoker 的源码实现1. HelloCommand最简命令HelloCommand.php 实现了Command接口通过构造函数注入Receiverexecute()直接委托给接收者输出Hello World?php declare(strict_types1); namespace DesignPatterns\Behavioral\Command; class HelloCommand implements Command { /** * Each concrete command is built with different receivers. * There can be one, many or completely no receivers, but there can be other commands in the parameters */ public function __construct(private Receiver $output) { } public function execute() { // sometimes, there is no receiver and this is the command which does all the work $this-output-write(Hello World); } }源码注释透露了命令对象构造的灵活性一个具体命令可以接收一个、多个甚至完全没有接收者参数里也可以是其他命令。当没有接收者时命令自己完成全部工作当参数是其他命令时就实现了命令的聚合。2. AddMessageDateCommand可撤销命令AddMessageDateCommand.php 实现UndoableCommand是仓库演示撤销机制的关键示例?php declare(strict_types1); namespace DesignPatterns\Behavioral\Command; class AddMessageDateCommand implements UndoableCommand { public function __construct(private Receiver $output) { } public function execute() { $this-output-enableDate(); } public function undo() { $this-output-disableDate(); } }execute()调用enableDate()开启消息日期显示undo()调用disableDate()关闭消息日期显示。同一份Receiver状态被两个方法对称地开启/关闭这就是可撤销最直白的实现——撤销不是删除历史而是把接收者的状态回滚到执行前的样子。3. Invoker只知道 execute 的调用者Invoker.php 是整个模式的入口大脑它只持有Command类型的命令对象通过setCommand()订阅命令、通过run()触发执行?php declare(strict_types1); namespace DesignPatterns\Behavioral\Command; class Invoker { private Command $command; public function setCommand(Command $cmd) { $this-command $cmd; } public function run() { $this-command-execute(); } }源码注释强调了两个设计要点setCommand()这类订阅方法在真实系统中可以有多种形态——**命令栈stack、命令列表list、固定集合fixed set**等run()对任何命令一视同仁——Invoker 不关心具体命令是什么无论执行HelloCommand还是AddMessageDateCommand它调用的都是同一个execute()。这正是请求发送者与执行者解耦的落地体现。四、测试验证命令执行与撤销的完整链路仓库用 PHPUnit 为命令模式提供了两个测试文件可以从可运行代码层面验证上述设计。1. 基础调用测试Tests/CommandTest.php 验证最基本的调用链路?php declare(strict_types1); namespace DesignPatterns\Behavioral\Command\Tests; use DesignPatterns\Behavioral\Command\HelloCommand; use DesignPatterns\Behavioral\Command\Invoker; use DesignPatterns\Behavioral\Command\Receiver; use PHPUnit\Framework\TestCase; class CommandTest extends TestCase { public function testInvocation() { $invoker new Invoker(); $receiver new Receiver(); $invoker-setCommand(new HelloCommand($receiver)); $invoker-run(); $this-assertSame(Hello World, $receiver-getOutput()); } }调用链为Invoker::setCommand()→Invoker::run()→Command::execute()→Receiver::write()最终断言接收者输出Hello World。2. 可撤销命令测试Tests/UndoableCommandTest.php 则完整演示了执行 → 附加日期 → 撤销 → 恢复的完整生命周期?php declare(strict_types1); namespace DesignPatterns\Behavioral\Command\Tests; use DesignPatterns\Behavioral\Command\AddMessageDateCommand; use DesignPatterns\Behavioral\Command\HelloCommand; use DesignPatterns\Behavioral\Command\Invoker; use DesignPatterns\Behavioral\Command\Receiver; use PHPUnit\Framework\TestCase; class UndoableCommandTest extends TestCase { public function testInvocation() { $invoker new Invoker(); $receiver new Receiver(); $invoker-setCommand(new HelloCommand($receiver)); $invoker-run(); $this-assertSame(Hello World, $receiver-getOutput()); $messageDateCommand new AddMessageDateCommand($receiver); $messageDateCommand-execute(); $invoker-run(); $this-assertSame(Hello World\nHello World [ . date(Y-m-d) . ], $receiver-getOutput()); $messageDateCommand-undo(); $invoker-run(); $this-assertSame(Hello World\nHello World [ . date(Y-m-d) . ]\nHello World, $receiver-getOutput()); } }该测试的三次断言清晰地展示了状态变迁第一次run()未开启日期输出Hello World执行AddMessageDateCommand后再次run()输出变为Hello World [2026-09-30]日期格式Y-m-d实际值随运行日期变化调用undo()后再run()日期功能被关闭输出恢复为纯文本Hello World。值得注意测试中AddMessageDateCommand的execute()/undo()是直接调用的而HelloCommand通过Invoker 调用——这正说明命令对象既可以交给 Invoker 统一触发也可以由业务代码直接操纵如撤销栈的管理者。五、真实应用场景从文本编辑器到大型 CLI仓库文档列举了两个极具代表性的实战场景1. 文本编辑器命令的撤销与堆栈文本编辑器是命令模式的经典案例——所有事件都是命令输入字符、删除、复制、粘贴等操作各自封装为命令对象它们可以被撤销undo、被堆叠stack起来形成操作历史、被保存save以便恢复会话。用户按下CtrlZ时系统实际上是在弹出命令栈并逐个调用undo()——这与本仓库UndoableCommand接口的设计完全一致。2. 大型 CLI 工具子命令分发大型 CLI 工具如 vagrant使用子命令把各类任务分发到不同模块中每个子命令都可以用命令模式实现CLI 的入口解析参数后把请求包装成对应命令交给 Invoker 执行。例如vagrant up、vagrant destroy、vagrant ssh各自对应一个命令类彼此独立、可独立测试、可单独替换——这正是命令模式每个操作一个类带来的可维护性收益。六、运行与测试指南本仓库要求PHP 8.0见根目录 composer.json同时依赖psr/http-message与ext-json开发环境使用 PHPUnit 9 与 PHP_CodeSniffer、Psalm 作为质量保障工具。若想在本地验证命令模式的实现安装依赖composer install运行命令模式相关测试仓库 autoload 已将Behavioral等目录映射为 classmapvendor/bin/phpunit Behavioral/Command/Tests/CommandTest.php Behavioral/Command/Tests/UndoableCommandTest.php两条测试用例通过即说明从Invoker 触发执行到UndoableCommand 撤销恢复的整条链路行为符合预期。七、小结命令模式的三大价值结合仓库文档与源码命令模式在 DesignPatternsPHP 中体现的三大价值可以总结为解耦Invoker 只知道execute()Receiver 只负责业务两者通过命令对象中转互不感知可撤销通过UndoableCommand的undo()对称回滚接收者状态支撑编辑器等场景的操作历史可组合命令对象可聚合其他命令用组合而非继承的方式低成本构建复杂命令配合命令栈、命令列表等容器形态即可扩展出宏命令、批量任务等更高级用法。从源码结构看Behavioral/Command目录是理解命令模式的最小可运行范本5 个核心 PHP 文件 2 个测试文件即完整覆盖了模式的全部角色与生命周期非常适合作为学习命令模式的起点。赞分享示例工程教程【免费下载链接】DesignPatternsPHPSample code for several design patterns in PHP 8.x项目地址https://gitcode.com/gh_mirrors/de/DesignPatternsPHP点击查看免费下载相关推荐DesignPatternsPHP 中介者模式Mediator Pattern实战指南用 PHP 8.x 解耦多组件协作DesignPatternsPHP 中介者模式Mediator Pattern实战指南用 PHP 8.x 解耦多组件协作 中介者模式Mediator P示例工程教程Unity3DTraining 命令模式Command Pattern实战解析请求封装、队列编排与撤销重做Unity3DTraining 命令模式Command Pattern实战解析请求封装、队列编排与撤销重做 导读 本文以 DesignPatterns/C示例工程Kotlin命令模式实战OrderCommand请求封装与执行控制Kotlin命令模式实战OrderCommand请求封装与执行控制 命令模式是设计模式中的重要概念它通过将请求封装为对象来实现请求者和执行者的解耦。在Kot示例工程上一篇5分钟掌握BetterNCM网易云音乐插件管理器的终极安装指南下一篇终极指南如何用OpenCore Legacy Patcher让老款Mac焕发新生创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →