当前位置: 首页 > news >正文

设计模式-备忘录模式

备忘录模式

1. 什么是备忘录模式?

想象一下你在玩一个有存档功能的游戏。当你觉得当前进度不错,或者要进行一个有风险的操作前,你会选择“存档”。这个“存档”就保存了你当前游戏的所有状态(比如角色位置、等级、物品栏等)。如果后续操作失败或者你想回到之前的状态,你就可以“读档”,恢复到存档时的状态。

备忘录模式 就是这样一种行为型设计模式,它的核心思想是:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

简单来说,它提供了一种状态保存和恢复的机制,允许你“撤销”操作或回到历史某个时间点的状态。

2. 备忘录模式的结构 (主要角色):

  • Originator (发起人/源对象):

    • 这是我们想要保存其状态的对象。

    • 它知道如何创建备忘录 (Memento) 来保存其当前状态。

    • 它也知道如何从备忘录中恢复其之前的状态。

    • 重要: Originator 内部的状态通常是私有的,不直接暴露给外部。

  • Memento (备忘录):

    • 这是一个简单的对象,用于存储 Originator 的内部状态。

    • 核心特点:

      • 它对 Originator 是“宽接口”:Originator 可以访问 Memento 内部的所有数据,以便保存和恢复状态。

      • 它对其他对象(特别是 Caretaker)是“窄接口”:Caretaker 只能持有 Memento 对象,但不能访问或修改 Memento 的内部状态。这是为了保护封装性。

    • Memento 自身不应该包含任何业务逻辑,它只是一个状态的快照容器。

  • Caretaker (负责人/管理者):

    • 负责保存 Memento 对象。

    • 不知道 Memento 内部的具体内容,也不关心。它只是像保管箱一样持有 Memento。

    • 当需要恢复状态时,它会将之前保存的 Memento 交还给 Originator。

    • Caretaker 可以保存多个 Memento,从而实现多步撤销或历史记录功能。

结构图示:

+-----------------+      creates      +-----------------+
|   Originator    |<------------------|     Memento     |
| - state         |                   | - state         |
| + setState()    |                   | + getState()    |  <-- (Usually package-private or accessible only to Originator)
| + createMemento()|                   |                 |
| + restore(m)    |                   +-----------------+
+-----------------+                         ^|                                   | (holds)| uses                              |V                                   |
+-----------------+                         |
|    Caretaker    |-------------------------+
| - mementoList   |
| + saveMemento(m)|
| + getMemento(idx)|
+-----------------+

实现窄接口和宽接口的技巧:

在 Java 中,为了实现 Memento 对 Originator 的宽接口和对 Caretaker 的窄接口,有几种常见做法:

  1. 内部类 (Inner Class):

    • 将 Memento 定义为 Originator 的一个内部类(甚至是私有静态内部类)。

    • 这样,Originator 可以访问 Memento 的所有成员(即使是私有的),而 Caretaker 只能通过 Memento 暴露的公共接口(如果有的话,但通常 Memento 对 Caretaker 没有任何有意义的公共方法)或者仅仅是持有 Memento 的引用。

  2. 包级私有 (Package-Private) 接口:

    • 定义一个接口 MementoInterface,只包含 Caretaker 需要的方法(通常是空的,或者只有标记作用)。

    • 让 Memento 类实现这个接口,并且 Memento 类的 getState() 等方法设置为包级私有或 protected。

    • Originator 和 Memento 放在同一个包下,这样 Originator 可以访问 Memento 的包级私有成员。Caretaker 只能通过 MementoInterface 来引用 Memento。

  3. 标记接口 (Marker Interface) + 封装:

    • Memento 实现一个空的标记接口。

    • Memento 的状态获取方法对 Originator 可见(例如,通过构造函数传入,或者 Originator 具有特殊权限访问)。

内部类是最常见且简洁的实现方式。

3. 备忘录模式的优缺点:

优点:

  • 封装性: 保持了 Originator 内部状态的封装。Caretaker 和其他客户端代码不需要知道 Originator 的内部结构。

  • 简化 Originator: Originator 不需要自己管理其历史状态,将状态的保存和恢复逻辑与自身核心业务逻辑解耦。

  • 状态恢复: 提供了方便的状态恢复机制,可以实现撤销/重做、回滚等功能。

  • 高内聚,低耦合: Originator 和 Memento 紧密相关,但它们与 Caretaker 之间的耦合度较低。

缺点:

  • 资源消耗: 如果 Originator 的状态非常复杂,或者需要保存大量的历史状态,那么创建和存储 Memento 对象可能会消耗大量的内存。需要谨慎管理 Memento 的生命周期。

  • 实现细节: 如果 Originator 的内部状态非常多,创建 Memento 的时候需要复制所有相关状态,可能会比较繁琐。

  • 可能破坏封装(如果 Memento 接口设计不当): 如果 Memento 暴露了过多的内部状态给非 Originator 对象,可能会破坏 Originator 的封装性。所以 Memento 的接口设计很重要。

4. 备忘录模式的应用场景:

  • 需要保存和恢复对象历史状态的场景。

  • 实现撤销/重做 (Undo/Redo) 功能: 文本编辑器、绘图软件、IDE 中的操作撤销。

  • 数据库事务的回滚 (Rollback) 机制的简化模型。

  • 游戏存档和读档功能。

  • 配置信息的快照和恢复: 当用户修改配置后,可以恢复到之前的某个配置版本。

  • 状态机中,需要回溯到某个先前状态时。

