备忘录模式 (Memento Pattern)

概述 (Overview)

备忘录模式是一种行为型设计模式,它允许在不暴露对象实现细节的情况下捕获对象的内部状态,并在以后将该对象恢复到原先保存的状态。

Memento Pattern is a behavioral design pattern that lets you save and restore the previous state of an object without revealing the details of its implementation.

意图 (Intent)

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.

适用场景 (When to Use)

  • 当需要保存和恢复对象的状态,而又不破坏对象的封装性时
  • 当需要实现撤销/重做功能时
  • 当需要保存对象的历史状态以便回滚时
  • 当需要创建对象状态的检查点时
  • 当需要实现事务回滚机制时
  • 当需要保存对象的多个状态快照时

结构 (Structure)

基础备忘录模式结构图 (Basic Memento Pattern Structure)

creates
stores
uses
uses
Memento
-state: Object
+getState()
Originator
-state: Object
+createMemento()
+setMemento(memento: Memento)
+setState(state: Object)
+getState()
Caretaker
-mementos: List
+addMemento(memento: Memento)
+getMemento(index: int)
Client
+saveState()
+restoreState()

实现方式 (Implementation Approaches)

1. 经典备忘录模式 (Classic Memento Pattern)

// 备忘录接口 - Memento Interface
public interface Memento {
    // 备忘录接口可以为空,也可以定义一些获取状态的方法
}

// 具体备忘录 - Concrete Memento
public class TextEditorMemento implements Memento {
    private final String content;
    private final int cursorPosition;
    private final Date timestamp;
    
    public TextEditorMemento(String content, int cursorPosition) {
        this.content = content;
        this.cursorPosition = cursorPosition;
        this.timestamp = new Date();
    }
    
    public String getContent() {
        return content;
    }
    
    public int getCursorPosition() {
        return cursorPosition;
    }
    
    public Date getTimestamp() {
        return timestamp;
    }
    
    @Override
    public String toString() {
        return String.format("Memento{content='%s...', cursor=%d, time=%s}", 
                           content.substring(0, Math.min(content.length(), 20)), 
                           cursorPosition, timestamp);
    }
}

// 原发器 - Originator
public class TextEditor {
    private String content;
    private int cursorPosition;
    
    public TextEditor() {
        this.content = "";
        this.cursorPosition = 0;
    }
    
    public void write(String text) {
        String beforeCursor = content.substring(0, cursorPosition);
        String afterCursor = content.substring(cursorPosition);
        content = beforeCursor + text + afterCursor;
        cursorPosition += text.length();
        System.out.println("写入文本: '" + text + "'");
    }
    
    public void delete(int length) {
        if (length <= 0 || cursorPosition < length) return;
        
        String beforeCursor = content.substring(0, cursorPosition - length);
        String afterCursor = content.substring(cursorPosition);
        content = beforeCursor + afterCursor;
        cursorPosition -= length;
        System.out.println("删除 " + length + " 个字符");
    }
    
    public void moveCursor(int position) {
        if (position >= 0 && position <= content.length()) {
            cursorPosition = position;
            System.out.println("移动光标到位置: " + position);
        }
    }
    
    public void moveCursorToEnd() {
        cursorPosition = content.length();
        System.out.println("移动光标到末尾");
    }
    
    // 创建备忘录
    public TextEditorMemento createMemento() {
        System.out.println("创建备忘录: " + content.substring(0, Math.min(content.length(), 10)) + "...");
        return new TextEditorMemento(content, cursorPosition);
    }
    
    // 从备忘录恢复状态
    public void restoreFromMemento(TextEditorMemento memento) {
        this.content = memento.getContent();
        this.cursorPosition = memento.getCursorPosition();
        System.out.println("从备忘录恢复状态");
    }
    
    // 获取当前状态
    public String getContent() {
        return content;
    }
    
    public int getCursorPosition() {
        return cursorPosition;
    }
    
    public void display() {
        System.out.println("当前内容: \"" + content + "\"");
        System.out.println("光标位置: " + cursorPosition);
        if (cursorPosition > 0) {
            System.out.println("光标指示: " + " ".repeat(cursorPosition - 1) + "^");
        }
    }
}

// 管理者 - Caretaker
public class TextEditorHistory {
    private final Stack<TextEditorMemento> history;
    private final int maxSize;
    
    public TextEditorHistory() {
        this(50); // 默认最多保存50个状态
    }
    
    public TextEditorHistory(int maxSize) {
        this.history = new Stack<>();
        this.maxSize = maxSize;
    }
    
    public void save(TextEditorMemento memento) {
        if (history.size() >= maxSize) {
            history.remove(0); // 移除最旧的状态
        }
        history.push(memento);
        System.out.println("保存备忘录到历史记录");
    }
    
    public TextEditorMemento undo() {
        if (history.isEmpty()) {
            throw new IllegalStateException("没有可撤销的状态");
        }
        TextEditorMemento memento = history.pop();
        System.out.println("撤销到备忘录: " + memento);
        return memento;
    }
    
    public boolean canUndo() {
        return !history.isEmpty();
    }
    
    public void clear() {
        history.clear();
        System.out.println("清空历史记录");
    }
    
    public int getHistorySize() {
        return history.size();
    }
    
