在軟體開發中,我們常需要提供「復原」功能:使用者編輯文件時可隨時撤銷、遊戲角色狀態可存檔讀檔、資料庫交易失敗需回滾。直接暴露物件內部狀態違反封裝性;複製整個物件又過於笨重。備忘錄模式提供標準解決方案:在不破壞封裝的前提下,捕獲物件內部狀態並在外部保存,日後可將物件恢復至保存狀態。本文透過「訊息插入需秒數能被 5 整除」的業務邏輯,完整展示備忘錄模式的三大角色協作機制。
SqlMessage,需要保存狀態的物件,提供 SaveMemento() 建立備忘錄、RestoreMemento(Memento) 恢復狀態。MessageModel,儲存 Originator 內部狀態(訊息內容、發布時間),對 Originator 可見,對 Caretaker 不透明(僅作儲存)。MessageModelCaretaker,負責保管備忘錄,不操作、不檢查備忘錄內容。
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() 獨立於備忘錄機制,符合單一職責。MessageModel 同時扮演資料實體與備忘錄角色,屬性為可讀寫,允許 Originator 存取:
public class MessageModel
{
public string Message { get; set; }
public DateTime PublishTime { get; set; }
// 建構子略
}
實務中,為強化封裝,可將 Memento 設計為 Originator 的巢狀類別,或使用介面隔離「窄介面(給 Caretaker)」與「寬介面(給 Originator)」。
public class MessageModelCaretaker
{
private MessageModel _messageModel;
public MessageModel MessageModel
{
get { return _messageModel; }
set { _messageModel = value; }
}
}
僅負責儲存備忘錄參考,不知曉內容結構,不參與業務邏輯。
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; // 僅更新時間重試
}
}
流程解析:
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),配合限制最大儲存數量防止記憶體洩漏。
IMemento(空介面給 Caretaker),OriginatorMemento 實作該介面並提供狀態存取方法給 Originator。備忘錄模式以「發起人-備忘錄-管理者」三角色協作,實現物件狀態的外部化保存與恢復,同時維護封裝邊界。本範例以「秒數能被 5 整除才能插入」的條件性重試場景,生動展示了備忘錄如何支援「失敗恢復原始狀態、僅修改特定欄位重試」的業務需求。實務中廣泛應用於:文字編輯器撤銷/重做、遊戲存檔讀檔、資料庫交易回滾、Wizard 精靈式介面的步驟返回、瀏覽器前進後退等場景。掌握此模式,能讓你在需要「時光倒流」功能時,擁有標準化、可維護的架構設計方案。