意圖(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 並呼叫 ExecuteConcreteCommand 將呼叫轉發至 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-宣告執行操作的介面
ConcreteCommandPasteCommand、OpenCommand定義 Receiver 物件與動作之間的綁定;實作 Execute,呼叫 Receiver 上的對應操作
ClientApplication建立 ConcreteCommand 並設定其 Receiver
InvokerMenuItem要求 Command 執行請求
ReceiverDocument、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)#

  • MacAppET++InterViewsUnidraw 都定義了遵循 Command 模式的類別,用於實作可復原操作
  • THINK 類別庫 將命令稱為「Task」,Task 物件沿 Chain of Responsibility 傳遞處理
  • Unidraw 的 Command 物件可像訊息一樣被發送給其他物件進行解釋,接收者可依據 run-time type information 委派解釋

相關模式(Related Patterns)#

  • Composite:可用於實作 MacroCommand
  • Memento:可保存 Command 執行復原所需的狀態
  • Prototype:需在放入 history list 前複製的命令扮演 Prototype 角色