    public TextEditorMemento getMemento(int index) {
        if (index < 0 || index >= history.size()) {
            throw new IndexOutOfBoundsException("索引超出范围");
        }
        return history.get(index);
    }
}

// 客户端使用 - Client Usage
public class TextEditorDemo {
    public static void main(String[] args) {
        System.out.println("=== 文本编辑器备忘录模式演示 ===");
        
        TextEditor editor = new TextEditor();
        TextEditorHistory history = new TextEditorHistory();
        
        System.out.println("\n--- 初始状态 ---");
        editor.display();
        
        // 编辑文本并保存状态
        System.out.println("\n--- 编辑文本 ---");
        editor.write("Hello ");
        history.save(editor.createMemento());
        
        editor.write("World");
        history.save(editor.createMemento());
        
        editor.write("!");
        history.save(editor.createMemento());
        
        editor.display();
        
        // 继续编辑
        System.out.println("\n--- 继续编辑 ---");
        editor.write(" Welcome");
        editor.display();
        
        // 撤销操作
        System.out.println("\n--- 撤销操作 ---");
        if (history.canUndo()) {
            editor.restoreFromMemento(history.undo());
            editor.display();
        }
        
        if (history.canUndo()) {
            editor.restoreFromMemento(history.undo());
            editor.display();
        }
        
        // 再次编辑
        System.out.println("\n--- 再次编辑 ---");
        editor.write(" Java");
        history.save(editor.createMemento());
        editor.display();
        
        System.out.println("\n历史记录大小: " + history.getHistorySize());
    }
}

2. 宽接口备忘录模式 (Wide Interface Memento Pattern)

// 宽接口备忘录 - Wide Interface Memento
public class GameMemento {
    private final int level;
    private final int score;
    private final int health;
    private final double x;
    private final double y;
    private final String weapon;
    private final List<String> inventory;
    private final Date timestamp;
    
    public GameMemento(int level, int score, int health, double x, double y, 
                      String weapon, List<String> inventory) {
        this.level = level;
        this.score = score;
        this.health = health;
        this.x = x;
        this.y = y;
        this.weapon = weapon;
        this.inventory = new ArrayList<>(inventory);
        this.timestamp = new Date();
    }
    
    // 宽接口:提供所有状态的访问方法
    public int getLevel() { return level; }
    public int getScore() { return score; }
    public int getHealth() { return health; }
    public double getX() { return x; }
    public double getY() { return y; }
    public String getWeapon() { return weapon; }
    public List<String> getInventory() { return new ArrayList<>(inventory); }
    public Date getTimestamp() { return timestamp; }
    
    @Override
    public String toString() {
        return String.format("GameMemento{level=%d, score=%d, health=%d, pos=(%.1f,%.1f), weapon='%s', items=%d}", 
                           level, score, health, x, y, weapon, inventory.size());
    }
}

// 游戏角色 - Game Character (Originator)
public class GameCharacter {
    private int level;
    private int score;
    private int health;
    private double x;
    private double y;
    private String weapon;
    private List<String> inventory;
    
    public GameCharacter(String name) {
        this.level = 1;
        this.score = 0;
        this.health = 100;
        this.x = 0.0;
        this.y = 0.0;
        this.weapon = "空手";
        this.inventory = new ArrayList<>();
        this.inventory.add("地图");
    }
    
    public void move(double dx, double dy) {
        this.x += dx;
        this.y += dy;
        System.out.printf("移动到 (%.1f, %.1f)%n", x, y);
    }
    
    public void attack() {
        if (health > 0) {
            System.out.println("使用 " + weapon + " 攻击!");
            score += 10;
        } else {
            System.out.println("生命值不足,无法攻击");
        }
    }
    
    public void takeDamage(int damage) {
        health = Math.max(0, health - damage);
        System.out.println("受到 " + damage + " 点伤害,当前生命值: " + health);
    }
    
    public void pickUpItem(String item) {
        inventory.add(item);
        System.out.println("拾取物品: " + item);
    }
    
    public void changeWeapon(String newWeapon) {
        String oldWeapon = weapon;
        weapon = newWeapon;
        System.out.println("更换武器: " + oldWeapon + " -> " + newWeapon);
    }
    
    public void levelUp() {
        level++;
        health = 100; // 升级后恢复生命值
        System.out.println("升级!当前等级: " + level);
    }
    
    // 创建备忘录
    public GameMemento createSavePoint() {
        System.out.println("创建游戏存档点...");
        return new GameMemento(level, score, health, x, y, weapon, inventory);
    }
    
    // 从备忘录恢复
    public void restoreFromSavePoint(GameMemento memento) {
        this.level = memento.getLevel();
        this.score = memento.getScore();
        this.health = memento.getHealth();
        this.x = memento.getX();
        this.y = memento.getY();
        this.weapon = memento.getWeapon();
        this.inventory = new ArrayList<>(memento.getInventory());
        System.out.println("从存档点恢复游戏状态");
    }
    
    public void displayStatus() {
        System.out.println("\n=== 角色状态 ===");
        System.out.println("等级: " + level);
        System.out.println("分数: " + score);
        System.out.println("生命值: " + health);
        System.out.println("位置: (" + x + ", " + y + ")");
        System.out.println("武器: " + weapon);
        System.out.println("背包: " + inventory);
    }
}

