備忘錄模式是行為型設計模式之一,用於在不破壞封裝性的前提下,捕獲物件內部狀態並外部化儲存,以便日後恢復。典型應用包括:編輯器的「復原/重做」、遊戲存檔讀檔、資料庫交易回滾、IDE 程式碼快照等。本文提供標準 C++ 實作範例,並解析關鍵設計細節。
friend 或公開介面)。#include <iostream>
#include <string>
#include <memory>
using State = std::string;
class Memento {
friend class Originator; // 僅 Originator 可存取內部狀態
State state_;
explicit Memento(const State& s) : state_(s) {}
public:
~Memento() = default;
// 禁止外部複製/移動,確保封裝
Memento(const Memento&) = delete;
Memento& operator=(const Memento&) = delete;
State getState() const { return state_; }
};
class Originator {
State state_;
public:
Originator() = default;
explicit Originator(const State& s) : state_(s) {}
// 建立備忘錄(轉移所有權給呼叫者)
std::unique_ptr<Memento> createMemento() {
return std::make_unique<Memento>(state_);
}
// 從備忘錄恢復
void restoreFromMemento(const Memento* m) {
if (m) state_ = m->getState();
}
// 業務介面
void setState(const State& s) { state_ = s; }
State getState() const { return state_; }
void printState() const { std::cout << state_ << ".." << std::endl; }
};
class Caretaker {
std::vector<std::unique_ptr<Memento>> history_;
size_t current_ = 0; // 指向當前狀態
public:
void save(Originator& o) {
// 丟棄 current 之後的未來(重做堆疊)
history_.resize(current_);
history_.push_back(o.createMemento());
++current_;
}
void undo(Originator& o) {
if (current_ == 0) return;
--current_;
o.restoreFromMemento(history_[current_].get());
}
void redo(Originator& o) {
if (current_ >= history_.size()) return;
o.restoreFromMemento(history_[current_].get());
++current_;
}
};
int main() {
Originator originator("old");
Caretaker caretaker;
originator.printState(); // old..
caretaker.save(originator);
originator.setState("new");
originator.printState(); // new..
caretaker.save(originator);
caretaker.undo(originator);
originator.printState(); // old..
caretaker.redo(originator);
originator.printState(); // new..
return 0;
}
Memento 將建構函式設為 private,並宣告 Originator 為 friend,確保只有發起人能建立/讀取備忘錄。外部(含 Caretaker)僅能持有不透明指標,無法竄改內部狀態。
使用 std::unique_ptr<Memento> 明確所有權轉移:createMemento 回傳唯一擁有權給 Caretaker,避免記憶體洩漏與雙重釋放。
範例使用 using State = std::string,實務上可為任意可複製/可序列化的型別(如 struct GameState { int hp, mp; std::vector<Item> items; };)。
Caretaker 以向量 + 索引實作線性歷史,支援無限級 Undo/Redo。若記憶體敏感,可改用固定大小環狀緩衝區。
clone(),可直接複製整個物件作為備忘錄,簡化實作但犧牲封裝性。| 誤區 | 正確做法 |
|---|---|
| 讓 Caretaker 直接存取 Memento 內部資料 | Memento 應為不透明型別,Caretaker 僅負責保管指標 |
| 使用裸指標手動管理 Memento 生命週期 | 採用智慧指標(unique_ptr/shared_ptr)自動管理 |
| 備忘錄包含大量重複資料導致記憶體暴增 | 採用增量儲存、壓縮、或限制歷史深度 |
| 多執行緒環境下未同步歷史堆疊 | 加鎖或使用無鎖資料結構保護 Caretaker |
備忘錄模式以優雅的封裝方式實現狀態捕獲與恢復,是實作 Undo/Redo、存檔讀檔、交易回滾的標準解法。掌握「發起人-備忘錄-管理者」三角色協作、善用現代 C++ 智慧指標與移動語意,即能編寫安全、高效、可擴展的狀態管理系統。