首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 命令模式完全指南:請求封裝、撤銷重做與佇列日誌實戰

     編輯:關於C#

前言

當系統需要支援「撤銷/重做」、操作佇列、請求日誌、巨集記錄、交易回滾等功能時,將請求封裝為獨立物件是關鍵。命令模式將「請求的發起者」與「請求的執行者」解耦:請求被封裝為命令物件,持有接收者參考與執行參數,發起者只需呼叫 Execute(),無需知道具體執行細節。本文透過訊息系統的 Insert/Delete 操作,完整展示命令模式如何實現多級撤銷、重做與歷史記錄管理。

模式結構與五大角色

  • Command(命令介面):ICommand,定義 Execute()、UnExecute() 契約。
  • ConcreteCommand(具體命令):SqlMessageCommand,持有 Action(Insert/Delete)與 MessageModel 參數,持有接收者 SqlMessage,實作執行與反向操作邏輯。
  • Receiver(接收者):SqlMessage,執行實際業務邏輯(Insert、Delete)。
  • Invoker(請求者):Message 類別,維護命令歷史清單 _listCommand 與當前指標 current,提供 Do()、Undo()、Redo() 高層操作。
  • Client(客戶端):建立接收者、命令、請求者,組裝關係並觸發操作序列。
命令模式類別圖

核心程式碼機制深度解析

1. 命令介面與枚舉定義

public enum Action { Insert, Delete }

public interface ICommand
{
    string Execute();   // 執行並回傳描述
    string UnExecute(); // 撤銷並回傳描述
}

2. 接收者:純業務邏輯執行者

public class SqlMessage
{
    public void Operation(Action action, MessageModel mm)
    {
        switch (action)
        {
            case Action.Insert: Insert(mm); break;
            case Action.Delete: Delete(mm); break;
        }
    }
    private void Insert(MessageModel mm) { /* 實際 SQL 插入 */ }
    private void Delete(MessageModel mm) { /* 實際 SQL 刪除 */ }
}

接收者不包含命令管理邏輯,專注業務實作,可被多種命令複用。

3. 具體命令:封裝請求與反向操作

public class SqlMessageCommand : ICommand
{
    private Action _action;
    private MessageModel _mm;

    public SqlMessageCommand(Action action, MessageModel mm)
    {
        this._action = action;
        this._mm = mm;
    }

    public string Execute()
    {
        new SqlMessage().Operation(_action, _mm);
        return _action.ToString() + ":" + _mm.Message;
    }

    public string UnExecute()
    {
        _action = GetUndoAction(_action); // 計算反向動作
        new SqlMessage().Operation(_action, _mm);
        return _action.ToString() + ":" + _mm.Message;
    }

    private Action GetUndoAction(Action action)
    {
        return action == Action.Insert ? Action.Delete : Action.Insert;
    }
}

關鍵設計:

  • 命令物件為「不可變」的請求快照:建構時鎖定 Action 與 MessageModel。
  • UnExecute() 透過 GetUndoAction() 計算反向操作(Insert↔Delete),實現語義級撤銷而非狀態快照恢復。
  • 每次 Execute() 都建立新的 SqlMessage 接收者實例(無狀態),亦可注入共享實例。

4. 請求者:歷史管理與撤銷重做核心

public class Message
{
    private List<ICommand> _listCommand = new List<ICommand>();
    private int current = 0; // 指向「下一個將執行的命令」索引

    public string Do(Action action, MessageModel mm)
    {
        ICommand cmd = new SqlMessageCommand(action, mm);
        string rtn = cmd.Execute();

        // 關鍵:執行新命令時,截斷 current 之後的歷史(重做棧清空)
        if (current < _listCommand.Count)
            _listCommand.RemoveRange(current, _listCommand.Count - current);

        _listCommand.Add(cmd);
        current++;
        return rtn;
    }

    public string Undo(int levels)
    {
        string rtn = "";
        for (int i = 0; i < levels; i++)
        {
            if (current > 0)
            {
                ICommand cmd = _listCommand[--current];
                rtn += cmd.UnExecute() + " ";
            }
        }
        return rtn;
    }