// 游戏存档管理器 - Game Save Manager (Caretaker)
public class GameSaveManager {
    private final Map<String, GameMemento> saveSlots;
    private final int maxSlots;
    private String autoSaveSlot;
    
    public GameSaveManager(int maxSlots) {
        this.saveSlots = new LinkedHashMap<>();
        this.maxSlots = maxSlots;
        this.autoSaveSlot = "auto_save";
    }
    
    public void saveGame(String slotName, GameMemento memento) {
        if (saveSlots.size() >= maxSlots && !saveSlots.containsKey(slotName)) {
            // 移除最旧的存档
            String oldestSlot = saveSlots.keySet().iterator().next();
            saveSlots.remove(oldestSlot);
            System.out.println("存档槽已满,移除最旧的存档: " + oldestSlot);
        }
        
        saveSlots.put(slotName, memento);
        System.out.println("游戏已保存到槽位: " + slotName);
    }
    
    public GameMemento loadGame(String slotName) {
        GameMemento memento = saveSlots.get(slotName);
        if (memento == null) {
            throw new IllegalArgumentException("存档槽不存在: " + slotName);
        }
        System.out.println("从槽位加载游戏: " + slotName);
        return memento;
    }
    
    public void autoSave(GameMemento memento) {
        saveGame(autoSaveSlot, memento);
        System.out.println("自动保存完成");
    }
    
    public GameMemento loadAutoSave() {
        return loadGame(autoSaveSlot);
    }
    
    public boolean hasAutoSave() {
        return saveSlots.containsKey(autoSaveSlot);
    }
    
    public void deleteSave(String slotName) {
        if (saveSlots.remove(slotName) != null) {
            System.out.println("删除存档: " + slotName);
        }
    }
    
    public List<String> getSaveSlots() {
        return new ArrayList<>(saveSlots.keySet());
    }
    
    public void displaySaveInfo() {
        System.out.println("\n=== 存档信息 ===");
        if (saveSlots.isEmpty()) {
            System.out.println("没有存档");
            return;
        }
        
        for (Map.Entry<String, GameMemento> entry : saveSlots.entrySet()) {
            System.out.println("槽位: " + entry.getKey() + " -> " + entry.getValue());
        }
    }
}

// 客户端使用 - Client Usage
public class GameSaveDemo {
    public static void main(String[] args) {
        System.out.println("=== 游戏存档备忘录模式演示 ===");
        
        GameCharacter character = new GameCharacter("勇者");
        GameSaveManager saveManager = new GameSaveManager(5);
        
        // 初始状态
        character.displayStatus();
        
        // 游戏过程
        System.out.println("\n--- 开始游戏 ---");
        character.move(10, 5);
        character.pickUpItem("生命药水");
        character.attack();
        saveManager.saveGame("checkpoint_1", character.createSavePoint());
        
        character.move(20, 10);
        character.takeDamage(30);
        character.pickUpItem("铁剑");
        character.changeWeapon("铁剑");
        character.attack();
        saveManager.saveGame("checkpoint_2", character.createSavePoint());
        
        character.move(30, 15);
        character.pickUpItem("魔法卷轴");
        character.attack();
        character.levelUp();
        saveManager.autoSave(character.createSavePoint());
        
        // 遭遇失败
        System.out.println("\n--- 遭遇强敌,角色死亡 ---");
        character.takeDamage(80);
        character.displayStatus();
        
        // 从存档恢复
        System.out.println("\n--- 从存档点恢复 ---");
        character.restoreFromSavePoint(saveManager.loadGame("checkpoint_2"));
        character.displayStatus();
        
        // 继续游戏
        System.out.println("\n--- 继续游戏 ---");
        character.move(25, 12);
        character.pickUpItem("盾牌");
        character.attack();
        saveManager.saveGame("checkpoint_3", character.createSavePoint());
        
        // 显示存档信息
        saveManager.displaySaveInfo();
        
        System.out.println("\n游戏继续...");
    }
}

3. 增量备忘录模式 (Incremental Memento Pattern)

// 增量状态变化 - Incremental State Change
public class StateChange {
    private final String propertyName;
    private final Object oldValue;
    private final Object newValue;
    private final Date timestamp;
    
    public StateChange(String propertyName, Object oldValue, Object newValue) {
        this.propertyName = propertyName;
        this.oldValue = oldValue;
        this.newValue = newValue;
        this.timestamp = new Date();
    }
    
    public String getPropertyName() { return propertyName; }
    public Object getOldValue() { return oldValue; }
    public Object getNewValue() { return newValue; }
    public Date getTimestamp() { return timestamp; }
    
    @Override
    public String toString() {
        return String.format("Change{%s: %s -> %s}", propertyName, oldValue, newValue);
    }
}

// 增量备忘录 - Incremental Memento
public class DocumentMemento {
    private final List<StateChange> changes;
    private final Date timestamp;
    
    public DocumentMemento() {
        this.changes = new ArrayList<>();
        this.timestamp = new Date();
    }
    
    public void addChange(StateChange change) {
        changes.add(change);
    }
    
    public List<StateChange> getChanges() {
        return new ArrayList<>(changes);
    }
    
    public Date getTimestamp() {
        return timestamp;
    }
    
    public boolean isEmpty() {
        return changes.isEmpty();
    }
    
