1.什么是备忘录模式
备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
举个例子:
公元1722年,康熙皇帝驾崩于北京畅春园,步军统领隆科多取出了藏在正大光明匾额后面的遗诏,宣布四阿哥胤禛克承大统,继承皇位,天朝帝国从此走进了新时代。康熙为什么采用遗诏,而不是自己宣布继承人?还不是为了防止出现意外:躲猫猫、马航370、相亲遇到翟欣欣、被闺蜜锁在门外…;别人能够知道和篡改遗诏的内容吗?别人敢吗?!这样康熙使用遗诏的方式,在龙驭宾天后,恢复了“皇帝类”的另一个对象实例:雍正。康熙爷在300年前就给我们玩了一把备忘录模式。
2.UML类图

Memeto角色(备忘录):保存Originator对象的内部状态,并且只能由Originator才能对它存取数据,它对其它对象是不可见的。“遗诏”就是这个角色,只有康熙知道,谁也不知道。
Originator角色(源发器):创建Memeto,把状态记录写入memeto,可以使用备忘录恢复内部状态。“皇帝类”就是这个角色,康熙和雍正分别是被销毁和被恢复的皇帝对象实例。
CareTaker角色(保管者):字面意思是小心的看管者,负责保存好备忘录,不能对备忘录的内容进行操作或检查。“隆科多”就是这个角色,看管遗诏还不得小心翼翼,还敢偷看和修改,他有几个脑袋?
3.分析说明
- 我们知道在Java中有clone操作,在C++中有拷贝构造函数,都可以创建一个完全相同的对象实例,那么我们是否可以使用它们来复制一个完全一模一样的Originator对象,作为它的副本呢?这样当需要恢复它的时候,从副本中得到它的原始状态,或者干脆直接使用备份的Originator对象,不更简单直接吗?不一定可行。
首先,Originator的状态不一定需要全部备份,比如有的状态是暂态的,只与它运行的时刻有关,备份它们是没有意义的,还浪费存储空间,也就不需要保存的;
其次,有的Originator的状态可能是活动对象,比如一个文件对象File或者网络连接对象Socket,它们是无法保存的。就算是保存下来,再恢复时,已经脱离了它所运行上下文环境,也无法正常运行,备份时应该只保存用于创建它们的信息就行了。比如,对于File对象可以保存它的文件路径,对于Socket对象,保存的是IP地址和端口号。
最后,保存的状态有可能序列化,以传输到其它进程,甚至持久化到文件系统,如果备份整个Originator,数据量有可能很大,比如Serializable对象在序列化时会把所有祖先类的状态属性全部保存。
- Originator自己负责保存状态行吗?不行,保存状态的目的是为了在某些场合恢复状态,如果Originator被杀死了,它所保存的状态,也就一块释放了,当然可以把状态持久化到磁盘上,但IO操作又是一个很大的开销,一般不使用。所以,只能保存在Originator的外部,比如让Caretaker来保存状态。
这样就会产生另一个问题,如果Originator把状态看作是自己的私有属性,不想暴露出来,那么Caretaker就无法得到数据,或者进一步说,就算Originator能把状态访问全部开放给Caretaker,比如C++中的友元类,这样既能封闭自己的状态,还能让友元类Caretaker来访问它的状态。那么随着业务的演化发展,Originator需要添加一个新的状态属性,怎样变化呢?首先要修改Originator,其次在保存状态时,要修改CareTaker,有两处变化,因此,需要封装CareTaker的变化。
- 既然该模式中的发生变化的地方是Originator的状态,那就使用Memeto来封装它。从类结构图我们也能看到,把状态封装在了Memeto中,就能做到既封装状态属性,又能封装状态保存和恢复操作(Memeto参数类型不变,它里面属性的变化不影响保存和恢复操作)。虽然Memeto对象关联着Originator和Caretaker,但是它却是一个具体类,没有使用抽象类来实现依赖倒置,为什么呢?因为它是一个数据类对象,只是用来保存Originator的状态数据(在实现时,可能都是private的,比如作为Originator的内部类存在时,封装了状态属性),而且Caretaker也从不关心Memeto的具体细节,也不会访问Memeto对象,就算变化也是在Memeto内变化,影响不到Caretaker(封装了状态保存和恢复操作)。如果发生了变化,比如增加或删除状态,Originator要修改自己内部状态,同时修改Memeto的保存和恢复状态的方法,都是在Originator内部发生的,显然对Caretaker没有影响。
4.备忘录Memeto对象是被动的,它只对状态进行读写,没有其它业务逻辑,保管者Caretaker对象负责保存备忘录,从不过问备忘录细节,源发器Originator负责产生和使用备忘录。因此,一个对象的状态记录和保存记录被分开处理了。
4.具体代码实现
1.发起人角色:记录当前时刻的内部状态,并负责创建和恢复备忘录数据,允许访问返回到先前状态所需的所有数据。
public class Originator {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Memento createMento() {
return (new Memento(state));
}
public void setMemento(Memento memento) {
state = memento.getState();
}
public void show() {
System.out.println("state = " + state);
}
}
2.备忘录角色:
负责存储Originator发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。
public class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
3.备忘录管理员角色
对备忘录进行管理、保存和提供备忘录,只能将备忘录传递给其他角色。
public class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
4.Client客户端
下面编写一小段代码测试一下,即先将状态置为On,保存后再将状态置为Off,然后通过备忘录管理员角色恢复初始状态。
public class Client {
public static void main(String[] args) {
Originator originator = new Originator();
originator.setState("On"); //Originator初始状态
originator.show();
Caretaker caretaker = new Caretaker();
caretaker.setMemento(originator.createMento());
originator.setState("Off"); //Originator状态变为Off
originator.show();
originator.setMemento(caretaker.getMemento()); //回复初始状态
originator.show();
}
运行结构:

5.使用场景
对象既想让外部对象来保存它的状态,又不想破坏它的内部状态的封装。
Android中Activity和Fragment使用onSavedInstance()来保存状态,就是使用备忘录模式来实现。
我们知道Activity的生命周期被Andriod系统给托管了,当配置发生了变化,或者系统内存资源不够时,Android一般会杀死Activity,为了保证用户体验的一致性,当用户重新返回被系统杀死的那个Activity时,Android会重建那个Activity,并把Activity的状态恢复到被杀死时的状态,这样在用户看来界面没有发生变化。
为了达到这个目的,就需要在Activity被杀死时保存状态,那么状态保存到哪儿呢,显然是不可能保存在Activity中,因为它被杀死后,所引用的对象全部释放了,那就只能保存在外部了,使用备忘录模式再合适不过了。如果我们分析android的代码,Originator对应的就是Activity,Memento对应的就是Bundle,Caretaker就是ActivityManangerService了。
网友评论