备忘录模式

备忘录模式:保存与恢复对象状态的设计艺术

一、备忘录模式的定义与核心思想

备忘录模式(Memento Pattern) 是一种行为型设计模式,它在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便后续需要时能将对象恢复到原先保存的状态。其核心思想是 “状态的封装与分离存储”—— 通过引入备忘录对象存储原发器(状态拥有者)的内部状态,让原发器无需暴露私有状态即可实现状态备份与恢复,同时避免原发器与状态存储逻辑耦合。

简单来说,备忘录模式解决了 “如何在不破坏对象封装的前提下,安全地保存和恢复对象历史状态” 的问题。例如,文本编辑器的撤销(Ctrl+Z)、游戏存档 / 读档、浏览器历史记录回退、数据库事务回滚等场景,都依赖备忘录模式实现状态的备份与恢复。

二、备忘录模式的角色构成

备忘录模式通常包含以下 3 个核心角色,各角色职责明确、协作完成状态的保存与恢复:

角色名称 核心职责 示例(文本编辑器场景)
原发器(Originator) 拥有内部状态,提供创建备忘录(保存当前状态)和从备忘录恢复状态的方法 TextEditor 类(文本编辑器)
备忘录(Memento) 封装原发器的内部状态,仅允许原发器访问其状态(对外隐藏细节) EditorMemento 类(编辑器状态备忘录)
负责人(Caretaker) 负责管理备忘录对象,提供备忘录的存储与获取方法,但不操作或检查备忘录的内容 HistoryManager 类(历史记录管理器)

核心约束:备忘录的状态仅能由原发器写入和读取,负责人仅负责 “保管” 备忘录,不能修改或查看其内部状态,确保封装性。

三、备忘录模式的工作原理与实现

1. 工作流程

  1. 原发器创建一个备忘录对象,将自身当前的内部状态保存到备忘录中;

  2. 负责人将备忘录对象存储起来(可存入列表、栈等结构,支持多状态备份);

  3. 当需要恢复状态时,负责人从存储中取出对应的备忘录对象,传递给原发器;

  4. 原发器从备忘录中恢复自身的内部状态。

2. 基础实现(文本编辑器撤销功能)

以文本编辑器的 “文本内容备份与撤销” 为例,实现备忘录模式,支持保存当前文本状态、恢复历史文本状态:

(1)备忘录(Memento)
// 备忘录:封装文本编辑器的内部状态(仅允许原发器访问)

public class EditorMemento {

   // 存储原发器的内部状态(文本内容)

   private final String content;

   // 构造器:仅允许原发器调用(包访问权限或通过原发器内部类实现)

   EditorMemento(String content) {

       this.content = content;

   }

   // 提供状态读取方法:仅允许原发器调用

   String getContent() {

       return content;

   }

}
(2)原发器(Originator)
// 原发器:文本编辑器,拥有内部状态(当前文本内容)

public class TextEditor {

   private String content; // 内部状态:当前编辑的文本

   // 构造器:初始化文本为空

   public TextEditor() {

       this.content = "";

   }

   // 业务方法:修改文本内容

   public void type(String text) {

       this.content += text;

       System.out.println("当前文本:" + content);

   }

   // 创建备忘录:保存当前状态

   public EditorMemento createMemento() {

       System.out.println("保存状态:" + content);

       return new EditorMemento(this.content); // 将当前状态存入备忘录

   }

   // 从备忘录恢复状态

   public void restoreFromMemento(EditorMemento memento) {

       this.content = memento.getContent();

       System.out.println("恢复状态:" + content);

   }

   // 辅助方法:获取当前文本(用于测试)

   public String getContent() {

       return content;

   }

}
(3)负责人(Caretaker)
// 负责人:管理备忘录对象(历史记录管理器)

import java.util.Stack;

public class HistoryManager {

   // 用栈存储备忘录,支持LIFO(后进先出),适配撤销逻辑

   private final Stack<EditorMemento> mementoStack = new Stack<>();

   // 存储备忘录(备份状态)

   public void saveMemento(EditorMemento memento) {

       mementoStack.push(memento);

   }

   // 获取最近的备忘录(用于撤销)

   public EditorMemento getLastMemento() {

       if (mementoStack.isEmpty()) {

           throw new IllegalStateException("无历史状态可恢复!");

       }

       return mementoStack.pop();

   }

   // 检查是否有可恢复的状态

   public boolean hasMemento() {

       return !mementoStack.isEmpty();

   }

}

(4)客户端测试
public class Client {