    @Override
    public String toString() {
        return String.format("DocumentMemento{changes=%d, time=%s}", changes.size(), timestamp);
    }
}

// 文档编辑器 - Document Editor (Originator)
public class DocumentEditor {
    private String title;
    private String content;
    private String font;
    private int fontSize;
    private String color;
    private boolean bold;
    private boolean italic;
    private boolean underline;
    
    public DocumentEditor() {
        this.title = "Untitled";
        this.content = "";
        this.font = "Arial";
        this.fontSize = 12;
        this.color = "Black";
        this.bold = false;
        this.italic = false;
        this.underline = false;
    }
    
    public void setTitle(String title) {
        String oldTitle = this.title;
        this.title = title;
        System.out.println("设置标题: " + title);
        // 这里可以记录状态变化
    }
    
    public void setContent(String content) {
        String oldContent = this.content;
        this.content = content;
        System.out.println("设置内容: " + content.substring(0, Math.min(content.length(), 20)) + "...");
        // 这里可以记录状态变化
    }
    
    public void setFont(String font) {
        String oldFont = this.font;
        this.font = font;
        System.out.println("设置字体: " + font);
        // 这里可以记录状态变化
    }
    
    public void setFontSize(int fontSize) {
        int oldSize = this.fontSize;
        this.fontSize = fontSize;
        System.out.println("设置字体大小: " + fontSize);
        // 这里可以记录状态变化
    }
    
    public void setColor(String color) {
        String oldColor = this.color;
        this.color = color;
        System.out.println("设置颜色: " + color);
        // 这里可以记录状态变化
    }
    
    public void toggleBold() {
        this.bold = !bold;
        System.out.println("切换粗体: " + bold);
        // 这里可以记录状态变化
    }
    
    public void toggleItalic() {
        this.italic = !italic;
        System.out.println("切换斜体: " + italic);
        // 这里可以记录状态变化
    }
    
    public void toggleUnderline() {
        this.underline = !underline;
        System.out.println("切换下划线: " + underline);
        // 这里可以记录状态变化
    }
    
    // 创建增量备忘录
    public DocumentMemento createIncrementalMemento() {
        DocumentMemento memento = new DocumentMemento();
        // 在实际应用中,这里会记录自上次备忘录以来的所有变化
        memento.addChange(new StateChange("title", null, title));
        memento.addChange(new StateChange("content", null, content));
        memento.addChange(new StateChange("font", null, font));
        memento.addChange(new StateChange("fontSize", null, fontSize));
        memento.addChange(new StateChange("color", null, color));
        memento.addChange(new StateChange("bold", null, bold));
        memento.addChange(new StateChange("italic", null, italic));
        memento.addChange(new StateChange("underline", null, underline));
        
        System.out.println("创建增量备忘录");
        return memento;
    }
    
    // 从增量备忘录恢复
    public void restoreFromIncrementalMemento(DocumentMemento memento) {
        System.out.println("从增量备忘录恢复:");
        for (StateChange change : memento.getChanges()) {
            System.out.println("  " + change);
            // 在实际应用中,这里会根据变化类型恢复相应的状态
        }
        System.out.println("恢复完成");
    }
    
    // 创建完整备忘录(快照)
    public DocumentMemento createFullMemento() {
        return createIncrementalMemento(); // 在实际应用中会有所不同
    }
    
    public void displayDocumentInfo() {
        System.out.println("\n=== 文档信息 ===");
        System.out.println("标题: " + title);
        System.out.println("内容: " + content.substring(0, Math.min(content.length(), 30)) + "...");
        System.out.println("字体: " + font + ", 大小: " + fontSize + ", 颜色: " + color);
        System.out.println("格式: " + (bold ? "粗体" : "") + " " + (italic ? "斜体" : "") + " " + (underline ? "下划线" : ""));
    }
}

// 文档历史管理器 - Document History Manager (Caretaker)
public class DocumentHistoryManager {
    private final Stack<DocumentMemento> undoStack;
    private final Stack<DocumentMemento> redoStack;
    private final int maxUndoLevels;
    
    public DocumentHistoryManager() {
        this(20); // 默认支持20级撤销
    }
    
    public DocumentHistoryManager(int maxUndoLevels) {
        this.undoStack = new Stack<>();
        this.redoStack = new Stack<>();
        this.maxUndoLevels = maxUndoLevels;
    }
    
    public void saveState(DocumentMemento memento) {
        if (undoStack.size() >= maxUndoLevels) {
            undoStack.remove(0); // 移除最旧的状态
        }
        undoStack.push(memento);
        redoStack.clear(); // 保存新状态后清空重做栈
        System.out.println("保存文档状态");
    }
    
    public DocumentMemento undo() {
        if (undoStack.isEmpty()) {
            throw new IllegalStateException("没有可撤销的状态");
        }
        
        DocumentMemento current = undoStack.pop();
        redoStack.push(current);
        
        if (undoStack.isEmpty()) {
            throw new IllegalStateException("已经是最初状态");
        }
        
        DocumentMemento previous = undoStack.peek();
        System.out.println("执行撤销操作");
        return previous;
    }
    
    public DocumentMemento redo() {
        if (redoStack.isEmpty()) {
            throw new IllegalStateException("没有可重做的状态");
        }
        
        DocumentMemento memento = redoStack.pop();
        undoStack.push(memento);
        System.out.println("执行重做操作");
        return memento;
    }
    
