备忘录(Memento)模式
备忘录模式又叫做快照模式或者Token模式。
备忘录对象是一个用来存储另一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。备忘录模式常常与命令模式和迭代子模式一起使用。
常见的系统往往不止存储一个状态,而是需要存储多个状态。这些状态常常是一个对象历史发展的不同阶段的快照,存储这些快照的备忘录对象叫做此对象的历史;某一个快照所处的位置叫做检查点。
1.角色
1.备忘录角色
备忘录角色有如下责任:
(1)将发起人(Originator)对象的内战状态存储起来。备忘录可以根据发起人对象的判断来决定存储多少发起人(Originator)对象的内部状态。
(2)备忘录可以保护其内容不被发起人(Originator)对象之外的任何对象所读取。
备忘录有两个等效的接口:
● 窄接口:负责人(Caretaker)对象(和其他除发起人对象之外的任何对象)看到的是备忘录的窄接口(narrow interface),这个窄接口只允许它把备忘录对象传给其他的对象。
● 宽接口:与负责人对象看到的窄接口相反的是,发起人对象可以看到一个宽接口(wide interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。
2. 发起人角色
发起人角色有如下责任:
(1)创建一个含有当前的内部状态的备忘录对象。
(2)使用备忘录对象存储其内部状态。
3.负责人(Caretaker)角色
负责人角色有如下责任:
(1)负责保存备忘录对象。
(2)不检查备忘录对象的内容。
2. 模式的实现
1. 白箱备忘录模式的实现
备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。因此这个实现又叫做“白箱实现”。
结构:

代码如下:
发起人角色代码:利用新建的备忘录对象将自己的内部状态存储起来
/**
* 发起人角色
*
*/
public class Originator { private String state; /**
* 工厂方法,返回一个新的备忘录对象
*/
public Memento createMemento() {
return new Memento(state);
} /**
* 将发起人恢复到备忘录对象所记载的状态
*/
public void restoreMemento(Memento memento) {
this.state = memento.getState();
} public String getState() {
return state;
} public void setState(String state) {
this.state = state;
System.out.println("当前状态:" + this.state);
}
}
备忘录角色类,备忘录对象将发起人对象传入的状态存储起来。
/**
* 备忘录角色
*
*/
public class Memento { private String state; public Memento(String state) {
this.state = state;
} public String getState() {
return state;
} public void setState(String state) {
this.state = state;
}
}
负责人角色类,负责人角色负责保存备忘录对象,但是从不修改(甚至不查看)备忘录对象的内容。
/**
* 负责人角色
*
*/
public class Caretaker { private Memento memento; /**
* 备忘录的取值方法
*/
public Memento retrieveMemento() {
return this.memento;
} /**
* 备忘录的赋值方法
*/
public void saveMemento(Memento memento) {
this.memento = memento;
}
}
客户端:
public class Client {
public static void main(String[] args) {
// 发起人,改变负责人对象的状态
Originator o = new Originator();
o.setState("On");
// 利用发起人创建备忘录
Memento memento = o.createMemento();
// 负责人,并将发起人对象的备忘录储存起来
Caretaker c = new Caretaker();
c.saveMemento(memento);
// 修改发起人的状态
o.setState("Off");
// 获取负责人保存的备忘录状态
Memento retrieveMemento = c.retrieveMemento();
// 恢复发起人对象的状态
o.restoreMemento(retrieveMemento);
System.out.println(o.getState());
}
}
结果:
当前状态:On
当前状态:Off
On
在上面客户端角色里面,首先将发起人对象的状态设置成“On”,并创建一个备忘录对象将这个状态存储起来;然后将发起人对象的状态改成“Off”;最后又将发起人对象恢复到备忘录对象所存储起来的状态,即“On”状态。
时序图如下:
将发起人对象的状态存储到白箱备忘录对象中去的时序图:

可以看出系统运行的时序是这样的:
(1)将发起人对象的状态设置成“On”。
(2)调用发起人角色的createMemento()方法,创建一个备忘录对象将这个状态存储起来。
(3)将备忘录对象存储到负责人对象中去。
将发起人对象恢复到备忘录对象所记录的状态的时序图如下所示:

可以看出,将发起人对象恢复到备忘录对象所记录的状态时,系统的运行时序是这样的:
(1)将发起人状态设置成“Off”。
(2)将备忘录对象从负责人对象中取出。
(3)将发起人对象恢复到备忘录对象所存储起来的状态,即“On”状态。
白箱实现的优缺点:
优点是实现比较简单,常常用作教学目的。缺点是破坏对发起人状态的封装。(而且上面的例子只能存储一个状态,又叫做一个检查点)
2.黑箱实现
备忘录角色对发起人(Originator)角色对象提供一个宽接口,而为其他对象提供一个窄接口。这样的实现叫做“黑箱实现”。
在JAVA语言中,实现双重接口的办法就是将备忘录角色类设计成发起人角色类的内部成员类。
将Memento设成Originator类的内部类,从而将Memento对象封装在Originator里面;在外部提供一个标识接口MementoIF给Caretaker以及其他对象。这样,Originator类看到的是Menmento的所有接口,而Caretaker以及其他对象看到的仅仅是标识接口MementoIF所暴露出来的接口。
类图如下:

源码:
发起人角色:定义了一个内部的Memento类。由于此Memento类的全部接口都是私有的,因此只有它自己和发起人类可以调用。
/**
* 发起人角色
*/
public class Originator { private String state; public String getState() {
return state;
} public void setState(String state) {
this.state = state;
System.out.println("赋值状态:" + state);
} /**
* 工厂方法,返还一个新的备忘录对象
*/
public MementoIF createMemento() {
return new Memento(state);
} /**
* 发起人恢复到备忘录对象记录的状态
*/
public void restoreMemento(MementoIF memento) {
this.setState(((Memento) memento).getState());
} /**
* 私有内部备忘录角色实现宽接口
*
* @author Administrator
*
*/
private class Memento implements MementoIF { private String state; /**
* 构造方法
*/
private Memento(String state) {
this.state = state;
} private String getState() {
return state;
} private void setState(String state) {
this.state = state;
}
}
}
窄接口MementoIF,这是一个标识接口,因此它没有定义出任何的方法。
/**
* 标识接口
*
*/
public interface MementoIF { }
负责人角色类Caretaker能够得到的备忘录对象是以MementoIF为接口的,由于这个接口仅仅是一个标识接口,因此负责人角色不可能改变这个备忘录对象的内容。
/**
* 负责人角色
*
* @author Administrator
*
*/
public class Caretaker { private MementoIF memento; /**
* 备忘录取值方法
*/
public MementoIF retrieveMemento() {
return memento;
} /**
* 备忘录赋值方法
*/
public void saveMemento(MementoIF memento) {
this.memento = memento;
}
}
客户端:
public class Client {
public static void main(String[] args) {
// 创建发起人
Originator o = new Originator();
// 创建负责人
Caretaker c = new Caretaker();
// 改变负责人对象的状态
o.setState("On");
// 创建备忘录对象,并将发起人对象的状态存储起来
c.saveMemento(o.createMemento());
// 修改发起人对象的状态
o.setState("Off");
// 恢复发起人对象的状态
o.restoreMemento(c.retrieveMemento());
}
}
结果:
赋值状态:On
赋值状态:Off
赋值状态:On
时序如下:
(1)将发起人对象的状态设置为“On”。
(2)调用createMemento()方法,创建一个备忘录对象将这个状态存储起来(此时createMemento()方法还回的明显类型是MementoIF接口,真实类型为Originator内部的Memento对象)。
(3)将备忘录对象存储到负责人对象中去。由于负责人对象拿到的仅是MementoIF接口,因此无法读出备忘录对象内部的状态。
(4)将发起人对象的状态设置为“Off”。
(5)调用负责人对象的retrieveMemento()方法将备忘录对象取出。注意此时仅能得到MementoIF接口,因此无法读出此对象的内部状态。
(6)调用发起人对象的restoreMemento()方法将发起人对象的状态恢复成备忘录对象所存储的起来的状态,即“On”状态。由于发起人对象的内部类Memento实现了MementoIF接口,这个内部类是传入的备忘录对象的真实类型,因此发起人对象可以利用内部类Memento的私有接口读出此对象的内部状态。
3.多重检查点
前面的例子都是只存储一个状态的简单实现,也可以叫做只有一个检查点。常见的系统往往需要存储不止一个状态,而是需要存储多个状态,或者叫做有多个检查点。
备忘录模式可以将发起人对象的状态存储到备忘录对象里面,备忘录模式可以将发起人对象恢复到备忘录对象所存储的某一个检查点上。下面给出一个示意性的、有多重检查点的备忘录模式的实现。
类图如下:

源代码:
发起人角色:将状态存在集合中,每一个状态都有一个指数index,叫做检查点指数。
import java.util.ArrayList;
import java.util.List; /**
* 发起人角色
*
* @author Administrator
*
*/
public class Originator { private List<String> states;
// 检查点指数
private int index; /**
* 构造函数
*/
public Originator() {
states = new ArrayList<String>();
index = 0;
} /**
* 工厂方法,返还一个新的备忘录对象
*/
public Memento createMemento() {
return new Memento(states, index);
} /**
* 将发起人恢复到备忘录对象记录的状态上
*/
public void restoreMemento(Memento memento) {
states = memento.getStates();
index = memento.getIndex();
} /**
* 状态的赋值方法
*/
public void setState(String state) {
states.add(state);
index++;
} /**
* 辅助方法,打印所有状态
*/
public void printStates() {
for (String state : states) {
System.out.println(state);
}
}
}
备忘录角色:存储任意多的状态,外界可以用检查点指数index来取出检查点上的状态(克隆了传入的states,不可以使用同一个引用)
import java.util.ArrayList;
import java.util.List; /**
* 备忘录角色
*
* @author Administrator
*
*/
public class Memento { private List<String> states;
private int index; /**
* 构造函数
*/
public Memento(List<String> states, int index) {
this.states = new ArrayList<String>(states);
this.index = index;
} public List<String> getStates() {
return states;
} public int getIndex() {
return index;
} }
负责人角色:根据检查点指数index来恢复发起人角色的状态,也可以根据检查点指数index来取消一个检查点。
import java.util.ArrayList;
import java.util.List; /**
* 负责人角色
*
* @author Administrator
*
*/
public class Caretaker { private Originator o;
private List<Memento> mementos = new ArrayList<Memento>();
private int current; /**
* 构造函数
*/
public Caretaker(Originator o) {
this.o = o;
current = 0;
} /**
* 创建一个新的检查点
*/
public int createMemento() {
Memento memento = o.createMemento();
mementos.add(memento);
return current++;
} /**
* 将发起人恢复到某个检查点
*/
public void restoreMemento(int index) {
Memento memento = mementos.get(index);
o.restoreMemento(memento);
} /**
* 将某个检查点删除
*/
public void removeMemento(int index) {
mementos.remove(index);
}
}
客户端:
public class Client {
public static void main(String[] args) {
Originator o = new Originator();
Caretaker c = new Caretaker(o);
// 改变状态
o.setState("state 0");
// 建立一个检查点
c.createMemento();
// 改变状态
o.setState("state 1");
// 建立一个检查点
c.createMemento();
// 改变状态
o.setState("state 2");
// 建立一个检查点
c.createMemento();
// 改变状态
o.setState("state 3");
// 建立一个检查点
c.createMemento();
// 打印出所有检查点
o.printStates();
System.out.println("-----------------恢复检查点2-----------------");
// 恢复到第二个检查点
c.restoreMemento(1);
// 打印出所有检查点
o.printStates();
}
}
结果:
state 0
state 1
state 2
state 3
-----------------恢复检查点2-----------------
state 0
state 1
state 2
可以看出,客户端角色通过不断改变发起人角色的状态,并将之存储在备忘录里面。通过指明检查点指数可以将发起人角色恢复到相应的检查点所对应的状态上。
4.自述历史模式
所谓“自述历史”模式(History-On-Self Pattern)实际上就是备忘录模式的一个变种。在备忘录模式中,发起人(Originator)角色、负责人(Caretaker)角色和备忘录(Memento)角色都是独立的角色。虽然在实现上备忘录类可以成为发起人类的内部成员类,但是备忘录类仍然保持作为一个角色的独立意义。在“自述历史”模式里面,发起人角色自己兼任负责人角色。
备忘录角色有如下责任:
(1)将发起人(Originator)对象的内部状态存储起来。
(2)备忘录可以保护其内容不被发起人(Originator)对象之外的任何对象所读取。
发起人角色有如下责任:
(1)创建一个含有它当前的内部状态的备忘录对象。
(2)使用备忘录对象存储其内部状态。
客户端角色有负责保存备忘录对象的责任。
源代码:
/**
* 标识接口(作为备忘录对象的身份和标识)
*
* @author Administrator
*
*/
public interface MementoIF { }
发起人角色:
/**
* 发起人角色(兼职发起人角色)
*
* @author Administrator
*
*/
public class Originator { public String state; /**
* 改变状态
*/
public void changeState(String state) {
this.state = state;
System.out.println("状态改变为:" + state);
} /**
* 工厂方法,返还一个新的备忘录对象
*/
public Memento createMemento() {
return new Memento(this);
} /**
* 将发起人恢复到备忘录对象所记录的状态上
*/
public void restoreMemento(MementoIF memento) {
Memento m = (Memento) memento;
changeState(m.state);
} private class Memento implements MementoIF { private String state; /**
* 构造方法
*/
private Memento(Originator o) {
this.state = o.state;
} private String getState() {
return state;
} }
}
客户端:
public class Client {
public static void main(String[] args) {
Originator o = new Originator();
// 修改状态
o.changeState("state 0");
// 创建备忘录
MementoIF memento = o.createMemento();
// 修改状态
o.changeState("state 1");
// 按照备忘录恢复对象的状态
o.restoreMemento(memento);
}
}
结果:
状态改变为:state 0
状态改变为:state 1
状态改变为:state 0
此种模式简单易懂,可能是备忘录模式最为流行的实现形式。
总结:
意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
主要解决:所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
何时使用:很多时候我们总是需要记录一个对象的内部状态,这样做的目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先的状态,使得他有"后悔药"可吃。
如何解决:通过一个备忘录类专门存储对象状态。
关键代码:客户不与备忘录类耦合,与备忘录管理类耦合。
应用实例: 1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、数据库的事务管理。
优点: 1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。 2、实现了信息的封装,使得用户不需要关心状态的保存细节。
缺点:消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
使用场景: 1、需要保存/恢复数据的相关状态场景。 2、提供一个可回滚的操作。
注意事项: 1、为了符合迪米特原则,还要增加一个管理备忘录的类。 2、为了节约内存,可使用原型模式+备忘录模式。
备忘录(Memento)模式的更多相关文章
- C++设计模式实现--备忘录(Memento)模式
一. 备忘录模式 定义:在不破坏封装性的前提下,捕获一个对象的内部状态.并在该对象之外保存这个状态. 这样以后就可将该对象恢复到原先保存的状态. 结构图: 使用范围: Memento 模式比較适用于功 ...
- 设计模式C++描述----17.备忘录(Memento)模式
一. 备忘录模式 定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可将该对象恢复到原先保存的状态. 结构图: 使用范围: Memento 模式比较适用于功能 ...
- Java设计模式之从[暗黑破坏神存档点]分析备忘录(Memento)模式
在大部分游戏中,都有一个"存档点"的概念.比如,在挑战boss前,游戏会在某个地方存档,假设玩家挑战boss失败,则会从这个存档点開始又一次游戏.因此,我们能够将这个"存 ...
- Memento 备忘录 快照模式
简介 定义: 在不破坏封装的前提下,捕获一个对象的[内部状态],并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态. 角色: 发起人Originator:要被备份的成员,它提供一创 ...
- Java设计模式(15)备忘录模式(Memento模式)
Memento定义:memento是一个保存另外一个对象内部状态拷贝的对象,这样以后就可以将该对象恢复到原先保存的状态. Memento模式相对也比较好理解,我们看下列代码: public class ...
- Memento 备忘录 快照模式 MD
备忘录模式 简介 在不破坏封装的前提下,捕获一个对象的[内部状态],并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态. 角色: 发起人Originator:要被备份的成员,它提供 ...
- Memento模式(备忘录设计模式)
Memento模式? 使用面向对象编程的方式实现撤销功能时,需要事先保存实例的相关状态信息.然后,在撤销时,还需要根据所保存的信息将实例恢复至原来的状态.这个时候你需要使用Memento设计模式.(以 ...
- 【行为型】Memento模式
备忘录模式顾名思义就是一种能有备忘作用的设计模式,其目的是在对象外部保存其在某一时刻的状态信息,并且在任何需要的时候,都可以通过备忘录中保存的状态数据恢复对象在当时情形下的状态. 备忘录模式旨在对象的 ...
- memento模式
参考资料 • 维基百科:https://en.wikipedia.org/wiki/Memento_pattern • 百度百科:http://baike.baidu.com/link?url=ZQZ ...
随机推荐
- 基于注解的SpringAOP源码解析(二)
在上篇文章 中我们搭建了一个阅读源码的demo工程,然后简单介绍了一下@EnableAspectJAutoProxy注解,这个注解最重要的功能就是为向Spring中注入了一个beanAnnotatio ...
- JS基石之-----常用方法封装的js库
解析 URL Params 为对象 let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E ...
- vue-cli的安装及版本查看更新
vue-cli安装 npm install vue-cli -g vue-cli的版本查看 vue -V vue-cli的3.0+以后使用的不是vue-cli了,如果用以上的安装命令安装的并不是最新版 ...
- Qt Examples - Boxes (在Qt场景视图中结合OpenGL渲染)
QT自带例程Boxes使用QT Graphics View框架实现了2D图形和3D图形的混合渲染,综合性比较强,整合知识较多,值得学习. 可以使用鼠标通过以下方式控制演示中的元素: 按住鼠标左键的同时 ...
- 你的MES系统又失败了?正确的打开方式在这里
都知道MES实施艰难,真正成功的很少:有人戏称:10个MES,7个失败.1个不死不活.1个伪成功,最后一个仍需努力. 导致MES实施失败的原因有很多,所谓“成功的MES是一样的,失败的MES各有各的失 ...
- Android 解决Execution failed for task ':app:clean.'报错
说实话,我还真是没见过比Execution failed for task ':app:clean.'更为恶心,更为不要脸的bug啦,代码没啥问题,一下子行让你爽爽,一下子又不鸟你啦,研究了下,终于找 ...
- intellij idea 2019 右键包新建文件是没有java Class选项
今天要测试一个技术点于是新建了一个springboot工程, 在新建文件的时候发现右键后java class文件选项不见了. 以前真的没有碰到这种问题, 感觉很是意外于是百度Google后找到了解决办 ...
- 如何检查linux服务器是否被入侵
被入侵服务器的症状 当服务器被没有经验攻击者或者自动攻击程序入侵了的话,他们往往会消耗 100% 的资源.他们可能消耗 CPU 资源来进行数字货币的采矿或者发送垃圾邮件,也可能消耗带宽来发动 DoS ...
- 剑指offer-08 二叉树的下一个节点
剑指offer第8题,本来想找leetcode上对应的题,后来没找到,直接去牛客网上刷了. 题目描述: 给定一个二叉树和其中的一个结点(pNode),请找出中序遍历顺序的下一个结点并且返回.注意,树中 ...
- IDEA中如何导入一个maven项目并配置相关设置
导入一个maven项目参照如下链接 https://jingyan.baidu.com/article/b0b63dbf0c0ac04a49307078.html 要想启动这个导入的项目目前我所接触到 ...