备忘录模式介绍
备忘录模式是一种行为模式,该模式用于保存对象当前状态,并且在之后可以再次恢复到此状态。备忘录模式实现的方式需要保证被保存的对象状态不能被对象从外部访问,目的是为了保护好被保存的这些对象状态的完整性以及内部实现不向外暴露。
备忘录模式的定义
在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样,以后就可将该对象恢复到原先保存的状态。
备忘录模式的使用场景
(1) 需要保存一个对象在某一个时刻的状态或部分状态。
(2) 如果用一个接口来让其他对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过中间对象可以间接访问其内部状态。
备忘录模式的UML类图
角色介绍:
- Originator:负责创建一个备忘录,可以记录、恢复自身的内部状态。同时Originator还可以根据需要决定Memento存储自身的哪些内部状态。
- Memento:备忘录角色,用于存储Originator的内部状态,并且可以防止Originator意外的对象访问Memento。
- Caretaker:负责存储备忘录,不能对备忘录的内容进行操作和访问,只能够将备忘录传递给其他对象。
备忘录模式的简单示例
下面以游戏存档为例来演示一下备忘录模式的实现。
首先我们建立游戏类、备忘录类、Caretaker类,玩游戏到某个节点对游戏进行存档,然后退出游戏,再重新进入时从存档中读取进度,并且进入存档时的进度。
首先看游戏类:
package com.guifa.memorandumdemo;
/**
* 游戏(数据模型可能不太合理,这里只是简单演示)
*/
public class CallOfDuty {
private int mCheckpoint = 1;
private int mLifeValue = 100;
private String mWeapon = "沙漠之鹰";
// 玩游戏
public void play() {
System.out.println("玩游戏:" + String.format("第%d关", mCheckpoint) + "奋战杀敌中");
mLifeValue -= 10;
System.out.println("进度升级啦");
mCheckpoint++;
System.out.println("到达" + String.format("第%d关", mCheckpoint));
}
// 退出游戏
public void quit() {
System.out.println("-------------------");
System.out.println("退出前的游戏属性" + this.toString());
System.out.println("退出游戏");
System.out.println("-------------------");
}
// 创建备忘录
public Memoto createMemoto() {
Memoto memoto = new Memoto();
memoto.mCheckpoint = mCheckpoint;
memoto.mLifeValue = mLifeValue;
memoto.mWeapon = mWeapon;
return memoto;
}
// 恢复游戏
public void restore(Memoto memoto) {
this.mCheckpoint = memoto.mCheckpoint;
this.mLifeValue = memoto.mLifeValue;
this.mWeapon = memoto.mWeapon;
System.out.println("恢复后的游戏属性" + this.toString());
}
@Override
public String toString() {
return "CallOfDuty{" +
"mCheckpoint=" + mCheckpoint +
", mLifeValue=" + mLifeValue +
", mWeapon='" + mWeapon + '\'' +
'}';
}
}
在CallOfDuty游戏类中,我们存储了几个关键字,关卡、人物的生命值、武器,当调用play函数玩游戏时,我们队关卡和人物的生命值进行修改。在该类中科院通过createMemoto函数来创建该用户的备忘录对象,也就是将自身的状态保存到一个Memoto对象中。外部可以通过restore函数将CallOfDuty对象的状态从备忘录对象中恢复。
我们先来看看备忘录对象,它只是存储CallOfDuty对象的字段,具体代码如下:
package com.guifa.memorandumdemo;
/**
* 备忘录类
*/
public class Memoto {
public int mCheckpoint;
public int mLifeValue;
public String mWeapon;
@Override
public String toString() {
return "Memoto{" +
"mCheckpoint=" + mCheckpoint +
", mLifeValue=" + mLifeValue +
", mWeapon='" + mWeapon + '\'' +
'}';
}
}
这是一个无状态、无操作的实体类。只负责用来存储Orginator角色的一些数据,防止外部直接访问Originator。
而备忘录的操作者则是Caretaker角色,我们看看相关代码:
package com.guifa.memorandumdemo;
/**
* Caretaker,负责管理Memoto
*/
public class Caretaker {
/**
* 备忘录
*/
public Memoto mMemoto;
/**
* 存档
*
* @param memoto memoto
*/
public void archive(Memoto memoto) {
this.mMemoto = memoto;
}
/**
* 取档
*
* @return memoto
*/
public Memoto getMemoto() {
return mMemoto;
}
}
Caretaker类的职责很简单,就是负责管理Memoto对象,也就是备忘录对象。
我们再来看看客户端使用的代码。
package com.guifa.memorandumdemo;
public class Client {
public static void main(String[] args) {
// 构建游戏对象
CallOfDuty game = new CallOfDuty();
// 1.打游戏
game.play();
Caretaker caretaker = new Caretaker();
// 2.游戏存档
caretaker.archive(game.createMemoto());
// 3.退出游戏
game.quit();
// 4.恢复游戏
game.restore(caretaker.getMemoto());
}
}
输出结果:
上述过程大致有如下4步:
(1) 开始游戏,闯关升级;
(2)游戏退出之前存档;
(3)退出游戏;
(4)重启游戏,从存档中恢复游戏进度。
CallOfDuty在这里为Originator角色,也就是需要存储数据的对象,在这里并没有直接存储CallOfDuty的对象,而是通过Memoto对CallOfDuty对象的数据进行存储,然后再存储Memoto对象,最终对Memoto的存取操作则交给Caretaker对象。再这个过程中,各个角色职责清晰、单一,代码也比较简单,即对外屏蔽了对CallOfDuty角色的直接访问,在满足了对象状态存取功能的同时也使得该模块的结构保持清晰、整洁。
总结
备忘录模式是在不破坏封装的条件下,通过备忘录对象存储另外一个对象内部状态的快照,在将来合适的时候把这个对象还原到存储起来的状态。
优点:
- 给用户提供了一种可以恢复状态的机制,可以使用户方便地回到某个历史的状态。
- 实现了信息的封装,使得用户不需要关系状态的保存细节。
缺点:
消耗资源,如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。