   public static void main(String[] args) {

       // 创建原发器(文本编辑器)和负责人(历史记录管理器)

       TextEditor editor = new TextEditor();

       HistoryManager history = new HistoryManager();

       // 第一次编辑:输入"Hello "

       editor.type("Hello ");

       history.saveMemento(editor.createMemento()); // 保存状态1

       // 第二次编辑:输入"Design Pattern! "

       editor.type("Design Pattern! ");

       history.saveMemento(editor.createMemento()); // 保存状态2

       // 第三次编辑:输入"This is Memento Pattern."

       editor.type("This is Memento Pattern.");

       System.out.println("n最终文本:" + editor.getContent());

       // 第一次撤销:恢复到状态2

       System.out.println("n=== 执行第一次撤销 ===");

       editor.restoreFromMemento(history.getLastMemento());

       System.out.println("撤销后文本:" + editor.getContent());

       // 第二次撤销:恢复到状态1

       System.out.println("n=== 执行第二次撤销 ===");

       editor.restoreFromMemento(history.getLastMemento());

       System.out.println("撤销后文本:" + editor.getContent());

       // 尝试第三次撤销(无历史状态)

       System.out.println("n=== 尝试第三次撤销 ===");

       if (history.hasMemento()) {

           editor.restoreFromMemento(history.getLastMemento());

       } else {

           System.out.println("已无更多历史状态可撤销!");

       }

   }

}
输出结果
当前文本:Hello

保存状态:Hello

当前文本:Hello Design Pattern!

保存状态:Hello Design Pattern!

当前文本:Hello Design Pattern! This is Memento Pattern.

最终文本:Hello Design Pattern! This is Memento Pattern.

=== 执行第一次撤销 ===

恢复状态:Hello Design Pattern!

撤销后文本:Hello Design Pattern!

=== 执行第二次撤销 ===

恢复状态:Hello

撤销后文本:Hello

=== 尝试第三次撤销 ===

已无更多历史状态可撤销!

3. 扩展:支持多状态备份与命名存档(游戏存档场景)

备忘录模式可灵活扩展为 “命名存档”“多版本状态管理”,只需在备忘录中增加状态标识(如存档名称、时间戳),负责人通过标识管理不同备忘录:

(1)扩展备忘录:增加存档标识
// 扩展备忘录:游戏存档备忘录(包含存档名称、角色等级、金币数量)

public class GameMemento {

   private final String saveName; // 存档名称

   private final int level; // 角色等级

   private final int gold; // 金币数量

   private final long saveTime; // 存档时间戳

   // 构造器:存储游戏当前状态

   public GameMemento(String saveName, int level, int gold) {

       this.saveName = saveName;

       this.level = level;

       this.gold = gold;

       this.saveTime = System.currentTimeMillis();

   }

   // 仅允许原发器访问的getter方法

   String getSaveName() {

       return saveName;

   }

   int getLevel() {

       return level;

   }

   int getGold() {

       return gold;

   }

   long getSaveTime() {

       return saveTime;

   }

}
(2)扩展原发器:游戏角色类
// 扩展原发器:游戏角色(拥有等级、金币等状态)

public class GameCharacter {

   private String name; // 角色名称

   private int level; // 等级

   private int gold; // 金币

   public GameCharacter(String name) {

       this.name = name;

       this.level = 1; // 初始等级1

       this.gold = 0; // 初始金币0

       System.out.println("创建角色:" + name + ",等级:" + level + ",金币:" + gold);

   }

   // 业务方法:升级(增加等级和金币)

   public void levelUp() {

       this.level++;

       this.gold += 100;

       System.out.println("角色升级!当前等级:" + level + ",金币:" + gold);

   }

   // 业务方法:消费金币

   public void spendGold(int amount) {

       if (this.gold >= amount) {

           this.gold -= amount;

           System.out.println("消费" + amount + "金币,剩余金币:" + gold);

       } else {

           System.out.println("金币不足,消费失败!");

       }

   }

   // 创建备忘录(保存当前游戏状态)

   public GameMemento createMemento(String saveName) {

       System.out.println("创建存档:" + saveName);

       return new GameMemento(saveName, this.level, this.gold);

   }

   // 从备忘录恢复状态

   public void restoreFromMemento(GameMemento memento) {

       this.level = memento.getLevel();

       this.gold = memento.getGold();

       System.out.println("恢复存档《" + memento.getSaveName() + "》成功!当前等级:" + level + ",金币:" + gold);

   }

   // 辅助方法:显示角色状态

   public void showStatus() {

       System.out.println("角色:" + name + ",等级:" + level + ",金币:" + gold);

   }

}
(3)扩展负责人:游戏存档管理器
// 扩展负责人:游戏存档管理器(支持按存档名称查找)

import java.util.HashMap;

import java.util.Map;

public class GameSaveManager {

   // 用Map存储备忘录,key为存档名称,支持按名称查找

   private final Map<String,GameMemento> saveMap = new HashMap<>(); //存储存档(按名称唯一标识)

   public void saveMemento(GameMemento memento) {

       saveMap.put(memento.getSaveName(), memento);

       System.out.println("存档《" + memento.getSaveName() + "》已保存");

   }

   // 按名称获取存档

