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

C# 備忘錄模式實戰:物件狀態捕獲與恢復機制完整指南

     編輯:關於C#

前言

在軟體開發中,我們常需要提供「復原」功能:使用者編輯文件時可隨時撤銷、遊戲角色狀態可存檔讀檔、資料庫交易失敗需回滾。直接暴露物件內部狀態違反封裝性;複製整個物件又過於笨重。備忘錄模式提供標準解決方案:在不破壞封裝的前提下,捕獲物件內部狀態並在外部保存,日後可將物件恢復至保存狀態。本文透過「訊息插入需秒數能被 5 整除」的業務邏輯,完整展示備忘錄模式的三大角色協作機制。

模式結構與三大核心角色

  • Originator(發起人):SqlMessage,需要保存狀態的物件,提供 SaveMemento() 建立備忘錄、RestoreMemento(Memento) 恢復狀態。
  • Memento(備忘錄):MessageModel,儲存 Originator 內部狀態(訊息內容、發布時間),對 Originator 可見,對 Caretaker 不透明(僅作儲存)。
  • Caretaker(管理者):MessageModelCaretaker,負責保管備忘錄,不操作、不檢查備忘錄內容。
備忘錄模式類別圖

核心程式碼機制深度解析

1. Originator:狀態擁有者與備忘錄工廠

public class SqlMessage
{
    private string _message;
    public string Message { get { return _message; } set { _message = value; } }

    private DateTime _publishTime;
    public DateTime PublishTime { get { return _publishTime; } set { _publishTime = value; } }

    // 業務邏輯:秒數能被 5 整除才允許插入
    public bool Insert(MessageModel mm)
    {
        if (mm.PublishTime.Second % 5 == 0)
            return true;
        return false;
    }

    // 建立備忘錄:封裝當前狀態
    public MessageModel SaveMemento()
    {
        return new MessageModel(_message, _publishTime);
    }

    // 恢復狀態:從備忘錄取回
    public void RestoreMemento(MessageModel mm)
    {
        this._message = mm.Message;
        this._publishTime = mm.PublishTime;
    }
}

關鍵設計:

  • SaveMemento() 回傳新建立的 MessageModel,包含當前關鍵狀態(此處為訊息內容與時間)。
  • RestoreMemento() 從備忘錄還原狀態,實現「時光倒流」。
  • 業務邏輯 Insert() 獨立於備忘錄機制,符合單一職責。

2. Memento:狀態容器

MessageModel 同時扮演資料實體與備忘錄角色,屬性為可讀寫,允許 Originator 存取:

public class MessageModel
{
    public string Message { get; set; }
    public DateTime PublishTime { get; set; }
    // 建構子略
}

實務中,為強化封裝,可將 Memento 設計為 Originator 的巢狀類別,或使用介面隔離「窄介面(給 Caretaker)」與「寬介面(給 Originator)」。

3. Caretaker:備忘錄保管者

public class MessageModelCaretaker
{
    private MessageModel _messageModel;
    public MessageModel MessageModel 
    { 
        get { return _messageModel; } 
        set { _messageModel = value; } 
    }
}

僅負責儲存備忘錄參考,不知曉內容結構,不參與業務邏輯。

4. 客戶端:重試機制與狀態恢復流程

SqlMessage m = new SqlMessage();
m.Message = "Message內容";
m.PublishTime = DateTime.Now;

MessageModelCaretaker mmc = new MessageModelCaretaker();
mmc.MessageModel = m.SaveMemento(); // 保存初始狀態

bool bln = false;
while (!bln)
{
    bln = m.Insert(new MessageModel(m.Message, m.PublishTime)); // 嘗試插入
    Response.Write(m.Message + " " + m.PublishTime + " " + bln);

    if (!bln)
    {
        System.Threading.Thread.Sleep(1000);      // 等待 1 秒
        m.RestoreMemento(mmc.MessageModel);       // 恢復原始訊息內容
        m.PublishTime = DateTime.Now;             // 僅更新時間重試
    }
}

流程解析:

  1. 保存初始狀態(訊息內容固定、時間為當下)。
  2. 嘗試插入,若失敗(秒數非 5 的倍數)。
  3. 等待 1 秒,恢復訊息內容(防止業務邏輯修改),更新時間為最新。
  4. 循環直到秒數能被 5 整除,插入成功。

執行結果驗證

Message內容 2007-5-23 21:32:13 False
Message內容 2007-5-23 21:32:14 False
Message內容 2007-5-23 21:32:15 True  ← 15 秒能被 5 整除,成功

結果顯示:訊息內容始終保持「Message內容」,只有時間在遞增,完美驗證了備忘錄模式「保存狀態→嘗試操作→失敗恢復→重試」的核心價值。

進階變體與最佳實務

多重備忘錄與歷史記錄

Caretaker 可改為 Stack<Memento> 或 List<Memento> 支援多級復原(Undo/Redo),配合限制最大儲存數量防止記憶體洩漏。

備忘錄封裝性強化

  • 巢狀類別:Memento 定義在 Originator 內部,私有建構子,只有 Originator 能建立/存取。
  • 介面隔離:定義 IMemento(空介面給 Caretaker),OriginatorMemento 實作該介面並提供狀態存取方法給 Originator。

效能考量

  • 大型物件頻繁建立備忘錄會消耗記憶體,可採用「增量備忘錄」僅儲存變更差異。
  • 序列化備忘錄至磁碟/資料庫支援跨進程、跨會話恢復(如編輯器自動存檔)。

優缺點總結

優點

  • 保護封裝性:外部物件無法存取 Originator 內部狀態細節。
  • 簡化 Originator:不需自行管理歷史狀態,職責單一。
  • 支援撤銷/重做、交易回滾、檢查點恢復等場景。

缺點

  • 備忘錄儲存大量狀態時記憶體開銷大。
  • Caretaker 需額外邏輯管理備忘錄生命週期(何時刪除)。
  • 若 Originator 狀態結構頻繁變更,Memento 類別需同步修改。

總結

備忘錄模式以「發起人-備忘錄-管理者」三角色協作,實現物件狀態的外部化保存與恢復,同時維護封裝邊界。本範例以「秒數能被 5 整除才能插入」的條件性重試場景,生動展示了備忘錄如何支援「失敗恢復原始狀態、僅修改特定欄位重試」的業務需求。實務中廣泛應用於:文字編輯器撤銷/重做、遊戲存檔讀檔、資料庫交易回滾、Wizard 精靈式介面的步驟返回、瀏覽器前進後退等場景。掌握此模式,能讓你在需要「時光倒流」功能時,擁有標準化、可維護的架構設計方案。


參考資源:DoFactory Memento Pattern

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