    public boolean canUndo() {
        return undoStack.size() > 1; // 至少有一个状态可以撤销到
    }
    
    public boolean canRedo() {
        return !redoStack.isEmpty();
    }
    
    public void clearHistory() {
        undoStack.clear();
        redoStack.clear();
        System.out.println("清空历史记录");
    }
    
    public int getUndoStackSize() {
        return undoStack.size();
    }
    
    public int getRedoStackSize() {
        return redoStack.size();
    }
}

// 客户端使用 - Client Usage
public class IncrementalMementoDemo {
    public static void main(String[] args) {
        System.out.println("=== 增量备忘录模式演示 ===");
        
        DocumentEditor editor = new DocumentEditor();
        DocumentHistoryManager history = new DocumentHistoryManager();
        
        // 初始状态
        editor.displayDocumentInfo();
        history.saveState(editor.createFullMemento());
        
        // 编辑文档
        System.out.println("\n--- 编辑文档 ---");
        editor.setTitle("我的文档");
        editor.setContent("这是一个测试文档,用于演示备忘录模式。");
        editor.setFont("Times New Roman");
        editor.setFontSize(14);
        editor.toggleBold();
        history.saveState(editor.createIncrementalMemento());
        
        // 继续编辑
        System.out.println("\n--- 继续编辑 ---");
        editor.setColor("Blue");
        editor.toggleItalic();
        editor.setContent("这是一个测试文档,用于演示备忘录模式。现在内容更丰富了。");
        history.saveState(editor.createIncrementalMemento());
        
        editor.displayDocumentInfo();
        
        // 撤销操作
        System.out.println("\n--- 撤销操作 ---");
        if (history.canUndo()) {
            editor.restoreFromIncrementalMemento(history.undo());
            editor.displayDocumentInfo();
        }
        
        // 重做操作
        System.out.println("\n--- 重做操作 ---");
        if (history.canRedo()) {
            editor.restoreFromIncrementalMemento(history.redo());
            editor.displayDocumentInfo();
        }
        
        System.out.println("\n撤销栈大小: " + history.getUndoStackSize());
        System.out.println("重做栈大小: " + history.getRedoStackSize());
    }
}

框架源码中的应用 (Framework Applications)

1. Java Serializable - 序列化机制

// Java序列化中的备忘录模式应用
// Java Serialization Memento Pattern Application

// 可序列化的游戏状态 - Serializable Game State
public class GameState implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String playerName;
    private int level;
    private int score;
    private double playerX;
    private double playerY;
    private List<String> inventory;
    private Map<String, Integer> achievements;
    
    public GameState(String playerName) {
        this.playerName = playerName;
        this.level = 1;
        this.score = 0;
        this.playerX = 0.0;
        this.playerY = 0.0;
        this.inventory = new ArrayList<>();
        this.achievements = new HashMap<>();
    }
    
    // 游戏操作方法
    public void movePlayer(double dx, double dy) {
        playerX += dx;
        playerY += dy;
        System.out.printf("玩家移动到 (%.1f, %.1f)%n", playerX, playerY);
    }
    
    public void addScore(int points) {
        score += points;
        System.out.println("获得分数: " + points + ", 总分数: " + score);
    }
    
    public void levelUp() {
        level++;
        System.out.println("升级!当前等级: " + level);
    }
    
    public void addItem(String item) {
        inventory.add(item);
        System.out.println("获得物品: " + item);
    }
    
    public void unlockAchievement(String achievement, int points) {
        achievements.put(achievement, points);
        addScore(points);
        System.out.println("解锁成就: " + achievement);
    }
    
    // 创建备忘录(序列化)
    public byte[] createMemento() throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(this);
        oos.close();
        System.out.println("创建游戏状态备忘录");
        return baos.toByteArray();
    }
    
    // 从备忘录恢复(反序列化)
    public static GameState restoreFromMemento(byte[] mementoData) throws IOException, ClassNotFoundException {
        ByteArrayInputStream bais = new ByteArrayInputStream(mementoData);
        ObjectInputStream ois = new ObjectInputStream(bais);
        GameState state = (GameState) ois.readObject();
        ois.close();
        System.out.println("从备忘录恢复游戏状态");
        return state;
    }
    
    public void displayStatus() {
        System.out.println("\n=== 游戏状态 ===");
        System.out.println("玩家: " + playerName);
        System.out.println("等级: " + level);
        System.out.println("分数: " + score);
        System.out.println("位置: (" + playerX + ", " + playerY + ")");
        System.out.println("背包: " + inventory);
        System.out.println("成就: " + achievements);
    }
    
    @Override
    public String toString() {
        return String.format("GameState{%s, Lv.%d, Score:%d, (%.1f,%.1f), Items:%d}", 
                           playerName, level, score, playerX, playerY, inventory.size());
    }
}

// 游戏存档管理器 - Game Save Manager (Caretaker)
public class SerializableGameSaveManager {
    private final Map<String, byte[]> saveSlots;
    private final String saveDirectory;
    
    public SerializableGameSaveManager(String saveDirectory) {
        this.saveSlots = new LinkedHashMap<>();
        this.saveDirectory = saveDirectory;
        ensureSaveDirectoryExists();
    }
    
