生活中無法時光倒流,但在軟體系統中,我們可以實現「狀態回滾」。備忘錄模式正是這顆讓物件隨時回到歷史狀態的「月光寶盒」。它在不破壞封裝性前提下,捕捉物件內部狀態並外部化儲存,廣泛應用於撤銷/重做、事務回滾、遊戲存檔等場景。
GoF 定義:在不破壞封裝性的前提下,捕捉一個物件的內部狀態,並在該物件之外保存這個狀態,以便日後恢復。
C++ 可用 friend 關鍵字實現「只有 Originator 可存取 Memento 內部」,Java 無此機制,需採用替代方案:
MementoNarrow(空介面/標記)與 MementoWide(含 getter/setter)。將 Memento 定義為 Originator 的 private static 內部類。外部類(含 Caretaker)完全看不見 Memento 的內部欄位與方法,只有 Originator 能存取。
public class Originator {
// 內部狀態
private int state = 90;
// 內部持有管理者
private Caretaker caretaker = new Caretaker();
// 私有內部類:外部不可見,只有 Originator 能存取
private static class Memento {
private int state;
Memento(int state) { this.state = state; }
int getState() { return state; }
}
// 建立備忘錄
public void createMemento() {
caretaker.saveMemento(new Memento(this.state));
}
// 恢復狀態
public void restoreMemento() {
Memento m = caretaker.getMemento();
if (m != null) this.state = m.getState();
}
// 管理者:只負責保管,類型為 Object 或窄介面
private static class Caretaker {
private Object memento; // 實際為 Memento,但對外不可見
void saveMemento(Object m) { memento = m; }
Object getMemento() { return memento; }
}
}
將 Memento 類別設為 package-private(無修飾符),放在同一 package,Originator 為 public,Caretaker 也在同 package。外部模組無法存取 Memento 細節。
// 發起人
public class GameRole {
private int vit; // 生命力
private int atk; // 攻擊力
private int def; // 防禦力
public GameRole(int vit, int atk, int def) {
this.vit = vit; this.atk = atk; this.def = def;
}
// 內部備忘錄類
private static class Memento {
private final int vit, atk, def;
Memento(int vit, int atk, int def) {
this.vit = vit; this.atk = atk; this.def = def;
}
}
// 建立備忘錄
public Memento save() {
return new Memento(vit, atk, def);
}
// 恢復
public void load(Memento m) {
this.vit = m.vit; this.atk = m.atk; this.def = m.def;
}
// 狀態改變模擬
public void fight() { vit -= 10; atk += 5; def -= 3; }
public void display() { System.out.printf("狀態:生命%d 攻擊%d 防禦%d%n", vit, atk, def); }
}
// 管理者
public class Caretaker {
private GameRole.Memento memento; // 編譯錯誤!Memento 是 private
// 實際專案中可用 Object 或介面代替
private Object mementoObj;
public void save(Object m) { mementoObj = m; }
public Object load() { return mementoObj; }
}
// 客戶端測試
public class Client {
public static void main(String[] args) {
GameRole role = new GameRole(100, 50, 30);
Caretaker caretaker = new Caretaker();
role.display(); // 初始狀態
caretaker.save(role.save()); // 存檔
role.fight(); // 戰鬥消耗
role.display(); // 受損狀態
role.load((GameRole.Memento) caretaker.load()); // 讀檔
role.display(); // 恢復初始
}
}
注意:上述客戶端強轉(GameRole.Memento)僅為示範。實際生產中,Caretaker 應持有Object或定義一個MementoIF窄介面,避免客戶端依賴具體備忘錄類。
serialization 實現深度複製備忘錄,或使用 record (Java 16+) 簡化不可變備忘錄定義。備忘錄模式是「狀態管理」的經典解法。Java 中採用 私有內部類 是實現強封裝的最佳實踐。掌握其核心——「狀態外部化、封裝不洩漏、管理者不干涉」,即可在專案中靈活應用。