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

Java 備忘錄模式完整實現:撤銷操作與狀態保存

     編輯:Java 相關知識

備忘錄模式在不破壞封裝性前提下,捕獲物件內部狀態並外部化保存,以便日後恢復。本文實現文本編輯器「撤銷」功能,展示 Originator (發起人)、Memento (備忘錄)、Caretaker (管理者) 三角色協作,並討論狀態序列化、記憶體優化與多級撤銷/重做擴展。

模式結構


┌─────────────┐         ┌─────────────┐         ┌─────────────┐
│  Originator │────────▶│  Memento    │◀───────▶│  Caretaker  │
│  (發起人)   │ 創建/恢復 │  (備忘錄)   │  存儲/管理  │  (管理者)   │
└─────────────┘         └─────────────┘         └─────────────┘
      │                       │                       │
      │ 內部狀態              │ 不可變快照            │ 棧/列表存儲
      ▼                       ▼                       ▼
  Word 物件            WordMemento              List

核心代碼實現

1. 發起人:Word 文檔物件


class Word {
    private String content;
    private String image;
    private String table;

    public Word(String content, String image, String table) {
        this.content = content;
        this.image = image;
        this.table = table;
    }

    // 創建備忘錄:將當前狀態封裝為不可變快照
    public WordMemento createMemento() {
        return new WordMemento(content, image, table);
    }

    // 恢復狀態
    public void restore(WordMemento memento) {
        this.content = memento.getContent();
        this.image = memento.getImage();
        this.table = memento.getTable();
    }

    // 業務方法
    public void setContent(String content) { this.content = content; }
    public void setImage(String image) { this.image = image; }
    public void setTable(String table) { this.table = table; }
    
    @Override public String toString() {
        return "Word{content='" + content + "', image='" + image + "', table='" + table + "'}";
    }
}

2. 備忘錄:不可變狀態快照


final class WordMemento {
    private final String content;
    private final String image;
    private final String table;

    public WordMemento(String content, String image, String table) {
        this.content = content;
        this.image = image;
        this.table = table;
    }

    // 僅提供 getter,無 setter,保證不可變
    public String getContent() { return content; }
    public String getImage() { return image; }
    public String getTable() { return table; }
}

3. 管理者:棧式存儲支援多級撤銷


class Caretaker {
    private final List history = new ArrayList<>();
    private int currentIndex = -1; // 當前狀態指針

    public void save(WordMemento memento) {
        // 截斷當前指針後的歷史 (新分支覆蓋重做棧)
        if (currentIndex < history.size() - 1) {
            history.subList(currentIndex + 1, history.size()).clear();
        }
        history.add(memento);
        currentIndex++;
    }

    public WordMemento undo() {
        if (currentIndex <= 0) {
            System.out.println("無法撤銷:已到達最早狀態");
            return null;
        }
        currentIndex--;
        return history.get(currentIndex);
    }

    public WordMemento redo() {
        if (currentIndex >= history.size() - 1) {
            System.out.println("無法重做:已到達最新狀態");
            return null;
        }
        currentIndex++;
        return history.get(currentIndex);
    }

    public boolean canUndo() { return currentIndex > 0; }
    public boolean canRedo() { return currentIndex < history.size() - 1; }
}

4. 客戶端演示


public class MementoDemo {
    public static void main(String[] args) {
        Word doc = new Word("初始內容", "logo.png", "table1");
        Caretaker caretaker = new Caretaker();
        
        // 保存初始狀態
        caretaker.save(doc.createMemento());
        System.out.println("初始: " + doc);

        // 修改 1
        doc.setContent("修改後內容 v1");
        doc.setImage("banner.png");
        caretaker.save(doc.createMemento());
        System.out.println("修改1: " + doc);

        // 修改 2
        doc.setContent("修改後內容 v2");
        doc.setTable("table2");
        caretaker.save(doc.createMemento());
        System.out.println("修改2: " + doc);

        // 撤銷
        WordMemento m = caretaker.undo();
        if (m != null) doc.restore(m);
        System.out.println("撤銷1: " + doc);

        // 撤銷
        m = caretaker.undo();
        if (m != null) doc.restore(m);
        System.out.println("撤銷2: " + doc);

        // 重做
        m = caretaker.redo();
        if (m != null) doc.restore(m);
        System.out.println("重做1: " + doc);
    }
}

輸出結果


初始: Word{content='初始內容', image='logo.png', table='table1'}
修改1: Word{content='修改後內容 v1', image='banner.png', table='table1'}
修改2: Word{content='修改後內容 v2', image='banner.png', table='table2'}
撤銷1: Word{content='修改後內容 v1', image='banner.png', table='table1'}
撤銷2: Word{content='初始內容', image='logo.png', table='table1'}
重做1: Word{content='修改後內容 v1', image='banner.png', table='table1'}

進階擴展與優化

1. 狀態序列化持久化 (磁盤/資料庫)


// 備忘錄實現 Serializable
final class WordMemento implements Serializable {
    private static final long serialVersionUID = 1L;
    // ... 同上
}

// 管理者持久化
class PersistentCaretaker extends Caretaker {
    private final String storagePath;
    
    public void saveToDisk() {
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream(storagePath))) {
            oos.writeObject(history);
            oos.writeInt(currentIndex);
        } catch (IOException e) { log.error("持久化失敗", e); }
    }
    
    @SuppressWarnings("unchecked")
    public void loadFromDisk() {
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream(storagePath))) {
            history.clear();
            history.addAll((List) ois.readObject());
            currentIndex = ois.readInt();
        } catch (IOException | ClassNotFoundException e) { log.error("加載失敗", e); }
    }
}

2. 記憶體優化:增量備忘錄 / 壓縩

  • 大對象 (如巨大文本) 全量快照佔用記憶體大。
  • 方案:存儲**差異** (diff/patch)、或定期全量 + 間隔增量、或壓縮 (GZIP/Protobuf)。
  • 文本編輯器常用 Piece Table / Rope 數據結構配合增量備忘錄。

3. 多發起人管理


class MultiCaretaker {
    private final Map caretakers = new HashMap<>();
    
    public Caretaker getCaretaker(String originatorId) {
        return caretakers.computeIfAbsent(originatorId, k -> new Caretaker());
    }
}

模式優缺點總結

優點缺點
不破壞封裝性 (發起人控制狀態暴露)大狀態對象頻繁備份佔用大量記憶體
簡化發起人代碼 (無需管理歷史)管理者需知道何時保存/恢復
支援任意級別撤銷/重做若發起人內部結構頻繁變更,備忘錄類需同步修改
易於實現持久化、棧點、分支語言無 GC 時需手動管理備忘錄生命週期

實際應用場景

  1. 文本編輯器 / IDE 撤銷重做 (IntelliJ、VS Code、Word)。
  2. 遊戲存檔/讀檔 (狀態快照)。
  3. 資料庫事務回滾 (WAL 日誌本質是備忘錄)。
  4. 工作流引擎狀態回退。
  5. 瀏覽器前進/後退 (History API)。

總結

備忘錄模式以「不可變快照 + 外部管理者」實現狀態回溯,核心在於:

  1. 發起人負責創建/恢復備忘錄,封裝內部狀態。
  2. 備忘錄不可變,僅提供 getter,安全傳遞。
  3. 管理者用棧/列表存儲,支援撤銷/重做/持久化。
  4. 大狀態場景需引入增量/壓縮優化記憶體。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved