欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 建筑 > 备忘录模式(Memento Pattern)详解

备忘录模式(Memento Pattern)详解

2025/5/9 18:34:24 来源:https://blog.csdn.net/yk_dflkg/article/details/147342147  浏览:    关键词:备忘录模式(Memento Pattern)详解

文章目录

    • 1. 什么是备忘录模式?
    • 2. 为什么需要备忘录模式?
    • 3. 备忘录模式的核心概念
    • 4. 备忘录模式的结构
    • 5. 备忘录模式的基本实现
      • 5.1 简单的文本编辑器示例
    • 6. 备忘录模式的进阶实现
      • 6.1 游戏角色状态保存示例
      • 6.2 备忘录模式的不同实现方式
        • 黑箱模式
        • 白箱模式
    • 7. 备忘录模式的复杂实现
      • 7.1 多状态备忘录
      • 7.2 使用内部类实现
    • 8. 备忘录模式在Java中的实际应用
      • 8.1 Java中的序列化与备忘录模式
      • 8.2 Java Swing中的撤销/重做功能
    • 9. 备忘录模式与其他设计模式的结合
      • 9.1 备忘录模式与命令模式
    • 10. 备忘录模式的优缺点
      • 10.1 优点
      • 10.2 缺点
    • 11. 备忘录模式的适用场景
    • 12. 备忘录模式与其他模式的比较
      • 12.1 备忘录模式 vs 原型模式
      • 12.2 备忘录模式 vs 命令模式
      • 12.3 备忘录模式 vs 状态模式
    • 13. 备忘录模式的常见问题与解决方案
      • 13.1 如何减少备忘录模式的内存消耗?
      • 13.2 如何实现多层撤销/重做?
      • 13.3 如何处理复杂对象的状态?
    • 14. 总结
      • 14.1 核心要点
      • 14.2 设计建议

1. 什么是备忘录模式?

备忘录模式是一种行为型设计模式,它允许在不破坏对象封装性的前提下,捕获对象的内部状态,并在需要时将对象恢复到这个状态。简单来说,备忘录模式就是用来实现"撤销"和"恢复"功能的一种设计模式。

备忘录模式使得程序可以回到某个历史状态,就像"时间旅行"一样,让我们能够重返过去的某个时间点。

2. 为什么需要备忘录模式?

在以下情况下,备忘录模式特别有用:

  1. 需要保存和恢复对象的状态:当我们需要提供撤销操作或回滚机制时
  2. 直接访问对象的成员变量会破坏封装性:我们需要保存对象状态,但又不希望破坏封装性
  3. 需要保存对象状态的快照:当我们需要暂时保存某些状态以便后期恢复时
  4. 事务回滚:当执行一系列操作后需要回滚到原始状态时
  5. 状态历史记录:当需要维护对象历史状态以便浏览或回溯时

3. 备忘录模式的核心概念

备忘录模式涉及三个核心角色:

  1. 发起人(Originator)

    • 需要保存状态的对象
    • 负责创建一个备忘录,存储自身的内部状态
    • 可以使用备忘录恢复自身状态
  2. 备忘录(Memento)

    • 存储发起人的内部状态
    • 只能被发起人访问,对其他对象不可见
    • 保护内部状态不被外部修改
  3. 管理者(Caretaker)

    • 负责保存备忘录
    • 不能对备忘录的内容进行操作或检查
    • 仅仅是一个存储备忘录的容器

4. 备忘录模式的结构

备忘录模式的UML类图如下:

+---------------+       创建       +---------------+
| Originator    |--------------->| Memento        |
+---------------+                +---------------+
| state         |                | state         |
| createMemento()|               +---------------+
| restoreMemento()|                    ^
+---------------+                      |^                              ||                              ||         保存/获取             ||                              |
+---------------+                      |
| Caretaker     |----------------------+
+---------------+
| mementoList   |
+---------------+

5. 备忘录模式的基本实现

5.1 简单的文本编辑器示例

下面是一个简单的文本编辑器示例,展示了备忘录模式的基本实现。

首先,定义备忘录类:

// 备忘录类 - 存储文本编辑器的状态
public class TextEditorMemento {private final String text;public TextEditorMemento(String text) {this.text = text;}// 只允许发起人访问状态protected String getText() {return text;}
}

然后,定义发起人类(文本编辑器):

// 发起人类 - 文本编辑器
public class TextEditor {private String text;public void setText(String text) {this.text = text;}public String getText() {return text;}// 创建备忘录,保存当前状态public TextEditorMemento save() {return new TextEditorMemento(text);}// 从备忘录恢复状态public void restore(TextEditorMemento memento) {this.text = memento.getText();}
}