    public string Redo(int levels)
    {
        string rtn = "";
        for (int i = 0; i < levels; i++)
        {
            if (current < _listCommand.Count)
            {
                ICommand cmd = _listCommand[current++];
                rtn += cmd.UnExecute() + " "; // 注意:此處呼叫 UnExecute 執行重做
            }
        }
        return rtn;
    }
}

核心機制解析:

  • _listCommand 記錄所有成功執行的命令歷史。
  • current 指標分隔「已執行」與「可重做」區域:
    [0 ... current-1] = 已執行(可撤銷)
    [current ... Count-1] = 已撤銷(可重做)
  • Do() 執行新命令時,移除 current 之後的舊重做歷史(分支覆蓋語義)。
  • Undo() 將 current 向左移動,呼叫 UnExecute() 執行反向操作。
  • Redo() 將 current 向右移動,同樣呼叫 UnExecute()(因反向的反向是正向)。

5. 客戶端測試:複雜操作序列驗證


Message m = new Message();
// 執行 8 個操作
m.Do(Action.Insert, new MessageModel("第1條", Now));
m.Do(Action.Insert, new MessageModel("第2條", Now));
m.Do(Action.Insert, new MessageModel("第3條", Now));
m.Do(Action.Insert, new MessageModel("第4條", Now));
m.Do(Action.Delete, new MessageModel("第2條", Now));
m.Do(Action.Insert, new MessageModel("第5條", Now));
m.Do(Action.Delete, new MessageModel("第3條", Now));
m.Do(Action.Insert, new MessageModel("第6條", Now));
m.Do(Action.Insert, new MessageModel("第7條", Now));

// 撤銷 4 次:刪除第7、刪除第6、插入第3、刪除第5
m.Undo(4);

// 重做 2 次:插入第5、刪除第3
m.Redo(2);

// 再撤銷 3 次:插入第3、刪除第5、插入第2
m.Undo(3);

執行結果完整驗證

操作
Insert:第1條
Insert:第2條
Insert:第3條
Insert:第4條
Delete:第2條
Insert:第5條
Delete:第3條
Insert:第6條
Insert:第7條

撤銷4次操作
Delete:第7條 Delete:第6條 Insert:第3條 Delete:第5條

重復2次操作
Insert:第5條 Delete:第3條

撤銷3次操作
Insert:第3條 Delete:第5條 Insert:第2條

結果精確對應命令歷史棧操作,驗證撤銷/重做機制正確性。

進階應用與最佳實務

巨集命令:組合模式應用

實作 MacroCommand : ICommand 內含 List<ICommand>,Execute() 循環執行子命令,支援批次操作單次撤銷。

命令持久化與日誌

  • 命令物件序列化至資料庫/檔案,實現操作審計日誌。
  • 應用啟動時反序列化重放命令,恢復系統狀態(Event Sourcing 雛形)。

非同步命令佇列

  • Invoker 內部使用 BlockingCollection<ICommand>,背景執行緒消費執行。
  • 支援優先級佇列、延遲執行、重試策略。

命令與狀態模式結合

接收者為狀態機時,命令可觸發狀態轉換,撤銷需恢復狀態(可結合備忘錄模式)。

優缺點總結

優點

  • 發起者與接收者解耦:發起者不知曉業務細節。
  • 支援撤銷/重做、日誌、佇列、巨集等高階功能。
  • 新增命令不修改既有程式碼(開閉原則)。
  • 命令可序列化、傳輸、遠端執行(RPC 雛形)。

缺點

  • 類別數量增加:每個操作一個命令類別。
  • 命令物件增加記憶體開銷(大量歷史需定期清理/壓縮)。
  • 語義級撤銷需精心設計反向操作,複雜業務難以完美逆向。

總結

命令模式將「請求」物件化,賦予請求一等公民地位:可傳遞、儲存、撤銷、重做、組合、記錄。本範例以訊息系統 Insert/Delete 為例,完整實現了命令封裝、接收者分離、請求者歷史管理(雙棧指標法)與多級撤銷重做的核心機制。實務中廣泛應用於:編輯器撤銷/重做、IDE 重構操作、資料庫遷移腳本、工作流引擎、遊戲指令重放、分散式系統命令模式(CQRS/Event Sourcing)。掌握命令模式,能讓你在需要「操作可逆、可記錄、可佇列、可遠端」的系統設計中,構建出靈活強大的指令處理架構。


參考資源:DoFactory Command Pattern

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved