Command Pattern(命令模式)

是什麼?

Command Pattern 將請求(Request)封裝成物件,讓你可以把請求存起來、傳遞、排隊、記錄,甚至做 Undo/Redo。命令物件包含了執行操作所需的所有資訊。

ℹ️GoF 分類

Command 屬於行為型模式(Behavioral Pattern),重點在於將「發出請求」和「執行請求」解耦。

什麼時候用?

用以下 if/then 條件判斷:

什麼時候不該用?

⚠️過度設計警告

如果操作很簡單且不需要撤銷或排隊,直接呼叫方法就好。每個操作都包一層 Command 會讓程式碼膨脹,只有在需要「把操作當作資料來處理」時才值得。

執行流程

1

定義 Command 介面

宣告 Execute 和可選的 Undo 方法

2

實作具體 Command

每個命令封裝接收者和操作參數

3

建立 Invoker

持有命令參考,負責觸發執行

4

建立 Receiver

實際執行業務邏輯的物件

5

Client 組裝

建立命令、設定接收者、交給 Invoker

流程解讀:Command 的四個角色各司其職。Command 介面定義「做什麼」的合約(Execute/Undo)。ConcreteCommand 封裝了 Receiver(誰來做)和參數(用什麼資料做)。Invoker 不關心命令內容,只負責在正確時機觸發。Receiver 是真正執行業務邏輯的物件。這種分離讓「發出請求」和「執行請求」完全解耦。

程式碼範例

C# 版本

csharp
// 1. Command 介面
public interface ICommand
{
    void Execute();
    void Undo();
}
 
// 2. Receiver:文字編輯器
public class TextEditor
{
    public string Content { get; private set; } = "";
 
    public void InsertText(string text) => Content += text;
    public void DeleteLast(int count) =>
        Content = Content[..^Math.Min(count, Content.Length)];
 
    public override string ToString() => $"「{Content}」";
}
 
// 3. 具體 Command
public class InsertTextCommand : ICommand
{
    private readonly TextEditor _editor;
    private readonly string _text;
 
    public InsertTextCommand(TextEditor editor, string text)
    {
        _editor = editor;
        _text = text;
    }
 
    public void Execute() => _editor.InsertText(_text);
    public void Undo() => _editor.DeleteLast(_text.Length);
}
 
// 4. Invoker:命令歷史管理器
public class CommandHistory
{
    private readonly Stack<ICommand> _history = new();
 
    public void ExecuteCommand(ICommand command)
    {
        command.Execute();
        _history.Push(command);
    }
 
    public void UndoLast()
    {
        if (_history.Count > 0)
            _history.Pop().Undo();
    }
}
 
// 5. 使用
var editor = new TextEditor();
var history = new CommandHistory();
 
history.ExecuteCommand(new InsertTextCommand(editor, "Hello"));
history.ExecuteCommand(new InsertTextCommand(editor, " World"));
Console.WriteLine(editor); // 「Hello World」
 
history.UndoLast();
Console.WriteLine(editor); // 「Hello」
 
history.UndoLast();
Console.WriteLine(editor); // 「」

TypeScript 版本

typescript
// 1. Command 介面
interface Command {
  execute(): void;
  undo(): void;
}
 
// 2. Receiver
class TextEditor {
  content = "";
 
  insertText(text: string) { this.content += text; }
  deleteLast(count: number) {
    this.content = this.content.slice(0, -count || undefined) ?? "";
  }
 
  toString() { return `「${this.content}」`; }
}
 
// 3. 具體 Command
class InsertTextCommand implements Command {
  constructor(private editor: TextEditor, private text: string) {}
 
  execute() { this.editor.insertText(this.text); }
  undo() { this.editor.deleteLast(this.text.length); }
}
 
// 4. Invoker
class CommandHistory {
  private history: Command[] = [];
 
  executeCommand(command: Command) {
    command.execute();
    this.history.push(command);
  }
 
  undoLast() {
    const command = this.history.pop();
    command?.undo();
  }
}
 
// 5. 使用
const editor = new TextEditor();
const history = new CommandHistory();
 
history.executeCommand(new InsertTextCommand(editor, "Hello"));
history.executeCommand(new InsertTextCommand(editor, " World"));
console.log(editor.toString()); // 「Hello World」
 
history.undoLast();
console.log(editor.toString()); // 「Hello」

Python 版本

python
from abc import ABC, abstractmethod
 
# 1. Command 介面
class Command(ABC):
    @abstractmethod
    def execute(self) -> None:
        pass
    @abstractmethod
    def undo(self) -> None:
        pass
 
# 2. Receiver
class TextEditor:
    def __init__(self):
        self.content = ""
 
    def insert_text(self, text: str) -> None:
        self.content += text
 
    def delete_last(self, count: int) -> None:
        self.content = self.content[:-count] if count else self.content
 
    def __str__(self) -> str:
        return f"「{self.content}」"
 
# 3. 具體 Command
class InsertTextCommand(Command):
    def __init__(self, editor: TextEditor, text: str):
        self._editor = editor
        self._text = text
 
    def execute(self) -> None:
        self._editor.insert_text(self._text)
 
    def undo(self) -> None:
        self._editor.delete_last(len(self._text))
 
# 4. Invoker
class CommandHistory:
    def __init__(self):
        self._history: list[Command] = []
 
    def execute_command(self, command: Command) -> None:
        command.execute()
        self._history.append(command)
 
    def undo_last(self) -> None:
        if self._history:
            self._history.pop().undo()
 
# 5. 使用
editor = TextEditor()
history = CommandHistory()
 
history.execute_command(InsertTextCommand(editor, "Hello"))
history.execute_command(InsertTextCommand(editor, " World"))
print(editor)  # 「Hello World」
 
history.undo_last()
print(editor)  # 「Hello」

Java 版本

java
import java.util.Stack;
 
// 1. Command 介面
public interface Command {
    void execute();
    void undo();
}
 
// 2. Receiver
public class TextEditor {
    private StringBuilder content = new StringBuilder();
 
    public void insertText(String text) { content.append(text); }
    public void deleteLast(int count) {
        int start = Math.max(0, content.length() - count);
        content.delete(start, content.length());
    }
 
    public String toString() { return "「" + content + "」"; }
}
 
// 3. 具體 Command
public class InsertTextCommand implements Command {
    private final TextEditor editor;
    private final String text;
 
    public InsertTextCommand(TextEditor editor, String text) {
        this.editor = editor;
        this.text = text;
    }
 
    public void execute() { editor.insertText(text); }
    public void undo() { editor.deleteLast(text.length()); }
}
 
// 4. Invoker
public class CommandHistory {
    private final Stack<Command> history = new Stack<>();
 
    public void executeCommand(Command cmd) {
        cmd.execute();
        history.push(cmd);
    }
 
    public void undoLast() {
        if (!history.isEmpty()) history.pop().undo();
    }
}
 
// 5. 使用
TextEditor editor = new TextEditor();
CommandHistory history = new CommandHistory();
 
history.executeCommand(new InsertTextCommand(editor, "Hello"));
history.executeCommand(new InsertTextCommand(editor, " World"));
System.out.println(editor); // 「Hello World」
 
history.undoLast();
System.out.println(editor); // 「Hello」

結構圖

Client
triggers
Invoker (CommandHistory)
executes
Command Interface
ConcreteCommand (InsertText)
implements
Receiver (TextEditor)

結構解讀:Client 把 ConcreteCommand 交給 Invoker,Invoker 在適當時機呼叫 Execute()。ConcreteCommand 內部持有 Receiver 的參考,Execute() 實際上是呼叫 Receiver 的方法。Invoker 完全不知道 Receiver 的存在,只認識 Command Interface。這讓同一個 Invoker 可以執行任何類型的命令。

實戰補充

💡資深開發者經驗

CQRS 架構:Command Query Responsibility Segregation 把 Command(寫入)和 Query(讀取)分開處理。MediatR 套件在 .NET 生態系中廣泛用來實作 Command 分發。

Command Queue:把命令放入 Message Queue(如 RabbitMQ、Azure Service Bus)可以實現非同步處理和流量削峰。

Macro Command:把多個 Command 組合成一個 Macro,一次執行或一次撤銷。這是 Composite Pattern + Command Pattern 的經典組合。

序列化命令:Command 物件可以序列化存到資料庫,實現 Event Sourcing——系統的所有狀態都是一連串命令的結果。

理解測驗

🤔 Command Pattern 的核心目的是什麼?

🤔 在 Command Pattern 中,Invoker 的職責是什麼?

🤔 以下哪個場景最不適合用 Command Pattern?

面試常見問題

Q: Command Pattern 的 Undo 和 Memento Pattern 的 Undo 有什麼差別?何時用哪個?

A: Command Undo 透過「反向操作」回復(如 Insert 的 Undo 是 Delete)。Memento Undo 透過「狀態快照」回復(恢復到之前保存的完整狀態)。選擇標準:如果每個操作都有明確的反向操作 → Command Undo 更省記憶體;如果操作複雜到難以定義反向操作 → Memento 更安全。實務中常搭配使用。

Q: CQRS 和 Command Pattern 是什麼關係?

A: CQRS(Command Query Responsibility Segregation)將「寫入操作(Command)」和「讀取操作(Query)」分開處理。Command Pattern 提供了封裝寫入操作的機制。在 .NET 中,MediatR 套件的 IRequest + IRequestHandler 就是 Command Pattern 在 CQRS 中的實踐。

相關模式

| 模式 | 關係 | |------|------| | Memento | 搭配使用 — Command 記錄「做了什麼」,Memento 記錄「之前的狀態」,兩者結合實現完整的 Undo 系統 | | Composite | Macro Command 就是 Composite + Command — 把多個 Command 組合成一個,一次執行或撤銷 | | Strategy | 都是把行為封裝成物件,但 Command 強調「請求的物件化」(可排隊、撤銷),Strategy 強調「演算法的替換」 | | Chain of Responsibility | 可搭配使用 — 命令沿著責任鏈傳遞,找到合適的處理者來執行 |

重點整理

💡一句話記住

Command Pattern = 點菜單:把請求寫在紙上,可以排隊、取消、重做。 口訣:「請求變物件,排撤記三合一」

| 概念 | 說明 | |------|------| | Command(命令介面) | 定義 Execute 和 Undo 方法 | | ConcreteCommand(具體命令) | 封裝 Receiver 和操作參數 | | Invoker(呼叫者) | 持有命令並觸發執行 | | Receiver(接收者) | 實際執行業務邏輯的物件 | | 核心好處 | 解耦請求發送者與執行者,支援 Undo/Redo | | 代價 | 每個操作都需要一個命令類別,類別數量會增加 |

你可能也想看

Observer PatternTemplate Method Pattern

按 ← → 鍵切換課程