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

備忘錄模式深度解析:Java 中狀態捕獲與撤銷機制實現

     編輯:Java 相關知識

模式定義與核心價值

備忘錄模式在不破壞封裝性的前提下,捕獲並外部化對象的內部狀態,以便稍後恢復至該狀態。核心角色:

  • Originator(發起人):擁有狀態,創建備忘錄,可從備忘錄恢復。
  • Memento(備忘錄):存儲發起人狀態快照,對發起人透明,對管理者不透明 (窄介面)。
  • Caretaker(管理者):負責保存備忘錄,不操作/檢查備忘錄內容。

經典應用場景

  • 編輯器的撤銷/重做 (Ctrl+Z / Ctrl+Y)。
  • 遊戲存檔/讀檔 (檢查點機制)。
  • 資料庫事務回滾、JVM 棧幀回溯。
  • 分布式系統狀態機快照 (Raft Log Compaction)。

完整代碼實現

1. 備忘錄類 (不可變、最終類)


public final class EditorMemento {
    private final String content;
    private final int cursorPosition;
    private final long timestamp;
    
    public EditorMemento(String content, int cursorPosition) {
        this.content = content;
        this.cursorPosition = cursorPosition;
        this.timestamp = System.currentTimeMillis();
    }
    
    // 僅提供給 Originator 的包訪問權限方法
    String getContent() { return content; }
    int getCursorPosition() { return cursorPosition; }
    long getTimestamp() { return timestamp; }
}

2. 發起人:文本編輯器


public class TextEditor {
    private String content = "";
    private int cursorPosition = 0;
    
    public void write(String text) {
        content = content.substring(0, cursorPosition) + text + content.substring(cursorPosition);
        cursorPosition += text.length();
    }
    
    public void delete(int length) {
        if (cursorPosition >= length) {
            content = content.substring(0, cursorPosition - length) + content.substring(cursorPosition);
            cursorPosition -= length;
        }
    }
    
    public void moveCursor(int pos) {
        cursorPosition = Math.max(0, Math.min(pos, content.length()));
    }
    
    // 創建備忘錄
    public EditorMemento save() {
        return new EditorMemento(content, cursorPosition);
    }
    
    // 從備忘錄恢復
    public void restore(EditorMemento memento) {
        this.content = memento.getContent();
        this.cursorPosition = memento.getCursorPosition();
    }
    
    @Override
    public String toString() {
        return String.format("內容: '%s' | 光標: %d", content, cursorPosition);
    }
}

3. 管理者:歷史記錄棧 (支援撤銷/重做)


public class HistoryManager {
    private final Deque undoStack = new ArrayDeque<>();
    private final Deque redoStack = new ArrayDeque<>();
    private static final int MAX_HISTORY = 100;
    
    public void pushUndo(EditorMemento m) {
        if (undoStack.size() >= MAX_HISTORY) undoStack.removeLast();
        undoStack.push(m);
        redoStack.clear(); // 新操作清空重做棧
    }
    
    public EditorMemento popUndo() { return undoStack.poll(); }
    public EditorMemento popRedo() { return redoStack.poll(); }
    
    public void pushRedo(EditorMemento m) { redoStack.push(m); }
    
    public boolean canUndo() { return !undoStack.isEmpty(); }
    public boolean canRedo() { return !redoStack.isEmpty(); }
}

4. 客戶端測試


public class MementoDemo {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();
        HistoryManager history = new HistoryManager();
        
        // 初始狀態保存
        history.pushUndo(editor.save());
        System.out.println("初始: " + editor);
        
        // 操作序列
        editor.write("Hello ");
        history.pushUndo(editor.save());
        System.out.println("寫入: " + editor);
        
        editor.write("World!");
        history.pushUndo(editor.save());
        System.out.println("寫入: " + editor);
        
        editor.moveCursor(5);
        editor.delete(6); // 刪除 "World!"
        history.pushUndo(editor.save());
        System.out.println("刪除: " + editor);
        
        // 撤銷兩次
        if (history.canUndo()) {
            history.pushRedo(editor.save()); // 當前狀態入重做棧
            editor.restore(history.popUndo());
            System.out.println("撤銷1: " + editor);
        }
        if (history.canUndo()) {
            history.pushRedo(editor.save());
            editor.restore(history.popUndo());
            System.out.println("撤銷2: " + editor);
        }
        
        // 重做一次
        if (history.canRedo()) {
            history.pushUndo(editor.save());
            editor.restore(history.popRedo());
            System.out.println("重做: " + editor);
        }
    }
}

輸出結果

初始: 內容: '' | 光標: 0
寫入: 內容: 'Hello ' | 光標: 6
寫入: 內容: 'Hello World!' | 光標: 12
刪除: 內容: 'Hello ' | 光標: 6
撤銷1: 內容: 'Hello World!' | 光標: 12
撤銷2: 內容: 'Hello ' | 光標: 6
重做: 內容: 'Hello World!' | 光標: 12

進階變體與優化

最佳實踐與避坑

  • 備忘錄不可變:一旦創建不可修改,保證歷史純淨。
  • 封裝性保護:備忘錄敏感方法限制為 package-private,僅 Originator 可訪問。
  • 記憶體控制:限制歷史棧大小 (如 100 步),或使用弱引用/增量存儲。
  • 大對象慎用:狀態巨大時 (如巨大文檔),改用增量或命令模式 (存操作而非狀態)。
  • 線程安全:多線程環境下,HistoryManager 需加鎖或用 ConcurrentLinkedDeque。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
變體特點適用場景
完全備忘錄備忘錄暴露所有狀態 getter,Originator 同包可見狀態簡單、需完全恢復
增量備忘錄只存儲變更差異 (如 Git diff),節省空間狀態大、變更小、頻繁保存
原型模式結合Originator 實現 Cloneable,直接 clone 作為備忘錄狀態對象支持深拷貝、結構穩定
序列化備忘錄將狀態序列化為字節數組/JSON 存儲跨進程/持久化/分布式恢復