意圖(Intent)#
將請求封裝為物件,使你可以用不同的請求來參數化客戶端、將請求放入佇列或記錄日誌,並支援可復原(undo)的操作。
別名(Also Known As)#
Action、Transaction
動機(Motivation)#
在 UI 工具包中,按鈕和選單等元件需要回應使用者的操作,但工具包本身並不知道具體該執行什麼——這取決於應用程式。Command 模式的解法是將請求本身包裝成物件。
關鍵概念:
- 定義一個抽象的 Command 類別,宣告
Execute介面 - ConcreteCommand 子類別儲存接收者(receiver)並實作
Execute,呼叫接收者上的具體操作 - 選單中的每個項目(MenuItem)持有一個 Command 物件,使用者選取時呼叫其
Execute
例如 PasteCommand 儲存一個 Document 作為接收者,Execute 時呼叫 Document 的 Paste 操作。OpenCommand 則提示使用者輸入文件名稱,建立 Document 並開啟。
此外,可定義 MacroCommand 來串接多個命令,依序執行,實現複合操作。
Command 模式的核心價值在於:發出請求的物件(Invoker)與知道如何執行的物件(Receiver)完全解耦。同一個 Command 可以同時被選單和按鈕共用,也可以在執行期間動態替換。
適用場景(Applicability)#
- 以操作來參數化物件(object-oriented 版的 callback)
- 在不同時間點指定、排入佇列並執行請求——Command 物件的生命週期可獨立於原始請求
- 支援 undo/redo:Command 的
Execute儲存反轉所需的狀態,搭配Unexecute操作與 history list 實現多層復原 - 支援變更日誌(logging):透過 load/store 操作維護持久化日誌,系統崩潰後可重新載入並重播命令
- 將系統建構在高階操作之上,適合支援 transaction 的資訊系統
結構(Structure)#
Client 建立 ConcreteCommand 並設定其 Receiver。Invoker(如 MenuItem)持有 Command 並呼叫 Execute。ConcreteCommand 將呼叫轉發至 Receiver 上的具體操作。
classDiagram
class Command {
<<interface>>
+Execute()
}
class ConcreteCommand {
-receiver
-state
+Execute()
}
class Client
class Invoker
class Receiver {
+Action()
}
Command <|.. ConcreteCommand
Invoker o--> Command
ConcreteCommand --> Receiver
Client ..> ConcreteCommand : creates
Client ..> Receiver參與者(Participants)#
| 參與者 | 範例 | 職責 |
|---|---|---|
| Command | - | 宣告執行操作的介面 |
| ConcreteCommand | PasteCommand、OpenCommand | 定義 Receiver 物件與動作之間的綁定;實作 Execute,呼叫 Receiver 上的對應操作 |
| Client | Application | 建立 ConcreteCommand 並設定其 Receiver |
| Invoker | MenuItem | 要求 Command 執行請求 |
| Receiver | Document、Application | 知道如何執行與請求相關的操作;任何類別都可以作為 Receiver |
協作方式(Collaborations)#
- Client 建立 ConcreteCommand 並指定其 Receiver
- Invoker 儲存 ConcreteCommand
- Invoker 呼叫
Execute;若支援 undo,ConcreteCommand 在執行前先儲存回復狀態 - ConcreteCommand 呼叫 Receiver 上的操作來完成請求
sequenceDiagram
participant Client
participant Invoker
participant ConcreteCommand
participant Receiver
Client->>ConcreteCommand: new(receiver)
Client->>Invoker: StoreCommand(cmd)
Invoker->>ConcreteCommand: Execute()
ConcreteCommand->>Receiver: Action()優缺點(Consequences)#
- 解耦 Invoker 與 Receiver——發出請求的物件不需要知道由誰、如何執行
- Command 是一等公民物件——可以像其他物件一樣被操作、傳遞、儲存
- 可組合為複合命令——使用 Composite 模式實作 MacroCommand
- 容易新增 Command——不需修改現有類別,符合 Open-Closed Principle
實作要點(Implementation)#
- Command 的智慧程度
- 最簡單:僅綁定 Receiver 與動作
- 最複雜:自行實作所有邏輯,不委派給 Receiver
- 折衷方案:動態尋找 Receiver
- 支援 undo/redo
- ConcreteCommand 需儲存額外狀態:Receiver 物件、操作參數、Receiver 的原始值
- 使用 history list 記錄已執行的命令,反向遍歷執行
Unexecute實現 undo,正向重新Execute實現 redo - 若命令狀態會隨呼叫變化,需在放入 history list 前複製命令物件(此時 Command 扮演 Prototype 角色)
undo/redo 過程中可能產生誤差累積(hysteresis),多次執行與反轉後應用程式狀態可能偏離原始值。可搭配 Memento 模式儲存更完整的狀態資訊來緩解此問題。
- 使用模板簡化——對於不需要 undo、不需要參數的簡單命令,可使用 C++ template 避免為每種 Receiver-Action 組合建立子類別
MacroCommand 的
Unexecute必須以Execute的反序執行子命令的Unexecute,這是實作複合命令撤銷時常被忽略的細節。
已知應用(Known Uses)#
- MacApp、ET++、InterViews、Unidraw 都定義了遵循 Command 模式的類別,用於實作可復原操作
- THINK 類別庫 將命令稱為「Task」,Task 物件沿 Chain of Responsibility 傳遞處理
- Unidraw 的 Command 物件可像訊息一樣被發送給其他物件進行解釋,接收者可依據 run-time type information 委派解釋
相關模式(Related Patterns)#
- Composite:可用於實作 MacroCommand
- Memento:可保存 Command 執行復原所需的狀態
- Prototype:需在放入 history list 前複製的命令扮演 Prototype 角色