PHP 命令模式

概述

命令模式(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 的事件系统,事件监听器本质是命令,通过触发事件执行多个监听器(命令),实现解耦扩展

命令模式与其他模式对比

对比维度 命令模式 策略模式 职责链模式
核心目标 封装请求,解耦发送者与接收者,支持请求管理 封装可替换算法,动态切换执行逻辑 请求在链中流转,找到对应处理者
对象关系 调用者→命令→接收者(命令持有接收者引用) 上下文→策略接口→具体策略(上下文依赖策略) 节点间单向依赖,形成链式结构
执行逻辑 调用者触发命令,命令调用接收者方法 上下文选择策略,执行单一策略逻辑 请求沿链传递,单个或多个节点处理
适用场景 请求排队、日志、撤销/重做、多模块协调 算法动态切换、多规则选择 多节点分级处理、规则校验
核心差异 侧重“请求封装与管理” 侧重“算法替换” 侧重“请求流转与分发”