模式动机
每个人都有过后悔的时候,但人生并无后悔药,有些错误一旦发生就无法再挽回,有些人一旦错过就不会再回来,有些话一旦说出口就不可能再收回,这就是人生。为了不后悔,凡事我们都需要三思而后行。说了这么多,大家可能已经晕了,不是在学设计模式吗?为什么弄出这么一堆人生感悟来,呵呵,别着急,本章将介绍一种让我们可以在软件中实现后悔机制的设计模式——备忘录模式,它是软件中的“后悔药”,是软件中的“月光宝盒”。这就是备忘录模式的动机。
模式定义
备忘录模式(Memento Pattern)
在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。备忘录模式又叫做快照模式(Snapshot Pattern)或 Token 模式。
备忘录模式是一种
对象行为型模式
。
模式结构
角色组成
备忘录模式包含如下角色:
Originator(原发器):
它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。Memento(备忘录):
存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,原发器的设计在不同的编程语言中实现机制会有所不同。Caretaker(负责人):
负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。
结构图
示例代码
首先,是原发器。Originator 类充当原发器,完整代码如下所示:
/**
* 原发器
*/
public class Originator {
private String state;
/**
* 创建一个备忘录对象
*/
public Memento createMemento() {
return new Memento(this);
}
/**
* 根据备忘录对象恢复原发器先前状态
*/
public void restoreMemento(Memento memento) {
this.state = memento.getState();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
在真实业务中,原发器类是一个具体的业务类,它包含一些用于存储成员数据的属性。
其次,是备忘录。Memento 类充当备忘录,完整代码如下所示:
/**
* 备忘录
*/
public class Memento {
private String state;
public Memento(Originator originator) {
this.state = originator.getState();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
对于类而言,它通常提供了与原发器相对应的属性(可以是全部,也可以是部分)用于存储原发器的状态。
在设计备忘录类时需要考虑其封装性,除了 Originator 类,不允许其他类来调用备忘录类 Memento 的构造函数与相关方法,如果不考虑封装性,允许其他类调用 setState() 等方法,将导致在备忘录中保存的历史状态发生改变,通过撤销操作所恢复的状态就不再是真实的历史状态,备忘录模式也就失去了本身的意义。
然后,是负责人。Context 类充当负责人,完整代码如下所示:
/**
* 负责人
*/
public class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
对于负责人类 Caretaker,它用于保存备忘录对象,并提供 getMemento() 方法用于向客户端返回一个备忘录对象,原发器通过使用这个备忘录对象可以回到某个历史状态。在 Caretaker 类中不应该直接调用 Memento 中的状态改变方法,它的作用仅仅用于存储备忘录对象。将原发器备份生成的备忘录对象存储在其中,当用户需要对原发器进行恢复时再将存储在其中的备忘录对象取出。
最后,是客户端场景类,完整代码如下所示:
public class MementoClient {
public static void main(String[] args) {
Caretaker caretaker = new Caretaker(); // 创建负责人对象
Originator originator = new Originator(); // 创建原发器对象
originator.setState("0"); // 初始化状态标识 "0"
Memento memento1 = originator.createMemento(); // 创建状态为"0"的备忘录对象
caretaker.setMemento(memento1); // 将记录了Originator状态的备忘录 交给 Caretaker备忘录管理者储存
System.out.println("Originator 的当前状态:" + originator.getState());
System.out.println("----- 更改原发器的状态 -----");
originator.setState("1"); // 更改原发器的状态标识为"1"
System.out.println("Originator 的当前状态:" + originator.getState());
System.out.println("----- 撤销至原发器的先前状态 -----");
originator.restoreMemento(caretaker.getMemento());
System.out.println("Originator 的当前状态:" + originator.getState());
}
}
模式分析
- 备忘录是一个很特殊的对象,只有原发器对它拥有控制的权力,负责人只负责管理,而其他类无法访问到备忘录,因此我们需要对备忘录进行封装。
- 为了实现对备忘录对象的封装,需要对备忘录的调用进行控制,对于原发器而言,它可以调用备忘录的所有信息,允许原发器访问返回到先前状态所需的所有数据;对于负责人而言,只负责备忘录的保存并将备忘录传递给其他对象;对于其他对象而言,只需要从负责人处取出备忘录对象并将原发器对象的状态恢复,而无须关心备忘录的保存细节。理想的情况是只允许生成该备忘录的那个原发器访问备忘录的内部状态。
- 备忘录模式在很多软件的使用过程中普遍存在,但是在应用软件开发中,它的使用频率并不太高,因为现在很多基于窗体和浏览器的应用软件并没有提供撤销操作。如果需要为软件提供撤销功能,备忘录模式无疑是一种很好的解决方案。在一些字处理软件、图像编辑软件、数据库管理系统等软件中备忘录模式都得到了很好的应用。
优点
备忘录模式的主要优点如下:
- 它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。
- 备忘录实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作。
缺点
备忘录模式的主要缺点如下:
- 资源消耗过大,如果需要保存的原发器类的成员变量太多,就不可避免需要占用大量的存储空间,每保存一次对象的状态都需要消耗一定的系统资源。
适用环境
在以下情况下可以使用备忘录模式:
- 保存一个对象在某一个时刻的全部状态或部分状态,这样以后需要时它能够恢复到先前的状态,实现撤销操作。
- 防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象。
总结
- 备忘录模式在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。备忘录模式是一种对象行为型模式。
- 备忘录是一个很特殊的对象,只有原发器对它拥有控制的权力,负责人只负责管理,而其他类无法访问到备忘录,因此我们需要对备忘录进行封装。
- 为了实现对备忘录对象的封装,需要对备忘录的调用进行控制,对于原发器而言,它可以调用备忘录的所有信息,允许原发器访问返回到先前状态所需的所有数据;对于负责人而言,只负责备忘录的保存并将备忘录传递给其他对象;对于其他对象而言,只需要从负责人处取出备忘录对象并将原发器对象的状态恢复,而无须关心备忘录的保存细节。理想的情况是只允许生成该备忘录的那个原发器访问备忘录的内部状态。
- 备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。
- 备忘录实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作。