最后,定义管理者类:

// 管理者类 - 维护备忘录历史
public class TextEditorCaretaker {private final List<TextEditorMemento> history = new ArrayList<>();private int currentIndex = -1;// 保存备忘录public void save(TextEditorMemento memento) {// 如果当前不是最后一个状态,则删除当前状态之后的所有状态if (currentIndex < history.size() - 1) {history.subList(currentIndex + 1, history.size()).clear();}history.add(memento);currentIndex = history.size() - 1;}// 撤销操作,返回上一个状态public TextEditorMemento undo() {if (currentIndex <= 0) {return null; // 无法撤销}currentIndex--;return history.get(currentIndex);}// 重做操作,恢复到下一个状态public TextEditorMemento redo() {if (currentIndex >= history.size() - 1) {return null; // 无法重做}currentIndex++;return history.get(currentIndex);}
}

创建一个测试类,演示备忘录模式:

public class TextEditorDemo {public static void main(String[] args) {// 创建文本编辑器(发起人)TextEditor editor = new TextEditor();// 创建历史记录管理者(管理者)TextEditorCaretaker caretaker = new TextEditorCaretaker();// 编辑文本并保存状态editor.setText("这是第一行文本");caretaker.save(editor.save());System.out.println("当前文本: " + editor.getText());// 继续编辑editor.setText("这是第一行文本\n这是第二行文本");caretaker.save(editor.save());System.out.println("当前文本: " + editor.getText());// 再次编辑editor.setText("这是第一行文本\n这是第二行文本\n这是第三行文本");caretaker.save(editor.save());System.out.println("当前文本: " + editor.getText());// 执行撤销操作TextEditorMemento previousState = caretaker.undo();if (previousState != null) {editor.restore(previousState);System.out.println("\n执行撤销后,文本变为: \n" + editor.getText());}// 再次撤销previousState = caretaker.undo();if (previousState != null) {editor.restore(previousState);System.out.println("\n再次撤销后,文本变为: \n" + editor.getText());}// 执行重做TextEditorMemento nextState = caretaker.redo();if (nextState != null) {editor.restore(nextState);System.out.println("\n执行重做后,文本变为: \n" + editor.getText());}// 再次重做nextState = caretaker.redo();if (nextState != null) {editor.restore(nextState);System.out.println("\n再次重做后,文本变为: \n" + editor.getText());}}
}

输出结果:

当前文本: 这是第一行文本
当前文本: 这是第一行文本
这是第二行文本
当前文本: 这是第一行文本
这是第二行文本
这是第三行文本执行撤销后,文本变为: 
这是第一行文本
这是第二行文本再次撤销后,文本变为: 
这是第一行文本执行重做后,文本变为: 
这是第一行文本
这是第二行文本再次重做后,文本变为: 
这是第一行文本
这是第二行文本
这是第三行文本

6. 备忘录模式的进阶实现

6.1 游戏角色状态保存示例

下面我们实现一个更复杂的例子:游戏角色状态的保存和恢复。

首先,定义备忘录类:

// 游戏角色状态备忘录
public class RoleStateMemento {private final int vitality;    // 生命力private final int attack;      // 攻击力private final int defense;     // 防御力public RoleStateMemento(int vitality, int attack, int defense) {this.vitality = vitality;this.attack = attack;this.defense = defense;}// 提供保护性的访问方法protected int getVitality() {return vitality;}protected int getAttack() {return attack;}protected int getDefense() {return defense;}
}

然后,定义发起人类(游戏角色):

// 游戏角色类(发起人)
public class GameRole {private String name;           // 角色名称private int vitality;          // 生命力private int attack;            // 攻击力private int defense;           // 防御力public GameRole(String name) {this.name = name;this.vitality = 100;       // 初始生命值this.attack = 50;          // 初始攻击力this.defense = 30;         // 初始防御力}// 创建备忘录,保存当前状态public RoleStateMemento saveState() {return new RoleStateMemento(vitality, attack, defense);}// 从备忘录恢复状态public void restoreState(RoleStateMemento memento) {this.vitality = memento.getVitality();this.attack = memento.getAttack();this.defense = memento.getDefense();}// 战斗,消耗生命值和防御力,提高攻击力public void fight() {this.vitality -= 30;this.attack += 20;this.defense -= 10;}// 使用药水,恢复生命值public void usePotion() {this.vitality += 40;}// 显示角色状态public void displayState() {System.out.println("角色: " + name);System.out.println("生命力: " + vitality);System.out.println("攻击力: " + attack);System.out.println("防御力: " + defense);System.out.println("--------------------");}
}

最后,定义管理者类:

// 游戏存档管理器(管理者)
public class GameStateCaretaker {private Map<String, RoleStateMemento> saveSlots = new HashMap<>();// 保存游戏状态到指定存档public void saveState(String slotName, RoleStateMemento memento) {saveSlots.put(slotName, memento);}// 从指定存档加载游戏状态public RoleStateMemento loadState(String slotName) {return saveSlots.get(slotName);}// 显示所有存档public void displaySaveSlots() {System.out.println("可用存档: ");for (String slotName : saveSlots.keySet()) {System.out.println("- " + slotName);}System.out.println("--------------------");}
}

创建测试类,演示游戏存档功能:

public class GameDemo {public static void main(String[] args) {// 创建游戏角色GameRole knight = new GameRole("骑士");// 创建游戏存档管理器GameStateCaretaker caretaker = new GameStateCaretaker();// 显示初始状态System.out.println("游戏开始,初始状态:");knight.displayState();// 保存初始状态caretaker.saveState("初始存档", knight.saveState());// 进行一场战斗System.out.println("进行一场战斗后:");knight.fight();knight.displayState();// 保存战斗后状态caretaker.saveState("战斗后存档", knight.saveState());// 使用药水恢复System.out.println("使用药水恢复后:");knight.usePotion();knight.displayState();// 保存使用药水后状态caretaker.saveState("恢复后存档", knight.saveState());// 显示所有存档caretaker.displaySaveSlots();// 从初始存档加载System.out.println("加载初始存档:");knight.restoreState(caretaker.loadState("初始存档"));knight.displayState();// 从战斗后存档加载System.out.println("加载战斗后存档:");knight.restoreState(caretaker.loadState("战斗后存档"));knight.displayState();// 从恢复后存档加载System.out.println("加载恢复后存档:");knight.restoreState(caretaker.loadState("恢复后存档"));knight.displayState();}
}

输出结果:

游戏开始,初始状态:
角色: 骑士
生命力: 100
攻击力: 50
防御力: 30
--------------------进行一场战斗后:
角色: 骑士
生命力: 70
攻击力: 70
防御力: 20
--------------------使用药水恢复后:
角色: 骑士
生命力: 110
攻击力: 70
防御力: 20
--------------------可用存档: 
- 战斗后存档
- 初始存档
- 恢复后存档
--------------------加载初始存档:
角色: 骑士
生命力: 100
攻击力: 50
防御力: 30
--------------------加载战斗后存档:
角色: 骑士
生命力: 70
攻击力: 70
防御力: 20
--------------------加载恢复后存档:
角色: 骑士
生命力: 110
攻击力: 70
防御力: 20
--------------------

6.2 备忘录模式的不同实现方式

备忘录模式有两种主要实现方式:黑箱模式和白箱模式。

黑箱模式

在黑箱实现中,备忘录类对外隐藏所有状态信息,只有发起人可以访问这些信息。

// 黑箱模式的备忘录类
public class BlackBoxMemento {// 私有的状态字段private final String state;public BlackBoxMemento(String state) {this.state = state;}// 外部类只能看到这是一个备忘录对象,无法访问其内容// 只有发起人可以通过强制类型转换恢复状态
}// 发起人类
public class Originator {private String state;public void setState(String state) {this.state = state;}// 创建备忘录public BlackBoxMemento saveToMemento() {return new BlackBoxMemento(state);}// 恢复状态 - 发起人知道备忘录的内部结构public void restoreFromMemento(BlackBoxMemento memento) {// 此处发起人可以访问备忘录内部状态this.state = ((BlackBoxMemento)memento).state;}
}
白箱模式

在白箱实现中,备忘录类提供公共的接口来访问其状态,但这样做会破坏封装性。

// 白箱模式的备忘录类
public class WhiteBoxMemento {private String state;public WhiteBoxMemento(String state) {this.state = state;}// 提供公共访问方法public String getState() {return state;}public void setState(String state) {this.state = state;}
}// 发起人类
public class Originator {private String state;public void setState(String state) {this.state = state;}// 创建备忘录public WhiteBoxMemento saveToMemento() {return new WhiteBoxMemento(state);}// 恢复状态public void restoreFromMemento(WhiteBoxMemento memento) {this.state = memento.getState();}
}

一般来说,黑箱模式更符合封装原则,但在Java中实现起来可能会更复杂。而白箱模式虽然违反了封装原则,但实现更简单直接。

7. 备忘录模式的复杂实现

7.1 多状态备忘录

有时我们需要保存对象的多个属性,并且希望能够选择性地恢复这些属性。下面是一个多状态备忘录的实现示例:

// 多状态备忘录类 - 可以选择性保存和恢复状态
public class DocumentMemento {private final String content;        // 文档内容private final String title;          // 文档标题private final String formatting;     // 文档格式private final Date lastModified;     // 最后修改时间// 构造函数public DocumentMemento(String content, String title, String formatting, Date lastModified) {this.content = content;this.title = title;this.formatting = formatting;this.lastModified = lastModified != null ? new Date(lastModified.getTime()) : null;}// 获取状态的保护性方法protected String getContent() {return content;}protected String getTitle() {return title;}protected String getFormatting() {return formatting;}protected Date getLastModified() {return lastModified != null ? new Date(lastModified.getTime()) : null;}
}

发起人类(文档):

// 文档类 - 发起人
public class Document {private String content;        // 文档内容private String title;          // 文档标题private String formatting;     // 文档格式private Date lastModified;     // 最后修改时间public Document(String title) {this.title = title;this.content = "";this.formatting = "normal";this.lastModified = new Date();}// 添加内容public void addContent(String newContent) {this.content += newContent;this.lastModified = new Date();}// 改变标题public void changeTitle(String newTitle) {this.title = newTitle;this.lastModified = new Date();}// 改变格式public void changeFormatting(String newFormatting) {this.formatting = newFormatting;this.lastModified = new Date();}// 显示文档信息public void display() {System.out.println("文档: " + title);System.out.println("最后修改时间: " + lastModified);System.out.println("格式: " + formatting);System.out.println("内容: \n" + content);System.out.println("--------------------");}// 创建完整备忘录public DocumentMemento createFullMemento() {return new DocumentMemento(content, title, formatting, lastModified);}// 创建只包含内容的备忘录public DocumentMemento createContentMemento() {return new DocumentMemento(content, title, null, null);}// 创建只包含格式的备忘录public DocumentMemento createFormattingMemento() {return new DocumentMemento(null, null, formatting, null);}// 从备忘录恢复状态 - 选择性恢复public void restoreFromMemento(DocumentMemento memento) {if (memento.getContent() != null) {this.content = memento.getContent();}if (memento.getTitle() != null) {this.title = memento.getTitle();}if (memento.getFormatting() != null) {this.formatting = memento.getFormatting();}if (memento.getLastModified() != null) {this.lastModified = memento.getLastModified();} else {// 如果没有恢复时间,则更新为当前时间this.lastModified = new Date();}}
}

管理者类:

// 文档版本管理器 - 管理者
public class DocumentCaretaker {// 使用Map存储不同类型的备忘录private Map<String, List<DocumentMemento>> versionHistory = new HashMap<>();// 初始化版本历史记录public DocumentCaretaker() {versionHistory.put("full", new ArrayList<>());versionHistory.put("content", new ArrayList<>());versionHistory.put("formatting", new ArrayList<>());}// 保存完整版本public void saveFullVersion(DocumentMemento memento) {versionHistory.get("full").add(memento);}// 保存内容版本public void saveContentVersion(DocumentMemento memento) {versionHistory.get("content").add(memento);}// 保存格式版本public void saveFormattingVersion(DocumentMemento memento) {versionHistory.get("formatting").add(memento);}// 获取最新的完整版本public DocumentMemento getLatestFullVersion() {List<DocumentMemento> fullVersions = versionHistory.get("full");if (fullVersions.isEmpty()) {return null;}return fullVersions.get(fullVersions.size() - 1);}// 获取最新的内容版本public DocumentMemento getLatestContentVersion() {List<DocumentMemento> contentVersions = versionHistory.get("content");if (contentVersions.isEmpty()) {return null;}return contentVersions.get(contentVersions.size() - 1);}// 获取最新的格式版本public DocumentMemento getLatestFormattingVersion() {List<DocumentMemento> formattingVersions = versionHistory.get("formatting");if (formattingVersions.isEmpty()) {return null;}return formattingVersions.get(formattingVersions.size() - 1);}// 显示所有版本历史数量public void displayVersionInfo() {System.out.println("版本历史信息:");System.out.println("完整版本数: " + versionHistory.get("full").size());System.out.println("内容版本数: " + versionHistory.get("content").size());System.out.println("格式版本数: " + versionHistory.get("formatting").size());System.out.println("--------------------");}
}

测试类:

public class MultiStateDocumentDemo {public static void main(String[] args) {// 创建文档Document document = new Document("未命名文档");// 创建文档版本管理器DocumentCaretaker caretaker = new DocumentCaretaker();// 显示初始状态System.out.println("文档初始状态:");document.display();// 保存初始完整版本caretaker.saveFullVersion(document.createFullMemento());// 添加内容document.addContent("这是第一段内容。");document.display();// 保存内容版本caretaker.saveContentVersion(document.createContentMemento());// 改变标题和内容document.changeTitle("我的文档");document.addContent("\n这是第二段内容。");document.display();// 保存完整版本caretaker.saveFullVersion(document.createFullMemento());// 改变格式document.changeFormatting("bold");document.display();// 保存格式版本caretaker.saveFormattingVersion(document.createFormattingMemento());// 显示版本信息caretaker.displayVersionInfo();// 恢复到最新的内容版本(只恢复内容,不恢复标题和格式)System.out.println("恢复到最新的内容版本(只恢复内容):");document.restoreFromMemento(caretaker.getLatestContentVersion());document.display();// 恢复到最新的格式版本(只恢复格式)System.out.println("恢复到最新的格式版本(只恢复格式):");document.restoreFromMemento(caretaker.getLatestFormattingVersion());document.display();// 恢复到最新的完整版本(恢复所有属性)System.out.println("恢复到最新的完整版本(恢复所有属性):");document.restoreFromMemento(caretaker.getLatestFullVersion());document.display();}
}

7.2 使用内部类实现

在Java中,我们可以使用内部类来实现备忘录模式,这样可以更好地保护备忘录的状态不被外部访问。

// 发起人类 - 使用内部类作为备忘录
public class EditorWithInnerMemento {private String content;public void setContent(String content) {this.content = content;}public String getContent() {return content;}// 创建备忘录public Memento save() {return new Memento(content);}// 从备忘录恢复public void restore(Memento memento) {content = memento.getSavedContent();}// 内部类实现备忘录public class Memento {private final String savedContent;private Memento(String contentToSave) {savedContent = contentToSave;}private String getSavedContent() {return savedContent;}}
}

使用内部类的管理者类:

// 管理者类
public class InnerMementoCaretaker {private List<EditorWithInnerMemento.Memento> history = new ArrayList<>();public void push(EditorWithInnerMemento.Memento memento) {history.add(memento);}public EditorWithInnerMemento.Memento pop() {if (history.isEmpty()) {return null;}EditorWithInnerMemento.Memento lastMemento = history.get(history.size() - 1);history.remove(history.size() - 1);return lastMemento;}
}

测试内部类实现:

public class InnerMementoDemo {public static void main(String[] args) {EditorWithInnerMemento editor = new EditorWithInnerMemento();InnerMementoCaretaker caretaker = new InnerMementoCaretaker();// 设置初始内容editor.setContent("第一版内容");System.out.println("初始内容: " + editor.getContent());// 保存状态caretaker.push(editor.save());// 修改内容editor.setContent("第二版内容");System.out.println("修改后内容: " + editor.getContent());// 保存状态caretaker.push(editor.save());// 再次修改内容editor.setContent("第三版内容");System.out.println("再次修改后内容: " + editor.getContent());// 恢复到上一个状态editor.restore(caretaker.pop());System.out.println("恢复到上一个状态后内容: " + editor.getContent());// 再次恢复到上一个状态editor.restore(caretaker.pop());System.out.println("再次恢复后内容: " + editor.getContent());}
}

8. 备忘录模式在Java中的实际应用

8.1 Java中的序列化与备忘录模式

Java提供的序列化机制可以看作是备忘录模式的一种实现形式,它允许我们将对象的状态保存到文件中,并在需要时恢复。

import java.io.*;// 可序列化对象 - 相当于发起人
public class SerializableGameState implements Serializable {private static final long serialVersionUID = 1L;private String playerName;private int level;private int score;private List<String> inventory;public SerializableGameState(String playerName) {this.playerName = playerName;this.level = 1;this.score = 0;this.inventory = new ArrayList<>();}// 游戏进展public void advanceLevel() {level++;score += 1000;}// 增加分数public void addScore(int points) {score += points;}// 添加物品到库存public void addItemToInventory(String item) {inventory.add(item);}// 显示游戏状态public void displayState() {System.out.println("玩家: " + playerName);System.out.println("等级: " + level);System.out.println("分数: " + score);System.out.println("物品库存: " + inventory);System.out.println("--------------------");}// 保存状态到文件 - 使用序列化public void saveToFile(String fileName) {try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName))) {oos.writeObject(this);System.out.println("游戏状态已保存到: " + fileName);} catch (IOException e) {System.out.println("保存游戏状态失败: " + e.getMessage());}}// 从文件加载状态 - 使用反序列化public static SerializableGameState loadFromFile(String fileName) {try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName))) {SerializableGameState gameState = (SerializableGameState) ois.readObject();System.out.println("游戏状态已从: " + fileName + " 加载");return gameState;} catch (IOException | ClassNotFoundException e) {System.out.println("加载游戏状态失败: " + e.getMessage());return null;}}
}

测试序列化实现:

public class SerializationMementoDemo {public static void main(String[] args) {String saveFile = "game_save.dat";// 创建新游戏SerializableGameState game = new SerializableGameState("玩家1");System.out.println("新游戏创建:");game.displayState();// 游戏进展game.advanceLevel();game.addScore(500);game.addItemToInventory("剑");game.addItemToInventory("盾");System.out.println("\n游戏进展后:");game.displayState();// 保存游戏状态game.saveToFile(saveFile);// 继续游戏game.advanceLevel();game.addScore(800);game.addItemToInventory("药水");System.out.println("\n继续游戏后:");game.displayState();// 加载之前保存的游戏状态SerializableGameState loadedGame = SerializableGameState.loadFromFile(saveFile);if (loadedGame != null) {System.out.println("\n加载存档后:");loadedGame.displayState();}}
}

8.2 Java Swing中的撤销/重做功能

Java Swing的UndoManager类提供了撤销/重做功能,这是备忘录模式的另一种实现。

import javax.swing.*;
import javax.swing.text.*;
import javax.swing.undo.*;
import java.awt.*;
import java.awt.event.*;public class UndoRedoTextEditor extends JFrame {private JTextArea textArea;private UndoManager undoManager;public UndoRedoTextEditor() {// 设置窗口setTitle("带撤销/重做功能的文本编辑器");setSize(500, 400);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 创建文本区域textArea = new JTextArea();JScrollPane scrollPane = new JScrollPane(textArea);add(scrollPane, BorderLayout.CENTER);// 创建撤销管理器undoManager = new UndoManager();// 监听文本变化Document doc = textArea.getDocument();doc.addUndoableEditListener(e -> undoManager.addEdit(e.getEdit()));// 创建工具栏JToolBar toolBar = new JToolBar();// 创建撤销按钮JButton undoButton = new JButton("撤销");undoButton.addActionListener(e -> {try {if (undoManager.canUndo()) {undoManager.undo();}} catch (CannotUndoException ex) {System.out.println("无法撤销: " + ex);}});// 创建重做按钮JButton redoButton = new JButton("重做");redoButton.addActionListener(e -> {try {if (undoManager.canRedo()) {undoManager.redo();}} catch (CannotRedoException ex) {System.out.println("无法重做: " + ex);}});// 添加按钮到工具栏toolBar.add(undoButton);toolBar.add(redoButton);// 添加工具栏到窗口add(toolBar, BorderLayout.NORTH);}public static void main(String[] args) {SwingUtilities.invokeLater(() -> {UndoRedoTextEditor editor = new UndoRedoTextEditor();editor.setVisible(true);});}
}

9. 备忘录模式与其他设计模式的结合

9.1 备忘录模式与命令模式

备忘录模式经常与命令模式结合使用,特别是在需要撤销操作的场景中。命令模式封装操作,而备忘录模式保存状态。

// 命令接口
interface Command {void execute();void undo();
}// 接收者类
class Calculator {private int currentValue;public Calculator() {this.currentValue = 0;}public void add(int value) {currentValue += value;}public void subtract(int value) {currentValue -= value;}public int getCurrentValue() {return currentValue;}public void setCurrentValue(int value) {this.currentValue = value;}// 备忘录内部类public class CalculatorMemento {private final int savedValue;private CalculatorMemento(int value) {this.savedValue = value;}private int getSavedValue() {return savedValue;}}// 创建备忘录public CalculatorMemento createMemento() {return new CalculatorMemento(currentValue);}// 从备忘录恢复状态public void restoreMemento(CalculatorMemento memento) {this.currentValue = memento.getSavedValue();}
}// 具体命令 - 加法
class AddCommand implements Command {private Calculator calculator;private int valueToAdd;private Calculator.CalculatorMemento memento;public AddCommand(Calculator calculator, int valueToAdd) {this.calculator = calculator;this.valueToAdd = valueToAdd;}@Overridepublic void execute() {memento = calculator.createMemento();  // 保存当前状态calculator.add(valueToAdd);}@Overridepublic void undo() {calculator.restoreMemento(memento);    // 恢复到之前的状态}
}// 具体命令 - 减法
class SubtractCommand implements Command {private Calculator calculator;private int valueToSubtract;private Calculator.CalculatorMemento memento;public SubtractCommand(Calculator calculator, int valueToSubtract) {this.calculator = calculator;this.valueToSubtract = valueToSubtract;}@Overridepublic void execute() {memento = calculator.createMemento();  // 保存当前状态calculator.subtract(valueToSubtract);}@Overridepublic void undo() {calculator.restoreMemento(memento);    // 恢复到之前的状态}
}// 调用者 - 计算器操作管理器
class CalculatorInvoker {private Stack<Command> commandHistory = new Stack<>();public void executeCommand(Command command) {command.execute();commandHistory.push(command);}public void undoLastCommand() {if (!commandHistory.isEmpty()) {Command lastCommand = commandHistory.pop();lastCommand.undo();} else {System.out.println("没有可撤销的命令");}}
}// 测试类
public class CommandMementoDemo {public static void main(String[] args) {Calculator calculator = new Calculator();CalculatorInvoker invoker = new CalculatorInvoker();System.out.println("初始值: " + calculator.getCurrentValue());// 执行加法命令Command addCommand = new AddCommand(calculator, 10);invoker.executeCommand(addCommand);System.out.println("执行加法后: " + calculator.getCurrentValue());// 执行减法命令Command subtractCommand = new SubtractCommand(calculator, 5);invoker.executeCommand(subtractCommand);System.out.println("执行减法后: " + calculator.getCurrentValue());// 撤销最后一个命令invoker.undoLastCommand();System.out.println("撤销后: " + calculator.getCurrentValue());// 再次撤销invoker.undoLastCommand();System.out.println("再次撤销后: " + calculator.getCurrentValue());}
}

10. 备忘录模式的优缺点

10.1 优点

  1. 保护封装性:备忘录模式可以保存对象的状态,同时不破坏对象的封装性。发起人对象可以决定哪些状态需要保存。

  2. 提供了可靠的恢复机制:通过备忘录模式,系统可以回到某个历史状态,提供了可靠的撤销/恢复功能。

  3. 降低发起人的复杂度:将状态存储和恢复的逻辑分离出来,发起人类不需要关心怎样保存历史状态。

  4. 支持事务操作:备忘录模式可以实现简单的事务操作,使得在操作失败时能够回滚到安全状态。

  5. 简化了发起人:发起人不需要保存自身的历史状态,也不需要管理这些历史版本。

10.2 缺点

  1. 资源消耗:如果需要保存的状态太大或者保存频率过高,可能会导致内存资源的大量占用。

  2. 管理者的膨胀:如果备忘录对象太多,管理者可能会变得非常复杂,占用大量内存。

  3. 额外的开发成本:需要为每个需要保存的对象创建相应的备忘录类。

  4. 性能问题:频繁地创建和恢复备忘录可能会影响系统性能。

  5. 可能增加复杂性:在某些情况下,增加备忘录模式可能会增加系统的复杂性,尤其是当发起人的状态非常复杂时。

11. 备忘录模式的适用场景

备忘录模式适用于以下场景:

  1. 需要提供撤销功能的应用程序:文本编辑器、图形编辑器等需要支持撤销/重做操作的应用。

  2. 需要保存历史快照的场景:如游戏存档、版本控制工具等需要保存特定时间点状态的场景。

  3. 需要事务回滚的系统:某些操作需要保证原子性,当操作失败时需要回滚到操作前的状态。

  4. 需要在不破坏封装的前提下保存和恢复对象状态:备忘录模式允许在不暴露对象内部结构的情况下操作其状态。

  5. 希望预防用户数据丢失:应用程序可以定期自动保存用户工作状态,以防意外关闭导致数据丢失。

  6. 模拟可能需要重复执行的场景:模拟实验或游戏场景,可能需要从特定节点重新开始。

12. 备忘录模式与其他模式的比较

12.1 备忘录模式 vs 原型模式

  • 备忘录模式:关注在不破坏对象封装的前提下,保存和恢复对象的状态。备忘录对象通常只存储必要的状态信息。
  • 原型模式:关注创建对象的完整副本。原型通常会复制对象的所有属性。

主要区别:

  • 备忘录模式用于保存和恢复状态,原型模式用于创建对象的副本。
  • 备忘录通常只存储必要的状态,而原型复制整个对象。

12.2 备忘录模式 vs 命令模式

  • 备忘录模式:关注对象状态的存储和恢复。
  • 命令模式:关注操作的封装,可以支持撤销/重做操作。

主要区别:

  • 备忘录保存状态,命令保存动作。
  • 命令模式通常结合备忘录模式使用,以提供基于状态的撤销功能。

12.3 备忘录模式 vs 状态模式

  • 备忘录模式:允许在不破坏封装的前提下捕获对象的内部状态,并在稍后恢复它。
  • 状态模式:允许对象在其内部状态改变时改变其行为。

主要区别:

  • 备忘录模式关注状态的存储和恢复,状态模式关注基于状态的行为变化。
  • 状态模式通常不包含恢复到之前状态的机制。

13. 备忘录模式的常见问题与解决方案

13.1 如何减少备忘录模式的内存消耗?

问题:在一些应用场景中,备忘录可能会占用大量内存资源,尤其是当需要保存大量状态或对象状态非常大时。

解决方案

  1. 增量备忘录:只保存状态的变化部分,而不是完整状态。
  2. 压缩备忘录:对备忘录中的数据进行压缩存储。
  3. 惰性保存:根据实际需要保存状态,而不是定期保存。
  4. 限制历史记录数量:设置一个最大历史记录数,超出时删除最旧的记录。
  5. 使用外部存储:将备忘录保存到文件或数据库,而不是内存中。
// 增量备忘录示例
public class IncrementalMemento {private Map<String, Object> changedProperties = new HashMap<>();private IncrementalMemento previousMemento;public void setProperty(String propertyName, Object propertyValue) {changedProperties.put(propertyName, propertyValue);}public Object getProperty(String propertyName) {if (changedProperties.containsKey(propertyName)) {return changedProperties.get(propertyName);} else if (previousMemento != null) {return previousMemento.getProperty(propertyName);}return null;}public void setPreviousMemento(IncrementalMemento previousMemento) {this.previousMemento = previousMemento;}
}

13.2 如何实现多层撤销/重做?

问题:简单的备忘录模式通常只提供单一的撤销/恢复功能,但实际应用可能需要多层次的撤销/重做支持。

解决方案

  1. 使用两个栈:分别用于撤销和重做操作。
  2. 记录操作序列:保存完整的操作序列,支持在任意点恢复。
  3. 命令模式结合:使用命令模式记录操作,备忘录模式记录状态。
// 多层撤销/重做管理器
public class MultiLevelUndoRedoManager<T> {private Stack<T> undoStack = new Stack<>();private Stack<T> redoStack = new Stack<>();public void saveState(T memento) {undoStack.push(memento);// 清空重做栈,因为有新的状态redoStack.clear();}public T undo() {if (undoStack.isEmpty()) {return null;}T memento = undoStack.pop();redoStack.push(memento);return undoStack.isEmpty() ? null : undoStack.peek();}public T redo() {if (redoStack.isEmpty()) {return null;}T memento = redoStack.pop();undoStack.push(memento);return memento;}public boolean canUndo() {return undoStack.size() > 1; // 至少需要两个状态才能撤销}public boolean canRedo() {return !redoStack.isEmpty();}
}

13.3 如何处理复杂对象的状态?

问题:当对象的状态非常复杂,包含多个相互关联的对象时,备忘录模式的实现会变得复杂。

解决方案

  1. 组合备忘录:为每个子对象创建单独的备忘录,然后组合起来。
  2. 序列化:使用Java的序列化机制来保存和恢复复杂对象状态。
  3. 选择性备忘录:只保存关键的状态信息,而不是所有状态。
  4. 分层备忘录:为对象的不同层次创建不同的备忘录。
// 组合备忘录示例
public class CompositeMemento {private Map<String, Memento> componentMementos = new HashMap<>();public void addComponentMemento(String componentId, Memento memento) {componentMementos.put(componentId, memento);}public Memento getComponentMemento(String componentId) {return componentMementos.get(componentId);}// 通用接口public interface Memento {void restore(Object originator);}
}

14. 总结

备忘录模式是一种行为型设计模式,它允许在不破坏对象封装性的前提下,捕获对象的内部状态,并在需要时将对象恢复到这个状态。

14.1 核心要点

  1. 角色划分:备忘录模式包含发起人、备忘录和管理者三个核心角色,各司其职。

  2. 封装保护:备忘录模式通过限制对备忘录内部状态的访问,保证了对象的封装性。

  3. 状态恢复:备忘录模式提供了可靠的状态存储和恢复机制,支持撤销/重做等功能。

  4. 应用广泛:备忘录模式在文本编辑器、游戏存档、事务系统等多种场景中有广泛应用。

  5. 实现灵活:备忘录模式有多种实现方式,如黑箱模式、白箱模式、内部类实现等。

14.2 设计建议

  1. 轻量化备忘录:尽量只保存必要的状态信息,减小备忘录的大小。

  2. 控制备忘录数量:设置合理的备忘录保存策略,避免过多备忘录占用资源。

  3. 结合其他模式:考虑与命令模式、原型模式等结合使用,解决更复杂的问题。

  4. 注意性能问题:在频繁创建或恢复备忘录的场景中,注意性能优化。

  5. 保护隐私数据:确保备忘录中的敏感数据不会被外部访问。

备忘录模式虽然概念简单,但应用灵活,是实现状态管理、操作撤销等功能的有力工具。通过合理使用备忘录模式,我们可以使应用程序更加健壮,提供更好的用户体验。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词