   public GameMemento getMemento(String saveName) {

       GameMemento memento = saveMap.get(saveName);

       if (memento == null) {

           throw new IllegalArgumentException("存档《" + saveName + "》不存在!");

       }

       return memento;

   }

   // 显示所有存档列表

   public void showAllSaves() {

       System.out.println("n=== 所有存档列表 ===");

       saveMap.forEach((name, memento) -> {

           System.out.println("存档名称:" + name + ",存档时间:" + new java.util.Date(memento.getSaveTime()) +

                              ",等级:" + memento.getLevel() + ",金币:" + memento.getGold());

       });

       System.out.println("===================n");

   }

}

(4)扩展客户端测试
public class GameClient {

   public static void main(String[] args) {

       // 创建游戏角色和存档管理器

       GameCharacter hero = new GameCharacter("勇者");

       GameSaveManager saveManager = new GameSaveManager();

       // 游戏进程1:升级并创建存档1

       hero.levelUp();

       hero.levelUp();

       hero.spendGold(50);

       saveManager.saveMemento(hero.createMemento("新手村存档"));

       // 游戏进程2:继续升级并创建存档2

       hero.levelUp();

       hero.spendGold(150);

       saveManager.saveMemento(hero.createMemento("主城存档"));

       // 显示所有存档

       saveManager.showAllSaves();

       // 恢复到"新手村存档"

       System.out.println("n=== 恢复到新手村存档 ===");

       hero.restoreFromMemento(saveManager.getMemento("新手村存档"));

       hero.showStatus();

       // 继续游戏并创建新存档

       System.out.println("n=== 继续游戏 ===");

       hero.levelUp();

       saveManager.saveMemento(hero.createMemento("副本前存档"));

       saveManager.showAllSaves();

   }

}
扩展输出结果
创建角色:勇者,等级:1,金币:0

角色升级!当前等级:2,金币:100

角色升级!当前等级:3,金币:200

消费50金币,剩余金币:150

创建存档:新手村存档

存档《新手村存档》已保存

角色升级!当前等级:4,金币:250

消费150金币,剩余金币:100

创建存档:主城存档

存档《主城存档》已保存

=== 所有存档列表 ===

存档名称:新手村存档,存档时间:Thu Aug 10 15:30:00 CST 2024,等级:3,金币:150

存档名称:主城存档,存档时间:Thu Aug 10 15:30:01 CST 2024,等级:4,金币:100

===================

=== 恢复到新手村存档 ===

恢复存档《新手村存档》成功!当前等级:3,金币:150

角色:勇者,等级:3,金币:150

=== 继续游戏 ===

角色升级!当前等级:4,金币:250

创建存档:副本前存档

存档《副本前存档》已保存

=== 所有存档列表 ===

存档名称:新手村存档,存档时间:Thu Aug 10 15:30:00 CST 2024,等级:3,金币:150

存档名称:主城存档,存档时间:Thu Aug 10 15:30:01 CST 2024,等级:4,金币:100

存档名称:副本前存档,存档时间:Thu Aug 10 15:30:02 CST 2024,等级:4,金币:250

===================

四、备忘录模式的应用场景

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

  1. 需要保存和恢复对象历史状态:例如,文本编辑器的撤销、游戏存档、浏览器历史记录、办公软件的文档恢复(如 Word 崩溃后恢复未保存内容)。

  2. 不希望暴露对象内部状态:需要在不破坏对象封装性的前提下,备份对象状态(备忘录仅允许原发器访问状态,负责人仅管理存储)。

  3. 状态变化频繁且需要回滚:例如,数据库事务(提交前保存状态,异常时回滚)、配置中心的配置变更回滚。

  4. 需要维护对象的多版本状态:例如,代码版本控制系统(Git 的 commit 本质是备忘录,分支切换是状态恢复)。

五、备忘录模式的优缺点

优点

  1. 保持对象封装性:备忘录仅允许原发器访问状态,避免原发器暴露私有属性,符合 “封装原则”。

  2. 简化原发器逻辑:原发器无需关心状态的存储与管理,只需专注核心业务,状态备份与恢复逻辑委托给备忘录和负责人。

  3. 支持灵活的状态恢复:可通过负责人管理多个备忘录,实现多版本状态备份与任意版本恢复(如游戏多存档、编辑器多级撤销)。

  4. 符合 “开闭原则”:新增状态类型或存储方式时,无需修改原发器代码,只需扩展备忘录和负责人(如从 “栈存储” 改为 “数据库存储”)。

缺点

  1. 资源消耗较大:若对象状态复杂或需要保存大量历史状态(如多级撤销),备忘录对象会占用较多内存或存储资源。

  2. 备忘录与原发器耦合:备忘录需要与原发器的内部状态结构保持

posted @ 2025-12-10 02:04  圣祖帝皇  阅读(3)  评论(0)    收藏  举报