1. 概述
备忘录模式是一种行为设计模式, 允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。
备忘录模式也很好理解,但实现过程是复杂了点。这次不从问题引入了,我们从使用场景引入:对于office这个办公软件,相信大家都不陌生了,他有一项功能叫做撤销,即把内容回退到上次的操作,这项功能在当今许多软件里都存在,那现在让你来实现,你会怎么做呢?
你选择采用直接的方式来实现该功能: 程序在执行任何操作前会记录所有的对象状态, 并将其保存下来。 当用户此后需要撤销某个操作时, 程序将从历史记录中获取最近的快照, 然后使用它来恢复所有对象的状态。
让我们来思考一下这些状态快照。 首先, 到底该如何生成一个快照呢? 很可能你会需要遍历对象的所有成员变量并将其数值复制保存。 但只有当对象对其内容没有严格访问权限限制的情况下, 你才能使用该方式。 不过很遗憾, 绝大部分对象会使用私有成员变量来存储重要数据, 这样别人就无法轻易查看其中的内容。
okk,我们可以先忽略这个问题,我们假设这个类可以公开它所有的属性。尽管这种方式能够解决当前问题, 让你可随时生成对象的状态快照, 但这种方式仍存在一些严重问题。 未来你可能会添加或删除一些成员变量。 这听上去很简单, 但需要对负责复制受影响对象状态的类进行更改。
还有更多问题。 让我们来考虑编辑器 (Editor) 状态的实际 “快照”, 它需要包含哪些数据? 至少必须包含实际的文本、 光标坐标和当前滚动条位置等。 你需要收集这些数据并将其放入特定容器中, 才能生成快照。
这个备忘录类中几乎没有任何方法, 但有许多与编辑器状态一一对应的成员变量。 为了让其他对象能保存或读取快照, 你很可能需要将快照的成员变量设为公有。 无论这些状态是否私有, 其都将暴露一切编辑器状态。 其他类会对快照类的每个小改动产生依赖, 除非这些改动仅存在于私有成员变量或方法中, 而不会影响外部类。
我们似乎走进了一条死胡同: 要么会暴露类的所有内部细节而使其过于脆弱; 要么会限制对其状态的访问权限而无法生成快照。
我们再想想,谁拥有这个类属性的访问权限?是他自己!!!那现在就有了一种想法,我们把保存备份数据这个动作交由它自己实现,不再让别人实现。具体怎么做呢?看下面代码。
- 总结
- 备忘录模式用于对对象的备份,便于我们去回溯我们的对象数据。
- 备忘录把备份这个功能交由对象本身实现,而不是让别的类去实现。
2. 特点
-
优点
- 你可以在不破坏对象封装情况的前提下创建对象状态快照。
- 你可以通过让负责人维护原发器状态历史记录来简化原发器代码。
-
缺点
- 如果客户端过于频繁地创建备忘录, 程序将消耗大量内存。
- 负责人必须完整跟踪原发器的生命周期, 这样才能销毁弃用的备忘录。
- 绝大部分动态编程语言 (例如 PHP、 Python 和 JavaScript) 不能确保备忘录中的状态不被修改。
-
使用场景
-
当你需要创建对象状态快照来恢复其之前的状态时, 可以使用备忘录模式。备忘录模式允许你复制对象中的全部状态 (包括私有成员变量), 并将其独立于对象进行保存。 尽管大部分人因为 “撤销” 这个用例才记得该模式, 但其实它在处理事务 (比如需要在出现错误时回滚一个操作) 的过程中也必不可少。
-
当直接访问对象的成员变量、 获取器或设置器将导致封装被突破时, 可以使用该模式。备忘录让对象自行负责创建其状态的快照。 任何其他对象都不能读取快照, 这有效地保障了数据的安全性。
-
3. 实现
-
UML类图
-
角色说明
- 原发器:可以生成自身状态的快照, 也可以在需要时通过快照恢复自身状态。
- 备份类:是原发器状态快照的值对象 。 通常做法是将备忘录设为不可变的, 并通过构造函数一次性传递数据。
- 负责人:负责人通过保存备忘录栈来记录原发器的历史状态。 当原发器需要回溯历史状态时, 负责人将从栈中获取最顶部的备忘录, 并将其传递给原发器的恢复 方法。
-
Java代码
-
原发器
/** * @Author: chy * @Description: 原发器,表示文档类 * @Date: Create in 21:09 2021/3/30 */ public class Document { // 表示要备份的数据 private String content; // 保存方法 public BackUp save(){ return new BackUp(content); } // 撤回方法 public void resume(BackUp backup){ content = backup.getContent(); } // 修改数据方法 public void change(String content){ this.content = content; } // 打印数据方法 public void printf(){ System.out.println(content); } }
-
备份类
/** * @Author: chy * @Description: 快照类,表示备份 * @Date: Create in 1:11 2021/3/31 */ public class BackUp { private String content; public String getContent() { return content; } public BackUp(String content) { this.content = content; } }
-
负责人
/** * @Author: chy * @Description: 负责人,这里表示历史记录 * @Date: Create in 1:12 2021/3/31 */ public class History { // 用栈保存备份 Stack<BackUp> history = new Stack<>(); // 添加历史记录 public void add(BackUp backUp){ history.push(backUp); } // 取出历史记录的备份 public BackUp getVersion(){ return history.pop(); } }
-
客户端
/** * @Author: chy * @Description: 客户端 * @Date: Create in 21:08 2021/3/30 */ public class Client { public static void main(String[] args) { // 新建历史记录类 History history = new History(); // 新建文档类 Document document = new Document(); // 每次修改数据就要添加历史记录 document.change("第一次"); history.add(document.save()); document.printf(); document.change("第二次"); history.add(document.save()); document.printf(); document.change("第三次"); history.add(document.save()); document.printf(); // 开始撤回 document.resume(history.getVersion()); document.printf(); document.resume(history.getVersion()); document.printf(); document.resume(history.getVersion()); document.printf(); } }
-
结果
-