本文目录导读:

是的,PHP 命令模式(Command Pattern)核心目的之一就是封装请求。
它将一个“请求”或“操作”(保存文章”、“发送邮件”)封装成一个独立的对象,这个对象包含了执行该操作所需的所有信息(调用者、参数等)。
下面通过一个类比和代码示例来详细说明。
核心思想:把“做什么”和“谁来做”分开
- 传统方式:你直接调用某个对象的方法。
$userRepository->save($userData),这里“请求”就是直接的方法调用,它紧耦合在调用方。 - 命令模式:你创建一个“命令对象”来表示这个保存操作,这个对象里封装了
$userRepository和$userData,调用方只需要执行$command->execute()即可,完全不知道内部是谁在干活、怎么干的。
为什么说它“封装请求”?三个关键点:
- 参数封装:命令对象将请求的所有参数(如
$userData)封装在自身属性中。 - 接收者封装:命令对象持有执行操作的具体对象(接收者,如
$userRepository),调用方无需知道接收者是谁。 - 执行入口统一:所有命令对象都有一个统一的执行入口(通常是
execute()方法),将复杂的调用逻辑简化。
代码示例(PHP)
假设有一个简单的“请求”:在系统中创建一个用户。
没有命令模式(直接调用)
<?php
class UserController {
public function store($request) {
// 直接实例化并调用,耦合度高
$repository = new UserRepository();
$mailer = new EmailService();
$repository->save($request['name'], $request['email']);
$mailer->sendWelcomeEmail($request['email']);
// 如果将来要添加日志、做撤销,这里必须改代码
}
}
使用命令模式(封装请求)
<?php
// --- 1. 命令接口 ---
interface Command {
public function execute(): void;
public function undo(): void; // 命令模式也常支持撤销
}
// --- 2. 具体命令:创建用户请求 ---
class CreateUserCommand implements Command {
// 封装了“请求”所需的所有信息(接收者 + 参数)
private UserRepository $repository;
private EmailService $mailer;
private array $userData;
public function __construct(UserRepository $repository, EmailService $mailer, array $userData) {
$this->repository = $repository;
$this->mailer = $mailer;
$this->userData = $userData; // 封装请求参数
}
public function execute(): void {
// 这里是真正执行“请求”的逻辑
$this->repository->save($this->userData['name'], $this->userData['email']);
$this->mailer->sendWelcomeEmail($this->userData['email']);
// 甚至可以在执行前后插入日志,而无需修改调用方
echo "用户创建请求已执行。\n";
}
public function undo(): void {
// 实现撤销逻辑,比如删除刚创建的用户
$this->repository->deleteByEmail($this->userData['email']);
echo "已撤销用户创建。\n";
}
}
// --- 3. 调用方(请求的发起者) ---
class UserController {
public function store(array $requestData): void {
// 1. 准备接收者
$repository = new UserRepository();
$mailer = new EmailService();
// 2. 创建命令对象(封装请求)
$command = new CreateUserCommand($repository, $mailer, $requestData);
// 3. 执行命令(不知道内部怎么实现的)
$command->execute();
// 未来可以轻松添加:$command->undo(); // 一键撤销
}
}
// --- 辅助类 ---
class UserRepository {
public function save(string $name, string $email): void {
echo "保存用户: $name ($email)\n";
}
public function deleteByEmail(string $email): void {
echo "删除用户: $email\n";
}
}
class EmailService {
public function sendWelcomeEmail(string $email): void {
echo "发送欢迎邮件至: $email\n";
}
}
// --- 使用 ---
$controller = new UserController();
$controller->store(['name' => '张三', 'email' => 'zhangsan@example.com']);
输出结果:
保存用户: 张三 (zhangsan@example.com)
发送欢迎邮件至: zhangsan@example.com
用户创建请求已执行。
封装请求带来的核心好处
- 解耦:调用方(如
UserController)不需要知道请求的具体执行者(UserRepository,EmailService)和组合逻辑。 - 可扩展:新增一个操作(如“批量创建用户”),只需新增一个
BatchCreateUserCommand,无需修改现有代码。 - 可实现队列、日志、撤销:
- 队列:可以将命令对象序列化后放入消息队列(如 RabbitMQ),稍后异步执行。
- 日志:在执行
execute()前后记录日志,用于审计或调试。 - 撤销:保存已执行的命令列表,通过调用每个命令的
undo()方法实现多步撤销。
- 宏命令:可以组合多个命令形成一个“宏命令”对象,一次执行完成一系列复杂请求。
典型的应用场景
- GUI 按钮操作:用户点击“保存”、“删除”按钮,每个按钮对应一个命令对象。
- 事务性操作:多个步骤需要一起成功或一起回滚,命令模式结合
undo()可以轻松实现。 - 请求队列:将客户端发来的请求封装成命令对象,放入队列中,由后台工作进程逐个执行。
- 框架中的实现:一些 PHP 框架(如 Laravel 的 Action 模式,或者自己实现的命令总线
Command Bus)本质上是命令模式思想的延伸。
| 问题 | 回答 |
|---|---|
| PHP命令模式封装请求吗? | 是的,核心功能就是封装请求。 |
| 封装了什么? | 封装的不仅仅是参数,还包括接收者(Receiver)以及如何调用接收者的完整逻辑。 |
| 和简单函数封装的区别? | 命令模式是一个可传递、可存储、可撤销的对象,而函数封装通常是即时执行的。 |
| 核心价值 | 将“发出请求”与“执行请求”完全分离,带来了极大的灵活性和可维护性。 |
当你看到“命令模式”时,可以理解为:把一次操作(请求)打包成一个可以传递、排队、记录和撤销的对象,这在需要高灵活性、异步处理或复杂工作流的应用程序中非常有用。