    private void ensureSaveDirectoryExists() {
        File dir = new File(saveDirectory);
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }
    
    public void saveGame(String slotName, GameState gameState) throws IOException {
        byte[] mementoData = gameState.createMemento();
        saveSlots.put(slotName, mementoData);
        
        // 同时保存到文件
        String fileName = saveDirectory + File.separator + slotName + ".sav";
        try (FileOutputStream fos = new FileOutputStream(fileName)) {
            fos.write(mementoData);
        }
        
        System.out.println("游戏已保存到槽位: " + slotName);
    }
    
    public GameState loadGame(String slotName) throws IOException, ClassNotFoundException {
        byte[] mementoData = saveSlots.get(slotName);
        if (mementoData == null) {
            // 尝试从文件加载
            String fileName = saveDirectory + File.separator + slotName + ".sav";
            File file = new File(fileName);
            if (file.exists()) {
                mementoData = Files.readAllBytes(file.toPath());
                saveSlots.put(slotName, mementoData);
            } else {
                throw new IllegalArgumentException("存档不存在: " + slotName);
            }
        }
        
        System.out.println("从槽位加载游戏: " + slotName);
        return GameState.restoreFromMemento(mementoData);
    }
    
    public void autoSave(GameState gameState) throws IOException {
        String autoSaveName = "auto_save_" + System.currentTimeMillis();
        saveGame(autoSaveName, gameState);
        System.out.println("自动保存完成: " + autoSaveName);
    }
    
    public List<String> getSaveSlots() {
        return new ArrayList<>(saveSlots.keySet());
    }
    
    public void deleteSave(String slotName) {
        saveSlots.remove(slotName);
        
        // 同时删除文件
        String fileName = saveDirectory + File.separator + slotName + ".sav";
        File file = new File(fileName);
        if (file.exists()) {
            file.delete();
        }
        
        System.out.println("删除存档: " + slotName);
    }
}

// 客户端使用 - Client Usage
public class SerializableMementoDemo {
    public static void main(String[] args) {
        System.out.println("=== Java序列化备忘录模式演示 ===");
        
        try {
            GameState game = new GameState("勇者");
            SerializableGameSaveManager saveManager = new SerializableGameSaveManager("saves");
            
            // 初始状态
            game.displayStatus();
            
            // 游戏过程
            System.out.println("\n--- 开始游戏 ---");
            game.movePlayer(10, 5);
            game.addScore(100);
            game.addItem("生命药水");
            game.unlockAchievement("首次移动", 50);
            saveManager.saveGame("checkpoint_1", game);
            
            game.movePlayer(20, 10);
            game.addScore(200);
            game.levelUp();
            game.addItem("铁剑");
            game.unlockAchievement("首次升级", 100);
            saveManager.saveGame("checkpoint_2", game);
            
            // 创建游戏副本进行实验
            System.out.println("\n--- 创建游戏副本进行实验 ---");
            GameState experimentalGame = GameState.restoreFromMemento(game.createMemento());
            experimentalGame.displayStatus();
            
            // 在副本中进行危险操作
            System.out.println("\n--- 在副本中进行危险操作 ---");
            experimentalGame.movePlayer(100, 50);
            experimentalGame.addScore(500);
            experimentalGame.takeDamage(80);
            experimentalGame.displayStatus();
            
            // 实验失败,恢复原状态
            System.out.println("\n--- 实验失败,恢复原状态 ---");
            game.displayStatus();
            
            // 继续正常游戏
            System.out.println("\n--- 继续正常游戏 ---");
            game.movePlayer(15, 8);
            game.addScore(150);
            saveManager.autoSave(game);
            
            game.displayStatus();
            
            // 显示存档信息
            System.out.println("\n可用存档:");
            saveManager.getSaveSlots().forEach(System.out::println);
            
        } catch (Exception e) {
            System.err.println("游戏存档操作失败: " + e.getMessage());
            e.printStackTrace();
        }
        
        System.out.println("\n=== Java序列化备忘录模式演示完成 ===");
    }
}

2. Spring State Machine - 状态机

// Spring状态机中的备忘录模式应用
// Spring State Machine Memento Pattern Application

// 订单状态枚举 - Order State Enum
public enum OrderState {
    CREATED, PAID, SHIPPED, DELIVERED, CANCELLED
}

// 订单事件枚举 - Order Event Enum
public enum OrderEvent {
    PAY, SHIP, DELIVER, CANCEL
}

// 订单状态机配置 - Order State Machine Configuration
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderState, OrderEvent> {
    
    @Override
    public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states) throws Exception {
        states
            .withStates()
                .initial(OrderState.CREATED)
                .state(OrderState.CREATED)
                .state(OrderState.PAID)
                .state(OrderState.SHIPPED)
                .state(OrderState.DELIVERED)
                .end(OrderState.CANCELLED);
    }
    
    @Override
    public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions) throws Exception {
        transitions
            .withExternal().source(OrderState.CREATED).target(OrderState.PAID).event(OrderEvent.PAY)
            .and()
            .withExternal().source(OrderState.PAID).target(OrderState.SHIPPED).event(OrderEvent.SHIP)
            .and()
            .withExternal().source(OrderState.SHIPPED).target(OrderState.DELIVERED).event(OrderEvent.DELIVER)
            .and()
            .withExternal().source(OrderState.CREATED).target(OrderState.CANCELLED).event(OrderEvent.CANCEL)
            .and()
            .withExternal().source(OrderState.PAID).target(OrderState.CANCELLED).event(OrderEvent.CANCEL);
    }
}