代码示例 (使用内部类实现 Memento):

假设我们有一个文本编辑器 Editor,它可以输入文本,并支持撤销操作。

import java.util.Stack;
​
// Memento (备忘录) - 作为 Editor 的静态内部类
class Editor {private String content; // Originator 的状态
​public Editor() {this.content = "";}
​public void type(String words) {this.content += words;}
​public String getContent() {return content;}
​// 创建备忘录,保存当前状态public EditorMemento save() {return new EditorMemento(this.content);}
​// 从备忘录恢复状态public void restore(EditorMemento memento) {if (memento != null) {this.content = memento.getSavedContent();}}
​// Memento 内部类// 只有 Editor 类可以访问其 getSavedContent() 方法(如果设置为 private,则通过 Editor 访问)// 或者可以设为包级私有,或者像这里一样,通过外部类间接控制访问public static class EditorMemento { // 为了简单,这里设为 public static 内部类private final String savedContent; // Memento 存储的状态
​private EditorMemento(String contentToSave) {this.savedContent = contentToSave;}
​// 这个方法理论上应该只被 Originator (Editor) 调用// 如果 EditorMemento 不是 public static,而是非静态内部类,Editor可以直接访问 savedContent// 如果是 private static 内部类,Editor 也可以访问// 这里为了Caretaker能从Editor获取,Editor能从Caretaker设置,做了一些简化private String getSavedContent() {return savedContent;}}
}
​
// Caretaker (负责人)
class History {private Stack<Editor.EditorMemento> history = new Stack<>(); // 用栈来保存历史记录
​public void save(Editor editor) {history.push(editor.save()); // 从 Originator 获取 Memento 并保存}
​public void undo(Editor editor) {if (!history.isEmpty()) {Editor.EditorMemento lastMemento = history.pop(); // 取出最近的 Mementoeditor.restore(lastMemento); // Originator 从 Memento 恢复状态} else {System.out.println("Nothing to undo.");}}
}
​
​
public class MementoPatternDemo {public static void main(String[] args) {Editor editor = new Editor();History history = new History();
​// 第一次操作editor.type("This is the first sentence. ");history.save(editor); // 保存状态1System.out.println("Current Content: " + editor.getContent());
​// 第二次操作editor.type("This is the second sentence. ");history.save(editor); // 保存状态2System.out.println("Current Content: " + editor.getContent());
​// 第三次操作editor.type("And this is the third one.");// history.save(editor); // 假设这次忘记保存了System.out.println("Current Content: " + editor.getContent());
​
​// 执行撤销history.undo(editor); // 撤销到状态2System.out.println("After first undo: " + editor.getContent());
​history.undo(editor); // 撤销到状态1System.out.println("After second undo: " + editor.getContent());
​history.undo(editor); // 没有更多可撤销的了System.out.println("After third undo (should be no change or message): " + editor.getContent());}
}

在这个例子中:

  • Editor 是 Originator。

  • EditorMemento 是 Memento,作为 Editor 的静态内部类。它的 savedContent 字段和构造函数是私有的(或者包级私有),getSavedContent() 是私有的(理论上),确保只有 Editor 能创建和解释它。

  • History 是 Caretaker,它使用一个 Stack 来存储 EditorMemento 对象,实现了多步撤销。

总结:

备忘录模式是一种强大的行为模式,它通过将对象的状态封装在备忘录中,实现了状态的外部存储和恢复,同时又不破坏对象的封装性。它在需要撤销/重做、历史记录或状态快照的场景中非常有用。关键在于设计好 Memento 的接口,使其对 Originator 开放足够的信息,而对其他对象保持封闭。

相关文章:

  • 代码随想录刷题day31
  • 专题一_双指针_快乐数
  • 解锁数据库简洁之道:FastAPI与SQLModel实战指南
  • 每日算法 -【Swift 算法】删除链表的倒数第 N 个结点
  • 南昌市新建区委书记陈奕蒙会见深兰科技集团董事长陈海波一行
  • 金融机构的网络安全
  • Day18
  • MySql简述
  • 【Linux Learning】SSH连线出现警告:WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
  • 判断素数两种方法【自用】
  • Linux之nginx部署网站
  • JavaScript 函数详解:从基础概念到实战应用
  • nginx配置中有无‘‘/’’的区别
  • antd-vue - - - - - a-table排序
  • 端到端自动驾驶研究:通过强化学习与世界模型的协同作用向VLA范式演进
  • Android OpenSL ES 音频播放完整实现指南
  • MySQL:InnoDB架构(内存架构篇)
  • 384_C++_unit是4字节大小,能存储32位(bit)bool操作,[7][48]这里用于计划表的时间节点内,二维数组中每一位代表一种AI功能的开关状态
  • 维度建模是什么意思?如何实现维度建模?
  • CPU Idle 状态与中断的关系
  • 国内做网站最大的公司/互联网推广公司排名
  • wordpress站点标题是什么/网络营销的策划方案
  • 做网站的图片/seo实战培训教程
  • 网站是否备案怎么查询/网络搜索关键词排名
  • 企业网站一般用什么框架做/西安网站制作价格
  • 贸易公司寮步网站建设哪家好/web网页制作教程