備忘錄模式基礎概念
備忘錄模式(Memento Pattern)是一種行為型設計模式,其核心思想是在不破壞封裝性的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態,以便后續可以將該對象恢復到先前保存的狀態。備忘錄模式通過將狀態保存和恢復的責任分離,實現了對象狀態管理的封裝和解耦。
備忘錄模式的核心組件
- 原發器(Originator)?- 需要保存狀態的對象,負責創建和恢復備忘錄。
- 備忘錄(Memento)?- 存儲原發器的內部狀態,通常通過原發器創建并由管理者持有。
- 管理者(Caretaker)?- 負責保存備忘錄,但不能對備忘錄的內容進行操作或檢查。
備忘錄模式的實現
下面通過一個文本編輯器的例子展示備忘錄模式的實現:
// 1. 備忘錄類 - 存儲原發器的狀態
class Memento {private final String content; // 需要保存的狀態public Memento(String content) {this.content = content;}public String getContent() {return content;}
}// 2. 原發器 - 文本編輯器
class TextEditor {private String content; // 內部狀態public void setContent(String content) {this.content = content;}public String getContent() {return content;}// 創建備忘錄,保存當前狀態public Memento createMemento() {return new Memento(content);}// 從備忘錄恢復狀態public void restoreMemento(Memento memento) {this.content = memento.getContent();}
}// 3. 管理者 - 負責保存備忘錄
class History {private final Stack<Memento> mementos = new Stack<>(); // 使用棧保存歷史狀態public void push(Memento memento) {mementos.push(memento);}public Memento pop() {if (mementos.isEmpty()) {return null;}return mementos.pop();}
}// 4. 客戶端代碼
public class MementoPatternClient {public static void main(String[] args) {TextEditor editor = new TextEditor();History history = new History();// 編輯文本并保存狀態editor.setContent("Hello");history.push(editor.createMemento());editor.setContent("Hello World");history.push(editor.createMemento());editor.setContent("Hello World!");System.out.println("當前內容: " + editor.getContent()); // 輸出: Hello World!// 撤銷操作editor.restoreMemento(history.pop());System.out.println("撤銷一次后的內容: " + editor.getContent()); // 輸出: Hello Worldeditor.restoreMemento(history.pop());System.out.println("再撤銷一次后的內容: " + editor.getContent()); // 輸出: Hello}
}
備忘錄模式的變體
白箱備忘錄?- 備忘錄的狀態對所有類可見,違反封裝原則,但實現簡單:
class Memento {private String content;public Memento(String content) {this.content = content;}// 所有類可訪問的公共方法public String getContent() {return content;}public void setContent(String content) {this.content = content;} }
黑箱備忘錄?- 使用內部類和接口實現封裝,原發器以外的類無法訪問備忘錄內容:
interface MementoInterface {// 空接口,僅用于標識 }class TextEditor {private String content;// 私有內部類實現備忘錄private class EditorMemento implements MementoInterface {private final String savedContent;public EditorMemento(String content) {this.savedContent = content;}private String getSavedContent() {return savedContent;}}public MementoInterface createMemento() {return new EditorMemento(content);}public void restoreMemento(MementoInterface memento) {EditorMemento editorMemento = (EditorMemento) memento;this.content = editorMemento.getSavedContent();} }
增量備忘錄?- 只保存狀態變化的部分,節省內存:
class IncrementalMemento {private final String addedText;private final int position;public IncrementalMemento(String addedText, int position) {this.addedText = addedText;this.position = position;}// 恢復方法由原發器實現 }
備忘錄模式的應用場景
- 撤銷 / 重做功能?- 如文本編輯器、圖形設計工具的歷史記錄
- 事務管理?- 數據庫操作的回滾機制
- 游戲存檔?- 保存游戲進度,支持讀取存檔
- 狀態恢復?- 系統崩潰后的恢復點機制
- 多級篩選?- 如電商平臺的篩選條件保存與恢復
- 瀏覽器歷史?- 網頁瀏覽歷史的前進 / 后退功能
備忘錄模式的優缺點
優點:
- 保持封裝性?- 不破壞對象的封裝前提下保存和恢復其內部狀態
- 簡化原發器?- 原發器不需要管理自己的歷史狀態,職責更清晰
- 支持撤銷操作?- 可以方便地實現多級撤銷和重做功能
- 狀態恢復透明?- 客戶端無需關心狀態的保存細節
- 符合開閉原則?- 可以在不修改原發器的情況下新增備忘錄類
缺點:
- 資源消耗?- 如果頻繁創建備忘錄,會占用大量內存
- 性能問題?- 對于大型對象,保存和恢復狀態可能影響性能
- 管理復雜度?- 管理者需要正確管理備忘錄的生命周期
- 序列化開銷?- 如果需要跨進程或持久化保存,可能增加序列化復雜度
使用備忘錄模式的注意事項
- 控制備忘錄大小?- 避免保存過大的對象狀態,考慮使用增量備忘錄
- 限制管理者權限?- 管理者只能保存和傳遞備忘錄,不能修改其內容
- 考慮內存管理?- 對于長期保存的備忘錄,需要考慮內存回收策略
- 處理復雜對象?- 對于包含引用的對象,需要處理深拷貝和淺拷貝問題
- 結合其他模式?- 備忘錄模式常與命令模式結合實現撤銷系統,與迭代器模式結合遍歷歷史狀態
- 持久化支持?- 如果需要持久化備忘錄,需考慮序列化和版本兼容性
總結
備忘錄模式通過將對象狀態的保存和恢復封裝在獨立的備忘錄類中,實現了對象狀態管理的解耦和封裝。它在不破壞對象封裝性的前提下,允許對象在需要時恢復到之前的狀態,是實現撤銷 / 重做功能、事務回滾、游戲存檔等場景的理想選擇。在實際開發中,合理使用備忘錄模式可以提高系統的可維護性和用戶體驗,但需要注意控制內存消耗和管理復雜度。