在日常生活中,我们在编辑文档有时候会使用 Ctrl+Z 组合键来撤销当前的操作,我们在浏览网页的时候会点返回回到之前页面,在程序中也经常会使用数据库事务管理中的回滚操作等等,都是希望将数据恢复到之前的状态。
多年来,这个功能已经非常的普遍了,以至于大家都希望每个应用程序都能支持回退操作,我们今天要学习的备忘录模式就是用来做这个事情的,它提供了一种弥补真实世界缺陷的方法,让“后悔药”在程序的世界中真实可行。
定义
备忘录模式是一种行为设计模式,又叫快照模式,是指在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后需要时能将该对象恢复到原先保存的状态。
通俗地说,备忘录模式就是一个对象的备份模式,提供了一种程序数据的备份方法,具体采用哪种方法来存储对象状态,取决于对象需要保存时间的长短。
备忘录模式的结构:
1)发起人(Originator)角色:记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据,它可以访问备忘录里所有的信息;
2)备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人;
3)管理者(Caretaker)角色:对备忘录进行管理、保存和提供备忘录,但其不能对备忘录的内容进行访问与修改。
备忘录模式的意图在于:为对象状态提供存储和恢复功能,对象的已保存状态数据在对象外部不可访问。
优点
1)可以在不违反封装的情况下生成对象的快照;
2)简化发起者的代码,发起人不需要管理和保存其内部状态的备份,所有信息都保存在备忘录中,由管理者统一来管理。
缺点
过于频繁的创建备忘录,会使得应用程序消耗更多的内存。
以英雄联盟中的英雄属性为例,吃了红buff,英雄的战斗力会有所提升,失效后恢复到之前的状态。
1)发起人(Originator)角色
- //发起人角色,即原始对象,需要备份的对象
- public class LolOriginator {
-
- private String name; //英雄名字
- private String type; //英雄类型
- private Integer fightingNum; //英雄战斗力
-
- public LolOriginator(String name,String type,Integer fightingNum){
- this.name = name;
- this.type = type;
- this.fightingNum = fightingNum;
- }
-
- //创建一个备忘录,进行备忘操作
- public LolMemento createMemento(){
- return new LolMemento(this);
- }
-
- //恢复成指定备忘录
- public void restoreMemento(LolMemento memento){
- this.name = memento.getName();
- this.type = memento.getType();
- this.fightingNum = memento.getFightingNum();
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getType() {
- return type;
- }
-
- public void setType(String type) {
- this.type = type;
- }
-
- public Integer getFightingNum() {
- return fightingNum;
- }
-
- public void setFightingNum(Integer fightingNum) {
- this.fightingNum = fightingNum;
- }
-
- @Override
- public String toString() {
- return this.getName()+"-"+this.getType()+"-"+this.getFightingNum();
- }
- }
2)备忘录(Memento)角色
- //备忘录
- public class LolMemento {
-
- private String name; //英雄名字
- private String type; //英雄类型
- private Integer fightingNum; //英雄战斗力
-
- public LolMemento(LolOriginator lol){
- this.name=lol.getName();
- this.type=lol.getType();
- this.fightingNum = lol.getFightingNum();
- }
-
- public String getName() {
- return name;
- }
-
- public String getType() {
- return type;
- }
-
- public Integer getFightingNum() {
- return fightingNum;
- }
-
- }
3)管理者(Caretaker)角色
- //管理者,负责管理备忘录对象
- public class LolCaretaker {
-
- private LolMemento memento;
-
- //如果有一连串的状态加成,可以保存多个状态
- //private List<LolMemento> mementoList = new ArrayList<LolMemento>();
-
- public LolMemento getMemento() {
- return memento;
- }
-
- public void setMemento(LolMemento memento) {
- this.memento = memento;
- }
-
- }
4)客户端
- public class Client {
-
- public static void main(String[] args) {
- //备忘录管理者
- LolCaretaker taker = new LolCaretaker();
-
- //创建一个英雄
- System.out.println("当前的英雄信息:");
- LolOriginator lol = new LolOriginator("德玛西亚之力", "战士", 666);
- System.out.println(" "+lol.toString());
-
- //进行一次备忘
- taker.setMemento(lol.createMemento());
-
- System.out.println("吃了一个红buff:");
- lol.setFightingNum(700);
- System.out.println(" "+lol.toString());
-
- System.out.println("红buff已经失效:");
- lol.restoreMemento(taker.getMemento()); //恢复到之前的状态
-
- System.out.println(" "+lol.toString());
-
- }
- }
案例效果:
此时的UML关系图:

借助备忘录模式,可以捕获对象的状态,对象的已保存状态数据在对象外部是不可访问,这保护了已保存状态数据的完整性,这也是备忘录模式的优势所在。
我们可能用的比较多的就是把对象的状态存储在另外一个对象中或者是为了支持对象跨多个会话的持久性存储,使用对象序列化来存储对象信息,也就是我们前面学习的原型模式,原型模式可以是备忘录模式的替代。所以备忘录模式并不常用。
应用场景:
1)当您想要生成对象状态的快照以便能够恢复对象的先前状态时,可以使用备忘录模式;
2)当直接访问对象的字段getter、setter 违反其封装时,可以使用备忘录模式。
JDK中备忘录模式的应用:
所有java.io.Serializable实现都可以模拟 Memento
javax.faces.component.StateHolder实现