文章目录
- [1 基本介绍](#1 基本介绍)
- [2 案例](#2 案例)
-
- [2.1 Webpage 类](#2.1 Webpage 类)
- [2.2 Memento 类](#2.2 Memento 类)
- [2.3 Browser 类](#2.3 Browser 类)
- [2.4 Client 类](#2.4 Client 类)
- [2.5 Client 类的运行结果](#2.5 Client 类的运行结果)
- [2.6 总结](#2.6 总结)
- [3 各角色之间的关系](#3 各角色之间的关系)
-
- [3.1 角色](#3.1 角色)
-
- [3.1.1 Originator ( 生成者 )](#3.1.1 Originator ( 生成者 ))
- [3.1.2 Memento ( 备忘录 )](#3.1.2 Memento ( 备忘录 ))
- [3.1.3 Caretaker ( 管理者 )](#3.1.3 Caretaker ( 管理者 ))
- [3.1.4 Client ( 客户端 )](#3.1.4 Client ( 客户端 ))
- [3.2 类图](#3.2 类图)
- [4 注意事项](#4 注意事项)
- [5 优缺点](#5 优缺点)
- [6 适用场景](#6 适用场景)
- [7 总结](#7 总结)
1 基本介绍
备忘录模式 (Memento Pattern)是一种 行为型 设计模式,它在 不破坏封装性 的前提下,将某个时间点的实例的状态 保存 下来,之后在有必要时,再将实例 恢复 至当时的状态。
2 案例
本案例实现了浏览器的 访问 和 回退 功能:
- 访问功能:输入网址,然后显示网址。
- 回退功能:返回上一个网页,然后显示网址。如果没有上一个网页,则显示无法回退。
2.1 Webpage 类
java
public class Webpage { // 网页
private String url; // 路径
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Memento createMemento() { // 创建备忘录
return new Memento(url);
}
public void restoreMemento(Memento memento) { // 从指定备忘录恢复状态
this.url = memento.getUrl();
}
}
2.2 Memento 类
java
public class Memento { // 保存网页路径的备忘录
// Memento 类的字段的可见性为 包内可见,这代表 Webpage 类可以随意使用 Memento 类中的字段
String url; // 路径
// 假设与 Memento 类同包的类只有 Webpage 类
// Memento 类的构造器的可见性为 包内可见,这代表只有 Webpage 类可以创建 Memento 类的实例
Memento(String url) {
this.url = url;
}
// 这个方法的可见性为 public,可以被本包外的类调用
public String getUrl() {
return url;
}
}
2.3 Browser 类
java
import java.util.LinkedList;
public class Browser { // 浏览器
private Webpage webpage = new Webpage(); // 当前的网页,初始化的网页没有网址
private LinkedList<Memento> mementos = new LinkedList<>(); // 存储 以前网页状态 的备忘录集合
public void access(String url) { // 访问一个网页
if (webpage.getUrl() != null) { // 如果当前网页有网址
mementos.push(webpage.createMemento()); // 则将当前网页的状态保存到备忘录集合中
}
webpage.setUrl(url);
System.out.println("当前的网页的网址为:" + webpage.getUrl());
}
public void back() { // 返回上一个网页
if (!mementos.isEmpty()) { // 如果之前访问过网页
webpage.restoreMemento(mementos.pop()); // 则返回到上一个网页
System.out.println("上一个网页的网址为:" + webpage.getUrl());
} else {
System.out.println("没有网页可以回退");
}
}
}
2.4 Client 类
java
public class Client { // 客户端,测试了 浏览器的 访问 和 返回 功能
public static void main(String[] args) {
Browser browser = new Browser();
browser.access("https://www.baidu.com");
browser.access("https://baike.baidu.com");
browser.access("https://www.jd.com");
browser.back();
browser.back();
browser.back();
}
}
2.5 Client 类的运行结果
当前的网页的网址为:https://www.baidu.com
当前的网页的网址为:https://baike.baidu.com
当前的网页的网址为:https://www.jd.com
上一个网页的网址为:https://baike.baidu.com
上一个网页的网址为:https://www.baidu.com
没有网页可以回退
2.6 总结
本案例中,在访问新的网页时,保存了网页的网址,用以之后的回退功能。
需要特别注意 Memento
类中 各个字段 和 各个方法(包括构造器)的可见性:
- 所有字段:由于它们在
Webpage
类中会使用到,所以将其可见性设置为 包内可见。 - 构造器:由于不想其他包的类随便生成
Memento
类的实例,所以将其可见性设置为 包内可见。 - 方法分为两类:
- 只想被
Webpage
类调用的方法:可见性设置为 包内可见。这种情况比较少,一般没有这种方法。 - 可以被包外的类调用的方法:可见性设置为
public
。
- 只想被
当将 Memento
类的 字段 和 方法 设置为如上的可见性时,就达成了本模式的一个重点------不破环 Webpage
类的封装性 。不过 Memento
类和 Webpage
类的 耦合度很高 ,如果要修改 Webpage
类的字段,则需要修改 Memento
类的字段。
3 各角色之间的关系
3.1 角色
3.1.1 Originator ( 生成者 )
该角色负责 实现 生成 Memento 角色的实例 和 根据指定 Memento 角色实例恢复自身状态 的方法 。本案例中,Webpage
类扮演了该角色。
3.1.2 Memento ( 备忘录 )
该角色负责 记录 Originator 角色中需要的 信息 ,这些信息可以被 Originator 角色直接访问,但不暴露给其他类。此外,Memento 角色中含有两类方法:
- 只能被 Originator 角色访问的方法(包括构造器)。
- 可以被其他类访问的方法。
这些方法具有不同的访问修饰符 。本案例中,Memento
类扮演了该角色。
3.1.3 Caretaker ( 管理者 )
该角色负责 在合适的情况下 记录 Originator 角色的状态(即保存其生成的 Memento 实例) ,然后在合适的情况下 恢复 Originator 角色的状态(即给 Originator 角色传递 Memento 实例) 。本案例中,Browser
类扮演了该角色。
3.1.4 Client ( 客户端 )
该角色负责 使用 Caretaker 角色完成具体的业务逻辑 。本案例中,Client
类扮演了该角色。
3.2 类图
说明:
- Caretaker 中可能用
Memento memento;
、List<Memento> mementos;
等的方式来 聚合 Memento。 - Memento 中有两类方法,一类是供给 Originator 调用的,另一类是提供给包外的类调用的。
4 注意事项
- ** Memento 的生命周期管理**:
- 及时使用与删除 : Memento 创建出来就应在"最近"的代码中使用,避免长时间占用内存 。若 Memento 不再需要,应立即删除其引用,以便垃圾回收器回收处理。
- 避免内存泄漏 :由于 Memento 可能包含大量数据,若不及时管理 ,可能会导致内存泄漏。
- 封装性保护 :
- 状态封装 :备忘录模式应 确保妥善封装 Originator 的内部状态 ,防止外部对象直接访问或修改这些状态。
- 接口设计 :Memento 和 Originator 之间的接口设计应清晰明确,确保 Memento 仅包含必要的状态信息,避免暴露过多的内部实现细节。
- 性能考虑 :
- 避免频繁创建 :备忘录模式的性能开销主要来自于备忘录对象的创建和存储。因此,应避免在频繁操作的场景(如循环中)创建备忘录对象,以减少不必要的资源消耗。
- 优化存储 :对于大型对象或复杂状态,应考虑使用 压缩 技术来优化存储,减少内存占用。
- 安全性考虑 :
- 敏感数据保护 :若 Memento 中包含 敏感数据 (如用户个人信息、密码等),则应采取 加密 、脱敏 等安全措施来保护这些数据的安全性。
- 并发控制 :在 多线程环境 下,若多个线程同时操作同一个发起人对象的状态,并尝试保存或恢复 Memento,则需要考虑 并发控制问题,以避免数据竞争或状态损坏。
5 优缺点
优点:
- 封装性保护 :备忘录模式能够有效地 保护封装边界 ,将 Originator 的内部状态细节隐藏起来,不直接暴露给外部对象。只有 Originator 能够直接访问和修改其内部状态 ,而其他对象(如 Caretaker)只能通过 Memento 来 间接地 存储 和 恢复 这些状态。
- 灵活的撤销操作 :在需要实现 撤销操作 的场景中,备忘录模式能够方便地 保存每一次操作前的状态,并允许用户随时撤销到之前的状态。这种撤销操作不依赖于系统当前的状态,因此具有很高的灵活性和可靠性。
- 状态恢复的无损性 :由于备忘录模式直接保存了对象的内部状态,因此在恢复状态时可以做到 完全无损,即恢复到保存备忘录时的精确状态,不会有任何数据丢失或损坏。
缺点:
- 资源消耗 :备忘录模式会 消耗较多的内存资源,因为需要为每个保存的状态创建一个备忘录对象。如果状态信息很大或者需要频繁地保存和恢复状态,那么这种资源消耗可能会变得非常显著。
- 管理复杂性 :随着系统中备忘录对象的增多,Caretaker 需要维护一个庞大的备忘录集合。这可能会增加系统的管理复杂性,使得 在需要时快速找到并恢复正确的备忘录变得困难。
- 耦合度较高 :在备忘录模式中,Memento 对 Originator 的 依赖性很强,如果修改 Originator 的字段,则也需要修改 Memento 的字段。
6 适用场景
- 文本编辑器的撤销/恢复操作 :在文本编辑器中,用户可能会进行多次编辑操作,如输入、删除、修改等。为了 支持撤销(Undo)和恢复(Redo)功能,编辑器可以使用备忘录模式来保存每次编辑操作前的文本状态。当用户执行撤销操作时,编辑器可以从备忘录中恢复到上一次保存的状态;当用户执行恢复操作时,则可以再次回到撤销前的状态。
- 游戏的存档/读档操作 :在游戏中,玩家可能需要保存游戏进度以便在之后继续游戏 ,或者在某些情况下需要恢复到之前的某个游戏状态。游戏可以使用备忘录模式来保存游戏状态,包括玩家的位置、物品、得分等信息。当玩家选择存档时,游戏将当前状态保存到备忘录中;当玩家选择读档时,则可以从备忘录中恢复之前保存的游戏状态。
- 数据库事务的回滚 :在数据库管理中,事务是一组不可分割的操作序列 ,它们要么全部执行成功,要么全部不执行。为了 支持事务的回滚操作(即在事务执行过程中发生错误时撤销已执行的操作),数据库系统可以使用备忘录模式来保存事务执行前的状态。如果事务执行失败或需要回滚,数据库系统可以从备忘录中恢复到事务开始前的状态。
- 安全监管中的状态记录 :在安全监管领域,系统可能需要记录其运行状态以便在出现问题时进行回溯和调查。通过使用备忘录模式,系统可以定期保存其关键状态信息到备忘录中。当系统出现问题时,监管人员可以从备忘录中检索之前的状态信息,以便分析问题的原因和过程。
7 总结
备忘录模式 是一种 行为型 设计模式,它在 不破坏 Originator 的封装性 的前提下,将它的某个时间点的状态通过 Memento 保存 下来,之后在有必要时,再使用 Memento 将它 恢复 至当时的状态。这种模式保证了 Originator 的封装性,而且在恢复状态时没有数据的丢失。不过缺点也很明显,就是会占用大量的内存或储存空间。在需要 先记录数据 ,然后在一段时间后恢复数据 的场景中,经常会使用到备忘录模式。