命令模式將「請求」封裝為物件,使請求可參數化、排隊、記錄、撤銷。適用於 GUI 按鈕、宏指令、交易回滾、工作佇列等場景。本文提供 PHP 介面導向實作,並示範撤銷功能。
| 角色 | 介面/類別 | 職責 |
|---|---|---|
| 命令介面 | Command | 宣告 execute()、undo() |
| 具體命令 | ConcreteCommand | 綁定接收者與動作,實作執行/撤銷邏輯 |
| 接收者 | Receiver | 執行實際業務操作 |
| 調用者 | Invoker | 持有命令物件,負責觸發執行/撤銷 |
| 客戶端 | Client | 組裝命令、接收者、調用者 |
<?php
// 1. 命令介面
interface Command {
public function execute();
public function undo();
}
// 2. 接收者:實際執行業務邏輯
class LightReceiver {
private $location;
private $isOn = false;
private $brightness = 0;
public function __construct(string $location) {
$this->location = $location;
}
public function turnOn() {
$this->isOn = true;
$this->brightness = 100;
echo "[{$this->location} 燈] 開啟,亮度 {$this->brightness}%\n";
}
public function turnOff() {
$this->isOn = false;
$this->brightness = 0;
echo "[{$this->location} 燈] 關閉\n";
}
public function dim(int $level) {
$this->brightness = $level;
echo "[{$this->location} 燈] 調光至 {$level}%\n";
}
public function getState(): array {
return ['on' => $this->isOn, 'brightness' => $this->brightness];
}
}
// 3. 具體命令:開燈
class LightOnCommand implements Command {
private $light;
private $prevState;
public function __construct(LightReceiver $light) {
$this->light = $light;
}
public function execute() {
$this->prevState = $this->light->getState(); // 記錄前狀態供撤銷
$this->light->turnOn();
}
public function undo() {
if ($this->prevState['on']) {
$this->light->turnOn();
$this->light->dim($this->prevState['brightness']);
} else {
$this->light->turnOff();
}
echo "[撤銷] 開燈命令 -> 恢復狀態\n";
}
}
// 4. 具體命令:關燈
class LightOffCommand implements Command {
private $light;
private $prevState;
public function __construct(LightReceiver $light) {
$this->light = $light;
}
public function execute() {
$this->prevState = $this->light->getState();
$this->light->turnOff();
}
public function undo() {
if ($this->prevState['on']) {
$this->light->turnOn();
$this->light->dim($this->prevState['brightness']);
} else {
$this->light->turnOff();
}
echo "[撤銷] 關燈命令 -> 恢復狀態\n";
}
}
// 5. 調用者:支援命令歷史與撤銷
class RemoteControlInvoker {
/** @var Command[] */
private $history = [];
private $lastCommand = null;
public function setCommand(Command $command) {
$this->lastCommand = $command;
}
public function pressButton() {
if ($this->lastCommand) {
$this->lastCommand->execute();
$this->history[] = $this->lastCommand;
}
}
public function pressUndo() {
if ($this->lastCommand) {
$this->lastCommand->undo();
array_pop($this->history);
$this->lastCommand = end($this->history) ?: null;
}
}
}
// 6. 客戶端測試
$livingRoomLight = new LightReceiver('客廳');
$bedroomLight = new LightReceiver('臥室');
$livingOn = new LightOnCommand($livingRoomLight);
$livingOff = new LightOffCommand($livingRoomLight);
$bedOn = new LightOnCommand($bedroomLight);
$remote = new RemoteControlInvoker();
echo "--- 開客廳燈 ---\n";
$remote->setCommand($livingOn);
$remote->pressButton();
echo "\n--- 開臥室燈 ---\n";
$remote->setCommand($bedOn);
$remote->pressButton();
echo "\n--- 關客廳燈 ---\n";
$remote->setCommand($livingOff);
$remote->pressButton();
echo "\n--- 撤銷最後操作 (關客廳燈) ---\n";
$remote->pressUndo();
echo "\n--- 再次撤銷 (開臥室燈) ---\n";
$remote->pressUndo();
--- 開客廳燈 ---
[客廳 燈] 開啟,亮度 100%
--- 開臥室燈 ---
[臥室 燈] 開啟,亮度 100%
--- 關客廳燈 ---
[客廳 燈] 關閉
--- 撤銷最後操作 (關客廳燈) ---
[撤銷] 關燈命令 -> 恢復狀態
[客廳 燈] 開啟,亮度 100%
--- 再次撤銷 (開臥室燈) ---
[撤銷] 開燈命令 -> 恢復狀態
[臥室 燈] 關閉
Invoker 不知具體業務邏輯,只呼叫 execute()。undo() 並記錄前狀態即可。MacroCommand 包含多個子命令,一次執行整個流程。class MacroCommand implements Command {
/** @var Command[] */
private $commands = [];
public function add(Command $cmd) { $this->commands[] = $cmd; }
public function execute() {
foreach ($this->commands as $cmd) $cmd->execute();
}
public function undo() {
// 撤銷順序相反
for ($i = count($this->commands)-1; $i >= 0; $i--) {
$this->commands[$i]->undo();
}
}
}
// 使用:一鍵「離家模式」= 關所有燈 + 鎖門 + 啟動警報
$leaveHome = new MacroCommand();
$leaveHome->add(new LightOffCommand($livingRoomLight));
$leaveHome->add(new LightOffCommand($bedroomLight));
$leaveHome->add(new LockDoorCommand($door));
$leaveHome->add(new AlarmOnCommand($alarm));
$remote->setCommand($leaveHome);
$remote->pressButton(); // 一次執行全部
命令模式將「做什麼」與「誰做、何時做」分離,是實現可撤銷操作、工作佇列、巨集指令、交易回滾的基石。PHP 介面 + 型別提示讓結構清晰、易於測試與擴展。