// 订单实体 - Order Entity
@Entity
@Table(name = "orders")
public class Order {
    @Id
    private String orderId;
    private String customerName;
    private BigDecimal amount;
    
    @Enumerated(EnumType.STRING)
    private OrderState state;
    
    @Column(columnDefinition = "TEXT")
    private String stateMachineContext; // 存储状态机上下文(备忘录)
    
    // 构造函数、getters和setters
    public Order() {}
    
    public Order(String orderId, String customerName, BigDecimal amount) {
        this.orderId = orderId;
        this.customerName = customerName;
        this.amount = amount;
        this.state = OrderState.CREATED;
    }
    
    // Getters and setters
    public String getOrderId() { return orderId; }
    public void setOrderId(String orderId) { this.orderId = orderId; }
    public String getCustomerName() { return customerName; }
    public void setCustomerName(String customerName) { this.customerName = customerName; }
    public BigDecimal getAmount() { return amount; }
    public void setAmount(BigDecimal amount) { this.amount = amount; }
    public OrderState getState() { return state; }
    public void setState(OrderState state) { this.state = state; }
    public String getStateMachineContext() { return stateMachineContext; }
    public void setStateMachineContext(String stateMachineContext) { this.stateMachineContext = stateMachineContext; }
}

// 订单服务 - Order Service
@Service
public class OrderService {
    private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
    private final StateMachine<OrderState, OrderEvent> stateMachine;
    private final OrderRepository orderRepository;
    
    @Autowired
    public OrderService(StateMachine<OrderState, OrderEvent> stateMachine, 
                       OrderRepository orderRepository) {
        this.stateMachine = stateMachine;
        this.orderRepository = orderRepository;
    }
    
    // 创建备忘录(保存状态机上下文)
    public String createStateMachineMemento() {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            stateMachine.getExtendedState().getVariables().forEach((key, value) -> {
                try {
                    oos.writeObject(new AbstractMap.SimpleEntry<>(key, value));
                } catch (IOException e) {
                    logger.error("序列化状态机变量失败", e);
                }
            });
            oos.close();
            return Base64.getEncoder().encodeToString(baos.toByteArray());
        } catch (IOException e) {
            logger.error("创建状态机备忘录失败", e);
            throw new RuntimeException("创建备忘录失败", e);
        }
    }
    
    // 从备忘录恢复(恢复状态机上下文)
    public void restoreStateMachineFromMemento(String mementoData) {
        try {
            byte[] data = Base64.getDecoder().decode(mementoData);
            ByteArrayInputStream bais = new ByteArrayInputStream(data);
            ObjectInputStream ois = new ObjectInputStream(bais);
            
            ExtendedState extendedState = stateMachine.getExtendedState();
            while (bais.available() > 0) {
                try {
                    @SuppressWarnings("unchecked")
                    Map.Entry<String, Object> entry = (Map.Entry<String, Object>) ois.readObject();
                    extendedState.getVariables().put(entry.getKey(), entry.getValue());
                } catch (EOFException e) {
                    break;
                }
            }
            ois.close();
            System.out.println("从备忘录恢复状态机上下文");
        } catch (IOException | ClassNotFoundException e) {
            logger.error("恢复状态机备忘录失败", e);
            throw new RuntimeException("恢复备忘录失败", e);
        }
    }
    
    // 处理订单事件
    @Transactional
    public void processOrderEvent(String orderId, OrderEvent event) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new IllegalArgumentException("订单不存在: " + orderId));
        
        // 恢复状态机状态
        if (order.getStateMachineContext() != null) {
            restoreStateMachineFromMemento(order.getStateMachineContext());
        }
        
        // 设置当前状态
        stateMachine.stop();
        stateMachine.getStateMachineAccessor().doWithAllRegions(access -> 
            access.resetStateMachine(new DefaultStateMachineContext<>(order.getState(), null, null, null)));
        
        // 发送事件
        Message<OrderEvent> message = MessageBuilder
            .withPayload(event)
            .setHeader("orderId", orderId)
            .build();
        
        stateMachine.start();
        stateMachine.sendEvent(message);
        
        // 获取新状态
        OrderState newState = stateMachine.getState().getId();
        order.setState(newState);
        
        // 保存状态机上下文(创建备忘录)
        String newContext = createStateMachineMemento();
        order.setStateMachineContext(newContext);
        
        orderRepository.save(order);
        
        logger.info("订单状态更新: {} -> {} for order: {}", order.getState(), newState, orderId);
    }
    
    // 创建订单并初始化状态机
    @Transactional
    public Order createOrder(String orderId, String customerName, BigDecimal amount) {
        Order order = new Order(orderId, customerName, amount);
        
        // 初始化状态机
        stateMachine.stop();
        stateMachine.start();
        
        // 创建初始备忘录
        String initialContext = createStateMachineMemento();
        order.setStateMachineContext(initialContext);
        
        order = orderRepository.save(order);
        logger.info("创建订单: {}", orderId);
        return order;
    }
    
    // 获取订单当前状态
    public OrderState getOrderState(String orderId) {
        return orderRepository.findById(orderId)
            .map(Order::getState)
            .orElseThrow(() -> new IllegalArgumentException("订单不存在: " + orderId));
    }
}

