概述
命令模式(Command Pattern)是一种行为型设计模式,其核心思想是将请求封装为独立的命令对象,使请求发送者与接收者解耦。命令对象包含执行请求所需的所有信息(如接收者、参数),支持请求的排队、日志记录、撤销/重做等功能,同时允许动态添加或修改命令,提升系统的灵活性和可扩展性。
该模式遵循“单一职责原则”和“开放-封闭原则”,命令发送者无需知晓接收者的具体实现,仅需调用命令对象的执行方法,接收者专注于核心业务逻辑,命令对象则负责请求的封装与转发,使系统各模块职责边界清晰。
核心价值
| 核心要点 | 详细说明 |
|---|---|
| 解耦请求关系 | 命令发送者与接收者完全解耦,发送者无需知道接收者的类名、方法或参数,仅通过命令对象交互 |
| 支持请求扩展 | 新增命令时无需修改发送者和接收者代码,仅需新增命令类,符合“开放-封闭原则” |
| 实现请求管理 | 可对命令进行排队(如任务队列)、日志记录(如操作审计)、撤销/重做(如编辑器操作)等管理 |
| 统一请求接口 | 所有命令实现统一接口,发送者可通过相同方式调用不同命令,简化代码逻辑 |
| 支持批量执行 | 可组合多个命令为复合命令(如事务操作),实现批量执行或回滚 |
适用场景
| 场景类型 | 具体说明 |
|---|---|
| 请求排队与异步执行 | 如任务队列、异步邮件发送、定时任务调度等,需将请求封装后延迟执行或批量处理 |
| 操作日志与审计 | 如后台管理系统的操作记录、数据库事务日志、敏感操作审计等,需记录命令执行轨迹 |
| 撤销/重做功能 | 如文本编辑器、表单操作、绘图工具等,需支持用户操作的回滚与重复执行 |
| 解耦复杂交互 | 如电商订单创建(涉及库存、支付、物流等多个接收者),需通过命令协调多模块交互 |
| 框架扩展机制 | 如框架的钩子函数、事件驱动系统、插件机制等,需通过命令封装扩展逻辑 |
注意事项
| 注意点 | 具体说明 |
|---|---|
| 避免过度设计 | 若请求简单且无需扩展(如单一接收者、无排队/日志需求),直接调用方法更高效,无需引入命令模式 |
| 控制命令类数量 | 频繁新增简单命令可能导致类数量膨胀,可通过复合命令或参数化命令合并相似逻辑 |
| 明确命令职责 | 命令类仅负责封装请求和调用接收者方法,不包含核心业务逻辑,业务逻辑应保留在接收者中 |
| 处理撤销/重做复杂性 | 实现撤销/重做需记录命令执行状态或反向操作,复杂场景下需合理设计状态存储机制,避免性能损耗 |
| 避免命令与接收者强耦合 | 命令类应通过构造函数注入接收者,而非在内部直接实例化,提升灵活性和可测试性 |
完整代码
以“电商订单操作”为业务场景,包含“创建订单”“取消订单”“退款”三个命令,接收者为库存服务、支付服务、订单服务,通过命令模式实现请求封装与解耦,并支持操作日志记录。
<?php
declare(strict_types = 1);
/**
* 命令接口:定义命令的统一规范
*/
interface CommandInterface
{
/**
* 执行命令
* @return string 执行结果
*/
public function execute(): string;
/**
* 获取命令描述(用于日志记录)
* @return string
*/
public function getDescription(): string;
}
/**
* 接收者1:库存服务(处理库存相关操作)
*/
class StockService
{
/**
* 扣减库存
* @param string $sku 商品SKU
* @param int $quantity 数量
* @return string
*/
public function deductStock(string $sku, int $quantity): string
{
return "库存服务:扣减商品【{$sku}】库存{$quantity}件";
}
/**
* 恢复库存
* @param string $sku 商品SKU
* @param int $quantity 数量
* @return string
*/
public function restoreStock(string $sku, int $quantity): string
{
return "库存服务:恢复商品【{$sku}】库存{$quantity}件";
}
}
/**
* 接收者2:支付服务(处理支付相关操作)
*/
class PaymentService
{
/**
* 发起支付
* @param string $orderNo 订单号
* @param float $amount 金额
* @return string
*/
public function pay(string $orderNo, float $amount): string
{
return "支付服务:订单【{$orderNo}】支付{$amount}元成功";
}
/**
* 发起退款
* @param string $orderNo 订单号
* @param float $amount 金额
* @return string
*/
public function refund(string $orderNo, float $amount): string
{
return "支付服务:订单【{$orderNo}】退款{$amount}元成功";
}
}
/**
* 接收者3:订单服务(处理订单相关操作)
*/
class OrderService
{
/**
* 创建订单
* @param string $orderNo 订单号
* @param array $items 商品项
* @return string
*/
public function createOrder(string $orderNo, array $items): string
{
$itemStr = implode(',', array_column($items, 'name'));
return "订单服务:创建订单【{$orderNo}】,商品:{$itemStr}";
}
/**
* 取消订单
* @param string $orderNo 订单号
* @return string
*/
public function cancelOrder(string $orderNo): string
{
return "订单服务:取消订单【{$orderNo}】";
}
}
/**
* 命令1:创建订单命令
*/
class CreateOrderCommand implements CommandInterface
{
private OrderService $orderService;
private StockService $stockService;
private PaymentService $paymentService;
private string $orderNo;
private array $items;
private float $amount;
public function __construct(
OrderService $orderService,
StockService $stockService,
PaymentService $paymentService,
string $orderNo,
array $items,
float $amount
) {
$this->orderService = $orderService;
$this->stockService = $stockService;
$this->paymentService = $paymentService;
$this->orderNo = $orderNo;
$this->items = $items;
$this->amount = $amount;
}
public function execute(): string
{
// 调用多个接收者方法,封装复杂请求
$result[] = $this->orderService->createOrder($this->orderNo, $this->items);
foreach ($this->items as $item) {
$result[] = $this->stockService->deductStock($item['sku'], $item['quantity']);
}
$result[] = $this->paymentService->pay($this->orderNo, $this->amount);
return implode(';', $result);
}
public function getDescription(): string
{
return "创建订单【{$this->orderNo}】,金额{$this->amount}元";
}
}
/**
* 命令2:取消订单命令
*/
class CancelOrderCommand implements CommandInterface
{
private OrderService $orderService;
private StockService $stockService;
private string $orderNo;
private array $items;
public function __construct(
OrderService $orderService,
StockService $stockService,
string $orderNo,
array $items
) {
$this->orderService = $orderService;
$this->stockService = $stockService;
$this->orderNo = $orderNo;
$this->items = $items;
}
public function execute(): string
{
$result[] = $this->orderService->cancelOrder($this->orderNo);
foreach ($this->items as $item) {
$result[] = $this->stockService->restoreStock($item['sku'], $item['quantity']);
}
return implode(';', $result);
}
public function getDescription(): string
{
return "取消订单【{$this->orderNo}】";
}
}
/**
* 命令3:退款命令
*/
class RefundOrderCommand implements CommandInterface
{
private PaymentService $paymentService;
private string $orderNo;
private float $amount;
public function __construct(PaymentService $paymentService, string $orderNo, float $amount)
{
$this->paymentService = $paymentService;
$this->orderNo = $orderNo;
$this->amount = $amount;
}
public function execute(): string
{
return $this->paymentService->refund($this->orderNo, $this->amount);
}
public function getDescription(): string
{
return "订单【{$this->orderNo}】退款{$this->amount}元";
}
}
/**
* 命令调用者:负责触发命令执行和日志记录
*/
class CommandInvoker
{
/**
* 执行命令并记录日志
* @param CommandInterface $command
* @return string
*/
public function invoke(CommandInterface $command): string
{
// 执行命令
$executeResult = $command->execute();
// 记录操作日志(实际可写入数据库或文件)
$log = $this->recordLog($command);
return "执行结果:{$executeResult};日志记录:{$log}";
}
/**
* 记录命令日志
* @param CommandInterface $command
* @return string
*/
private function recordLog(CommandInterface $command): string
{
$time = date('Y-m-d H:i:s');
$user = 'admin'; // 模拟当前操作用户
return "【{$time}】用户【{$user}】执行操作:{$command->getDescription()}";
}
}
// 测试代码
try {
// 1. 初始化接收者
$orderService = new OrderService();
$stockService = new StockService();
$paymentService = new PaymentService();
// 2. 初始化命令调用者
$invoker = new CommandInvoker();
// 3. 测试创建订单命令
echo "=== 测试创建订单 ===" . PHP_EOL;
$items = [
['sku' => 'IPHONE15', 'name' => 'iPhone 15', 'quantity' => 1],
['sku' => 'AIRPODS', 'name' => 'AirPods Pro', 'quantity' => 1]
];
$createCommand = new CreateOrderCommand(
$orderService,
$stockService,
$paymentService,
'OD20260106001',
$items,
12998.0
);
echo $invoker->invoke($createCommand) . PHP_EOL . PHP_EOL;
// 4. 测试取消订单命令
echo "=== 测试取消订单 ===" . PHP_EOL;
$cancelCommand = new CancelOrderCommand(
$orderService,
$stockService,
'OD20260106001',
$items
);
echo $invoker->invoke($cancelCommand) . PHP_EOL . PHP_EOL;
// 5. 测试退款命令
echo "=== 测试退款 ===" . PHP_EOL;
$refundCommand = new RefundOrderCommand(
$paymentService,
'OD20260106001',
12998.0
);
echo $invoker->invoke($refundCommand) . PHP_EOL;
} catch (\Exception $e) {
echo "错误:" . $e->getMessage();
}
核心结构拆解
命令模式的核心由“命令接口”“具体命令”“接收者”和“调用者”四部分组成,结构清晰且职责明确:
| 结构组成 | 详细说明 |
|---|---|
| 命令接口(CommandInterface) | 定义命令的统一规范(如 execute getDescription),确保所有命令实现一致的调用方式 |
| 具体命令(CreateOrderCommand 等) | 实现命令接口,持有接收者引用,封装请求参数,在 execute 方法中调用接收者的具体方法 |
| 接收者(OrderService、StockService 等) | 封装核心业务逻辑(如订单创建、库存扣减),提供命令执行所需的方法,不依赖命令类 |
| 调用者(CommandInvoker) | 负责触发命令执行,可添加日志记录、权限校验等通用逻辑,与具体命令和接收者解耦 |
关键优化点
| 优化方向 | 详细说明 |
|---|---|
| 依赖注入解耦 | 具体命令通过构造函数注入接收者,而非内部实例化,提升命令的灵活性和可测试性 |
| 统一命令接口 | 命令接口明确定义 execute 方法,确保调用者可通过相同方式调用所有命令,简化代码 |
| 日志记录封装 | 调用者封装日志记录逻辑,所有命令执行时自动记录,避免重复编码 |
| 支持多接收者协调 | 单个命令可调用多个接收者方法(如创建订单命令协调订单、库存、支付服务),实现复杂业务流程封装 |
| 命令描述标准化 | 命令类实现 getDescription 方法,统一日志记录格式,提升日志可读性 |
常见错误规避
| 错误类型 | 问题描述及解决方案 |
|---|---|
| 命令类包含业务逻辑 | 问题:将核心业务逻辑(如库存计算、支付校验)写入命令类,导致命令与业务耦合,接收者失去独立性。 解决:命令类仅负责调用接收者方法,业务逻辑必须保留在接收者中 |
| 调用者直接依赖具体命令 | 问题:调用者代码直接引用具体命令类(如 new CreateOrderCommand()),导致切换命令时需修改调用者代码。解决:调用者仅依赖 CommandInterface,通过外部注入命令实例,降低耦合 |
| 接收者在命令中硬编码 | 问题:命令类内部直接 new 接收者(如 $this->orderService = new OrderService()),导致命令与接收者强耦合。解决:通过构造函数注入接收者,支持接收者的替换和模拟测试 |
| 忽略命令的可扩展性 | 问题:简单命令未实现统一接口,新增命令时需修改调用者逻辑,违背开放-封闭原则。 解决:所有命令必须实现 CommandInterface,确保扩展时无需修改原有代码 |
| 撤销/重做设计不当 | 问题:未记录命令执行状态,导致无法实现撤销/重做,或状态存储过多导致性能损耗。 解决:仅在需要撤销/重做的场景下设计状态存储,采用轻量级存储方案(如记录关键参数而非完整对象) |
扩展场景示例(新增命令无需修改原有代码)
若业务新增“修改订单地址”命令,仅需新增命令类,无需修改接收者、调用者和已有命令代码:
// 新增接收者方法(若需扩展接收者功能,无需修改命令相关代码)
class OrderService
{
// 原有方法...
/**
* 修改订单地址
* @param string $orderNo 订单号
* @param string $newAddress 新地址
* @return string
*/
public function updateOrderAddress(string $orderNo, string $newAddress): string
{
return "订单服务:修改订单【{$orderNo}】收货地址为【{$newAddress}】";
}
}
// 新增命令:修改订单地址命令
class UpdateOrderAddressCommand implements CommandInterface
{
private OrderService $orderService;
private string $orderNo;
private string $oldAddress;
private string $newAddress;
public function __construct(OrderService $orderService, string $orderNo, string $oldAddress, string $newAddress)
{
$this->orderService = $orderService;
$this->orderNo = $orderNo;
$this->oldAddress = $oldAddress;
$this->newAddress = $newAddress;
}
public function execute(): string
{
return $this->orderService->updateOrderAddress($this->orderNo, $this->newAddress);
}
public function getDescription(): string
{
return "修改订单【{$this->orderNo}】地址,从【{$this->oldAddress}】改为【{$this->newAddress}】";
}
/**
* 扩展:实现撤销功能
* @return string
*/
public function undo(): string
{
$result = $this->orderService->updateOrderAddress($this->orderNo, $this->oldAddress);
return "撤销修改:{$result}";
}
}
// 测试新增命令
echo "=== 测试修改订单地址 ===" . PHP_EOL;
$updateCommand = new UpdateOrderAddressCommand(
$orderService,
'OD20260106001',
'北京市朝阳区',
'上海市浦东新区'
);
echo $invoker->invoke($updateCommand) . PHP_EOL;
// 测试撤销功能
echo "=== 测试撤销修改 ===" . PHP_EOL;
echo $updateCommand->undo() . PHP_EOL;
实际应用场景
| 场景分类 | 具体说明 |
|---|---|
| 任务队列系统 | 如 Laravel 的队列系统,将“发送邮件”“生成报表”“推送通知”封装为命令,放入队列异步执行 |
| 后台操作审计 | 如 CMS 系统的文章发布、用户管理、权限修改等操作,通过命令记录操作人、时间、内容,便于审计追溯 |
| 编辑器撤销/重做 | 如 Markdown 编辑器的文本输入、格式修改、图片插入等操作,通过命令栈记录操作,支持撤销和重做 |
| 电商订单流程 | 如订单创建、取消、退款、发货等操作,通过命令协调库存、支付、物流、通知等多个模块 |
| 框架事件驱动 | 如 Laravel 的事件系统,事件监听器本质是命令,通过触发事件执行多个监听器(命令),实现解耦扩展 |
命令模式与其他模式对比
| 对比维度 | 命令模式 | 策略模式 | 职责链模式 |
|---|---|---|---|
| 核心目标 | 封装请求,解耦发送者与接收者,支持请求管理 | 封装可替换算法,动态切换执行逻辑 | 请求在链中流转,找到对应处理者 |
| 对象关系 | 调用者→命令→接收者(命令持有接收者引用) | 上下文→策略接口→具体策略(上下文依赖策略) | 节点间单向依赖,形成链式结构 |
| 执行逻辑 | 调用者触发命令,命令调用接收者方法 | 上下文选择策略,执行单一策略逻辑 | 请求沿链传递,单个或多个节点处理 |
| 适用场景 | 请求排队、日志、撤销/重做、多模块协调 | 算法动态切换、多规则选择 | 多节点分级处理、规则校验 |
| 核心差异 | 侧重“请求封装与管理” | 侧重“算法替换” | 侧重“请求流转与分发” |


