介绍
备忘录模式又叫快照模式(Snapshot Pattern)或Token模式,是对象的行为模式。
备忘录对象是一个用来存储另外一个对象内部状态的快照(snapshot)的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉住,并外部化存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。备忘录模式常常与命令模式和迭代器模式一同使用。
模式引入
很多应用软件都提供了一项功能,如 Word、记事本、Photoshop、Eclipse 等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 IE 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
备忘录模式能记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态。
模式角色
备忘录模式有以下角色:
- Originator(发起人),负责创建一个备忘录Memento,用以记录当前时刻自身的内部状态,并可使用备忘录恢复内部状态。Originator可以根据需要决定Memento存储自己的哪些内部状态。
- Memento(备忘录),负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- Caretaker(负责人),负责备忘录Memento,不检查Memento的内容。
模式实现
- 宽接口和白箱实现
- 窄接口和黑箱实现
在白箱子实现中,Memento对象持有Originator类的状态图,且Memento对象对Caretaker“透明”。在黑箱子实现中,Memento对象对Caretaker“不透明”
白箱子
如果Memento的内部所存储的状态对所有对象公开,那么这个实现是“白箱”实现。“白箱”实现将Originator的状态存储在一个大家看得到的地方,因此是破坏封装性的。但是通过程序猿自律,同样可以在一定程度上实现模式的大部分用意。
/**
* 发起人
*/
public class Originator {
// 一个状态
private String state;
// 工厂方法,返回一个新的备忘录对象
public Memento createMemento() {
return new Memento(state);
}
// 将发起人恢复到备忘录对象所记载的状态
public void restoreMemento(Memento memento) {
state = memento.getState();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = 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 memento;
}
public void saveMemento(Memento memento) {
this.memento = memento;
}
}
public class MementoClient {
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());
}
}
黑箱子
如果将Memento设成Originator类的内部类,将Memento的方法全部设成私有方法,这样只有Memento自己和发起人Originator可以调用;并在外部提供一个标识接口MementoIF给Caretaker以及其他对象,标识接口MementoIF没有提供任何方法,因此对外部来说Memento对象的内容都是不可见的。这是“黑箱”实现。
// 标识接口,实现模式中的“窄接口”
interface MementoIF {
}
/**
* 发起人
* Memento是Originator类的内部类
*/
public class Originator {
private String state;
// 工厂方法,返回一个新的备忘录对象
public MementoIF createMemento() {
return new Memento(state);
}
// 将发起人恢复到备忘录对象所记载的状态
public void restoreMemento(MementoIF memento) {
Memento m = (Memento) memento;
state = m.getState();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
/**
* 内部类,方法私有,并实现标识接口MementoIF
*/
protected 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;
}
}
}
/**
* 负责人
*/
class Caretaker {
private MementoIF memento;
public MementoIF retrieveMemento() {
return memento;
}
public void saveMemento(MementoIF memento) {
this.memento = memento;
}
}
class MementoClient {
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());
}
}
模式退化
在“黑箱”实现中,Originator可以兼任Caretaker。
// 标识接口
public interface MementoIF {
}
/**
* Originator兼任Caretaker
*/
public class Originator {
private String state;
// 改变状态
public void changeState(String state) {
this.state = state;
}
// 工厂方法,返回一个新的备忘录对象
public MementoIF createMemento() {
return new Memento(this);
}
// 将发起人恢复到备忘录对象所记载的状态
public void restoreMemento(MementoIF memento) {
Memento m = (Memento) memento;
changeState(m.state);
}
public String getState() {
return state;
}
protected class Memento implements MementoIF {
private String state;
private Memento(Originator o) {
this.state = o.getState();
}
private String getState() {
return state;
}
}
}
public class MementoClient {
public static void main(String[] args) {
Originator o = new Originator();
o.changeState("On");
// 保存状态
MementoIF m = o.createMemento();
o.changeState("Off");
// 恢复状态
o.restoreMemento(m);
}
}
负责人角色增强
在前面的实现中,负责人角色所承担的责任并不包括操控发起人角色进行备忘录的创建和状态的恢复,这两个责任是由客户端角色直接调用发起人角色和备忘录角色做到的。
如果能让负责人角色调用备忘录角色和发起人角色,进行备忘录创建和恢复发起人状态,那么客户端便不再需要协调备忘录角色和发起人角色,而只需要调用负责人角色即可。要做到这一点,负责人角色就必须持有一个对发起人角色的引用。
public class Originator {
private String state;
// 工厂方法,返回一个新的备忘录对象
public Memento createMemento() {
return new Memento(state);
}
// 将发起人恢复到备忘录对象所记载的状态
public void restoreMemento(Memento memento) {
state = memento.getState();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = 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 Originator originator;
private Memento memento;
public Caretaker(Originator originator) {
this.originator = originator;
}
public Memento restoreMemento() {
return originator.createMemento();
}
public void createMemento() {
this.memento = originator.createMemento();
}
}
public class MementoClient {
public static void main(String[] args) {
Originator o = new Originator();
Caretaker c = new Caretaker(o);
o.setState("On");
// 保存状态
c.createMemento();
o.setState("Off");
// 恢复状态
c.restoreMemento();
}
}
模式优缺点
优点:
- 有时一些发起人对象的内部信息必须保存在发起人对象以外的地方,但是必须要由发起人对象自己读取,这时,
使用备忘录模式可以把复杂的发起人内部信息对其他的对象屏蔽起来,从而可以恰当地保持封装的边界。 - 本模式简化了发起人类。发起人不再需要管理和保存其内部状态的一个个版本,客户端可以自行管理他们所需
要的这些状态的版本。 - 当发起人角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原。
缺点:
- 如果发起人角色的状态需要完整地存储到备忘录对象中,那么在资源消耗上面备忘录对象会很昂贵。
- 当负责人角色将一个备忘录 存储起来的时候,负责人可能并不知道这个状态会占用多大的存储空间,从而无法提醒用户一个操作是否很昂贵。
- 当发起人角色的状态改变的时候,有可能这个状态无效。如果状态改变的成功率不高的话,不如采取“假如”协议模式。
关于“假如”协议部分,摘抄自《Java与模式》,很不错的一本书哦~
模式的应用场景
- 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
- 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,Eclipse 等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作(和命令模式一起使用)。