命令模式將「請求」封裝為獨立對象,實現請求參數化、隊列化、日誌化、可撤銷操作。是 GUI 按鈕、異步任務、事務腳本的經典選擇。
| 角色 | 職責 | 對應類 |
|---|---|---|
| Command (命令接口) | 聲明 execute() 統一接口 | Command interface |
| ConcreteCommand (具體命令) | 綁定接收者與動作,實現 execute() | LightOnCommand |
| Receiver (接收者) | 執行具體業務邏輯 | Light |
| Invoker (調用者/請求者) | 持有命令對象,觸發 execute() | RemoteControl |
| Client (客戶端) | 創建命令、接收者、調用者並裝配 | 腳本主程序 |
<?php
// 1. 命令接口
interface Command {
public function execute();
public function undo(); // 支持撤銷
}
// 2. 接收者:燈
class Light {
private $location;
public function __construct($location) { $this->location = $location; }
public function on() { echo "{$this->location} 燈已開啟\n"; }
public function off() { echo "{$this->location} 燈已關閉\n"; }
}
// 3. 具體命令:開燈
class LightOnCommand implements Command {
private $light;
public function __construct(Light $light) { $this->light = $light; }
public function execute() { $this->light->on(); }
public function undo() { $this->light->off(); }
}
// 4. 具體命令:關燈
class LightOffCommand implements Command {
private $light;
public function __construct(Light $light) { $this->light = $light; }
public function execute() { $this->light->off(); }
public function undo() { $this->light->on(); }
}
// 5. 調用者:遙控器(支持撤銷、隊列)
class RemoteControl {
private $onCommands = [];
private $offCommands = [];
private $lastCommand = null;
private $history = []; // 命令隊列/日誌
public function setCommand($slot, Command $onCmd, Command $offCmd) {
$this->onCommands[$slot] = $onCmd;
$this->offCommands[$slot] = $offCmd;
}
public function onButtonPressed($slot) {
$cmd = $this->onCommands[$slot];
$cmd->execute();
$this->lastCommand = $cmd;
$this->history[] = ['cmd' => $cmd, 'time' => time(), 'type' => 'on'];
}
public function offButtonPressed($slot) {
$cmd = $this->offCommands[$slot];
$cmd->execute();
$this->lastCommand = $cmd;
$this->history[] = ['cmd' => $cmd, 'time' => time(), 'type' => 'off'];
}
public function undoButtonPressed() {
if ($this->lastCommand) {
$this->lastCommand->undo();
$this->history[] = ['cmd' => $this->lastCommand, 'time' => time(), 'type' => 'undo'];
}
}
public function getHistory() { return $this->history; }
}
// 6. 客戶端裝配
$livingRoomLight = new Light('客廳');
$kitchenLight = new Light('廚房');
$remote = new RemoteControl();
$remote->setCommand(0, new LightOnCommand($livingRoomLight), new LightOffCommand($livingRoomLight));
$remote->setCommand(1, new LightOnCommand($kitchenLight), new LightOffCommand($kitchenLight));
// 使用
$remote->onButtonPressed(0); // 客廳 燈已開啟
$remote->offButtonPressed(0); // 客廳 燈已關閉
$remote->undoButtonPressed(); // 客廳 燈已開啟 (撤銷關閉)
// 查看日誌
print_r($remote->getHistory());
undo()(文本編輯器、事務回滾)class MacroCommand implements Command {
private $commands = [];
public function add(Command $cmd) { $this->commands[] = $cmd; }
public function execute() { foreach ($this->commands as $c) $c->execute(); }
public function undo() { foreach (array_reverse($this->commands) as $c) $c->undo(); }
}
// 一鍵「離家模式」
$leaveHome = new MacroCommand();
$leaveHome->add(new LightOffCommand($livingRoomLight));
$leaveHome->add(new LightOffCommand($kitchenLight));
$leaveHome->add(new LockDoorCommand($frontDoor));
$leaveHome->add(new ArmAlarmCommand($alarm));
$remote->setCommand(9, $leaveHome, new MacroCommand()); // 撤銷需反向宏命令
$remote->onButtonPressed(9); // 依次執行所有關閉/鎖定/布防
handle() 對應 execute()up()/down() 即 execute()/undo()命令模式將「做什麼」與「誰做、何時做」解耦,是構建靈活、可測試、可擴展系統的基石模式之一。