// 客户端使用 - Client Usage
@SpringBootApplication
public class SpringStateMachineMementoDemo {
    
    public static void main(String[] args) {
        SpringApplication.run(SpringStateMachineMementoDemo.class, args);
        
        System.out.println("=== Spring状态机备忘录模式演示 ===");
        System.out.println("启动Spring Boot应用,测试状态机备忘录功能");
    }
    
    @Component
    public static class StateMachineMementoDemoRunner implements CommandLineRunner {
        
        @Autowired
        private OrderService orderService;
        
        @Override
        public void run(String... args) throws Exception {
            System.out.println("\n--- 开始Spring状态机备忘录模式演示 ---");
            
            // 创建订单
            String orderId = "ORDER-" + System.currentTimeMillis();
            Order order = orderService.createOrder(orderId, "张三", new BigDecimal("299.99"));
            System.out.println("订单初始状态: " + order.getState());
            
            // 处理订单事件
            System.out.println("\n--- 处理订单事件 ---");
            orderService.processOrderEvent(orderId, OrderEvent.PAY);
            System.out.println("支付后状态: " + orderService.getOrderState(orderId));
            
            orderService.processOrderEvent(orderId, OrderEvent.SHIP);
            System.out.println("发货后状态: " + orderService.getOrderState(orderId));
            
            orderService.processOrderEvent(orderId, OrderEvent.DELIVER);
            System.out.println("送达后状态: " + orderService.getOrderState(orderId));
            
            System.out.println("\nSpring状态机备忘录模式演示完成");
        }
    }
}

备忘录模式与其他模式比较 (Comparison with Other Patterns)

特性 备忘录模式 命令模式 原型模式 状态模式
目的 状态保存恢复 请求封装 对象复制 状态管理
关注点 状态快照 操作记录 对象克隆 状态转换
使用场景 撤销/重做 撤销/重做 对象复制 状态机
封装性 保持封装 可暴露操作 暴露克隆 状态封装
实现复杂度 中等 中等

最佳实践 (Best Practices)

1. 备忘录设计 (Memento Design)

  • 保持备忘录的不可变性
  • 只保存必要的状态信息
  • 考虑使用窄接口保护原发器的封装性

2. 原发器设计 (Originator Design)

  • 明确定义需要保存的状态
  • 提供清晰的创建和恢复方法
  • 考虑状态的验证和一致性检查

3. 管理者设计 (Caretaker Design)

  • 合理管理备忘录的生命周期
  • 考虑使用栈结构支持撤销/重做
  • 实现适当的内存管理策略

4. 性能优化 (Performance Optimization)

  • 使用增量备忘录减少内存占用
  • 考虑使用对象池重用备忘录对象
  • 实现适当的清理机制

5. 错误处理 (Error Handling)

  • 处理备忘录创建和恢复过程中的异常
  • 提供状态验证机制
  • 考虑实现回滚机制

优缺点 (Pros and Cons)

优点 (Advantages)

  • 保持封装性:不破坏对象的封装边界
  • 简化原发器:原发器不需要管理多个版本的状态
  • 支持撤销/重做:可以轻松实现撤销和重做功能
  • 状态历史:可以维护对象的状态历史
  • 检查点机制:支持创建状态检查点

缺点 (Disadvantages)

  • 内存消耗:保存大量状态可能消耗大量内存
  • 性能开销:创建和恢复备忘录需要时间
  • 实现复杂:需要仔细设计备忘录接口
  • 循环引用:可能导致循环引用问题
  • 状态一致性:需要确保状态的一致性

实际应用建议 (Practical Application Tips)

1. 状态选择

  • 只保存必要的状态信息
  • 避免保存冗余或计算得出的状态
  • 考虑状态的粒度和精度

2. 内存管理

  • 实现适当的清理机制
  • 考虑使用压缩技术减少内存占用
  • 合理设置备忘录数量限制

3. 并发处理

  • 在并发环境下确保线程安全
  • 考虑使用不可变对象
  • 处理好状态的同步问题

4. 测试策略

  • 测试备忘录的创建和恢复
  • 验证状态的一致性
  • 测试边界情况和异常处理

总结 (Summary)

备忘录模式是一种优雅的行为型设计模式,它通过在不破坏封装性的前提下捕获和保存对象的内部状态,为撤销/重做、状态回滚和历史记录等功能提供了强大的支持。这种模式在现代软件系统中有着广泛的应用,从文本编辑器的撤销功能到游戏存档系统,再到复杂的状态管理。

在实际应用中,备忘录模式特别适合以下场景:

  • 需要实现撤销/重做功能的应用程序
  • 需要保存对象历史状态的系统
  • 需要支持状态回滚的事务处理
  • 需要创建状态检查点的长时间运行程序
  • 需要实现状态快照的调试工具

通过合理使用备忘录模式,可以构建出功能丰富、用户友好的系统。然而,需要注意内存管理和性能优化,避免过度使用导致系统资源消耗过大。在设计时应该权衡好功能需求和系统资源之间的关系,确保系统的可扩展性和可维护性。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