1. 备忘录模式
1.1 简介
备忘录模式是一种软件设计模式,它提供一种能将一个对象恢复到旧状态的能力(回滚式的撤销操作)。备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式。Memento Pattern在不破坏封闭的前提下,捕获并外部化一个对象的内部状态,这样之后就可以将该对象恢复到保存时的状态。
在应用软件的开发过程中,很多时候我们都需要记录一个对象的内部状态。在具体实现过程中,为了允许用户取消不确定的操作或从错误中恢复过来,需要实现备份点和撤销机制,而要实现这些机制,必须事先将状态信息保存在某处,这样才能将对象恢复到它们原先的状态。备忘录模式是一种给我们的软件提供后悔药的机制,通过它可以使系统恢复到某一特定的历史状态。
备忘录模式通过三个对象来实现:originator、caretaker和memento。originator是一些拥有内部状态的对象。caretaker将在originator之上进行一些处理,但是同时希望能够撤销之前的处理操作。caretaker首先向originator请求一个memento对象,然后它将进行它所要进行的任何操作。为了回滚回进行这些操作的状态,caretaker将返回memento对象给originator。memento对象是一种不透明对象(caretaker不能也不应该对其改变)。使用这种模式的时候,需要注意的是originator可能改变其他对象或资源的状态,备忘录模式只对单一对象起作用。
1.2 Memento Pattern的uml
Memento Pattern的角色:
Memento:
备忘录,用于存储originator对象的内部状态。memento对象应该根据originator的实际需要,存储必要的originator对象的内部状态。它能够保护对状态的访问。memento实际上拥有两个接口。caretaker对象只能访问memento对象的窄接口,即它只能够把memento对象传递给其他对象。相反,originator对象可以访问memento的宽接口,即获取所有能恢复到旧状态的必要数据。理想情况下,只有创建memento的oiginator对象才有权限访问memento对象的内部状态信息.Originator:
发起人。创建一个memento对象来存储它当前内部状态的一个快照,这些内部信息将保存在memento对象之中。Caretaker:
管理者,负责memento对象的安全存储,但从不对memento对象的内容进行操作或检查。
2. Memento Pattern的示例
我们以游戏为示例,游戏角色拥有生命值和经验值,每一次打怪升级都需要消耗生命值和经验值,冷却足够时间后都会恢复。
** GamePlayer:**
// Originator
public class GamePlayer {
// 遊戲角色的生命值
private int mHp;
// 遊戲角色的經驗值
private int mExp;
public GamePlayer(int hp, int exp)
{
mHp = hp;
mExp = exp;
}
public GameMemento saveToMemento()
{
return new GameMemento(mHp, mExp);
}
public void restoreFromMemento(GameMemento memento)
{
mHp = memento.getGameHp();
mExp = memento.getGameExp();
}
public void play(int hp, int exp)
{
mHp = mHp - hp;
mExp = mExp + exp;
}
}
** GameMemento:**
// Memento
public class GameMemento {
// 假設只有這兩個資料要保留
private int mGameHp;
private int mGameExp;
public GameMemento(int hp, int exp)
{
mGameHp = hp;
mGameExp = exp;
}
public int getGameHp()
{
return mGameHp;
}
public int getGameExp()
{
return mGameExp;
}
}
** Caretaker:**
// Caretaker
public class GameCaretaker {
// 保留要處理的資料。
// 這邊只是範例,所以 Caretaker
// 只能處理一個 Memento。
// 實務上當然可以用更複雜的結構來
// 處理多個 Memento,如 ArrayList。
private GameMemento mMemento;
public GameMemento getMemento()
{
return mMemento;
}
public void setMemento(GameMemento memento)
{
mMemento = memento;
}
}
** 客户端调用示例:**
public static void main(String[] args)
{
// 創造一個遊戲角色
GamePlayer player = new GamePlayer(100, 0);
// 先存個檔
GameCaretaker caretaker = new GameCaretaker();
caretaker.setMemento(player.seveToMemento());
// 不小心死掉啦
player.play(-100, 10);
// 重新讀取存檔,又是一尾活龍
player.restoreFromMemento(caretaker.getMemento());
}
3. 总结
备忘录模式的优点:
- 提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用先前存储起来的备忘录将状态复原。
- 实现了信息的封装,一个备忘录对象是一种原发器对象的表示,不会被其他代码改动,这种模式简化了原发器对象,备忘录只保存原发器的状态,采用堆栈来存储备忘录对象可以实现多次撤销操作,可以通过在负责人中定义集合对象来存储多个备忘录。
备忘录模式的缺点:
- 资源消耗过大,如果类的成员变量太多,就不可避免占用大量的内存,而且每保存一次对象的状态都需要消耗内存资源,如果知道这一点大家就容易理解为什么一些提供了撤销功能的软件在运行时所需的内存和硬盘空间比较大了。
备忘录模式适用环境:
- 保存一个对象在某一个时刻的状态或部分状态,这样以后需要时它能够恢复到先前的状态。
- 如果用一个接口来让其他对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过负责人可以间接访问其内部状态
备忘录模式应用:
- 几乎所有的文字或者图像编辑软件都提供了撤销(Ctrl+Z)的功能,即撤销操作,但是当软件关闭再打开时不能再进行撤销操作,也就是说不能再回到关闭软件前的状态,实际上这中间就使用到了备忘录模式,在编辑文件的同时可以保存一些内部状态,这些状态在软件关闭时从内存销毁,当然这些状态的保存也不是无限的,很多软件只提供有限次的撤销操作。
- 数据库管理系统DBMS所提供的事务管理应用了备忘录模式,当数据库某事务中一条数据操作语句执行失败时,整个事务将进行回滚操作,系统回到事务执行之前的状态。
备忘录模式扩展:
备忘录的封装性
- 为了确保备忘录的封装性,除了原发器外,其他类是不能也不应该访问备忘录类的,在实际开发中,原发器与备忘录之间的关系是非常特殊的,它们要分享信息而不让其他类知道,实现的方法因编程语言的不同而不同。
- C++可以用friend关键字,使原发器类和备忘录类成为友元类,互相之间可以访问对象的一些私有的属性;
- 在Java语言中可以将两个类放在一个包中,使它们之间满足默认的包内可见性,也可以将备忘录类作为原发器类的内部类,使得只有原发器才可以访问备忘录中的数据,其他对象都无法使用备忘录中的数据。
多备份实现 - 在负责人中定义一个集合对象来存储多个状态,而且可以方便地返回到某一历史状态。
- 在备份对象时可以做一些记号,这些记号称为检查点(Check Point)。在使用HashMap等实现时可以使用Key来设置检查点。