备忘录模式简单介绍
备忘录模式是一种行为模式,该模式用于保存对象当前的状态,并且在之后可以再次恢复到此状态,这有点像我们平时说的“后悔药”。备忘录模式实现的方式需要保证被保存的对象状态不嗯呢该被对象从外部访问,目的是为了保护号被保存的这些对象状态的完整性以及内部实现不向外暴露。
备忘录模式的定义
在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样,以后就可将该对象恢复到原先保存的状态。
备忘录模式的使用场景
- (1)需要保存一个对象在某一个时刻的状态或部分状态。
- (2)如果用一个接口来让其他对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过中间对象可以间接访问其内部状态。
说白了它主要应用在:对象在执行某些操作为防止意外而在执行操作前将对象状态备份的场景,有点类似于事务回滚的意思。
备忘录模式的 UML 类图
角色介绍:
- Originator:发起人角色,负责创建一个备忘录,可以记录、恢复自身的内部状态。同时 Originator 还可以根据需要决定 Memento 存储自身的哪些内部状态。
- Memento:备忘录角色,用于存储 Originator 的内部状态,并且可以防止 Originator 以外的对象访问 Memento(Originator 的内部状态)。
- Caretaker:负责任角色,负责存储备忘录,不能对备忘录的内容进行操作和访问,只能够将备忘录传递给其他对象。
备忘录模式的简单实战
对于备忘录模式来说,比较贴切的场景应该是游戏中的存档功能,该功能就是将游戏进度存储到本地文件系统或者数据库中,下次再次进入时从本地加载进度,使得玩家能够继续上一次的游戏之旅。这里我们以游戏存档为例来简单演示一下备忘录模式的实现。
首先我们建立游戏类、备忘录类、Caretaker(管理)类,玩游戏到某个节点对游戏进行存档,然后退出游戏,再次进入游戏时从存档中读取进度,并且存档时的进度:
先看游戏类:
public class Game {
private String name; //玩家姓名
private int position; //当前关卡
//开始游戏
public void play() {
Log.e("Game", "当前玩家:" + this.name + "\r\n"
+ "当前存档:" + (position == 0 ? "1 个" : "无") + "\r\n"
+ "当前游戏进度:" + "第 " + position + " 关");
position++;
}
//退出游戏
public void quit() {
Log.e("Game", "当前玩家:" + this.name + "\r\n"
+ "当前游戏进度:" + "第 " + position + " 关");
}
//创建备忘录
public Memento createMemento() {
Memento memento = new Memento();
memento.setName(name);
memento.setPosition(position);
return memento;
}
//恢复游戏
public void restore(Memento memento){
this.name = memento.getName();
this.position = memento.getPosition();
}
}
在 Game 游戏类中国,我们存储了几个关键字段(也就是所谓的状态),玩家的姓名以及当前通过的关卡。当调用 play 函数时表示开始进行游戏,没调用一次表示通过一个关卡。在该类中可以通过 createMemento 函数来创建当前用户的游戏进度(备忘录对象),也就是将自身的状态保存到一个 Memento 对象中。外部可以通过 restore 函数将 Game 对象的状态从备忘录对象中恢复。
我们再来看看备忘录对象,它只是存储 Game 对象的字段(状态信息),具体代码如下:
public class Memento {
private String name; //玩家姓名
private int position; //当前关卡
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
}
这个类只用来负责存储 Originator(发起人)角色的一些数据,防止外部直接访问 Originator 内部的状态。
而负责人的角色则为 Caretaker 角色,我们来看下它都做了什么操作:
public class Caretaker {
private Memento memento; //当前游戏状态
//存档
public void storeMemento(Memento memento) {
this.memento = memento;
}
//获取存档
public Memento restoreMemento() {
return memento;
}
}
Caretaker 类的职责很简单,就是负责管理 Memento 对象,也就是备忘录对象。
我们再来看一下客户端代码的实现:
//构建游戏对象
Game game = new Game();
//打游戏
game.play(); //通过关卡 1
game.play(); //通过关卡 2
game.play(); //通过关卡 3
game.play(); //通过关卡 4
game.play(); //通过关卡 5
//不玩了 存档
Caretaker caretaker = new Caretaker();
caretaker.storeMemento(game.createMemento());
//突然又想玩了继续玩...
game.play(); //通过关卡 6
game.play(); //通过关卡 7
game.play(); //通过关卡 8
//突然断电,恢复游戏(从游戏存档中继续游戏进度)
game.restore(caretaker.restoreMemento()); //恢复到关卡 5
Game 在这里为 Originator 角色,也就是需要存储数据的对象,在这里并没有直接存储 Game 对象,而是通过 Memento 对 Game 对象的数据进行存储,然后再存储 Memento 对象,最终对 Memento 的存储操作则交给 Caretaker 对象。在这个过程中,各个角色职责清晰、单一,代码也比较简单,既对外屏蔽了对 Game 角色状态的直接访问,在满足了对象状态存取功能的同时也使得该模块的结构保持清晰、整洁。
总结
备忘录模式是在不破坏封装的条件下,通过备忘录对象(Memento)存储另外一个对象内部状态的快照,在将来合适的时候把这个对象还原到存储起来的状态。
优点
- (1)给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
- (2)实现了信息的封装,使得用户不需要关系状态的保存细节。
缺点
- 消耗资源,如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存(因为需要重新创建 Memento 对象进行保存 Originator 中的各种状态)。