Memento 模式
使用Memento模式可以实现应用程序的以下功能。
- Undo(撤销)
- Redo(重做)
- History(历史记录)
- Snapshot(快照)
Memento模式事先将某个时间点的实例的状态保存下来,之后在必要时,再将实例恢复至当时的状态。
示例程序
类的一览表
名字 | 说明 |
---|---|
Memento | 表示Gamer状态的类 |
Gamer | 表示游戏主人公的类。它会生成Memento的实例 |
Main | 进行游戏的类。它会事先保存Memento的实例,之后会根据需要恢复Gamer的状态。 |
示例程序类图
程序功能:
- 游戏是自动进行的
- 游戏的主人公通过掷骰子来决定下一个状态
- 当骰子点数为1的时候,主人公的金钱会增加
- 当骰子点数为2的时候,主人公的金钱会减少
- 当骰子点数为6的时候,主人公会得到水果
- 主人公没有钱的时候游戏会结束
Memento
import java.util.*;
public class Memento {
int money; // 所持金钱
ArrayList fruits; // 当前获得的水果
public int getMoney() { // 获取当前所持金钱(narrow interface)
return money;
}
Memento(int money) { // 构造函数(wide interface)
this.money = money;
this.fruits = new ArrayList();
}
void addFruit(String fruit) { // 添加水果(wide interface)
fruits.add(fruit);
}
List getFruits() { // 获取当前所持所有水果(wide interface)
return (List)fruits.clone();
}
}
Gamer
import java.util.*;
public class Gamer {
private int money; // 所持金钱
private List fruits = new ArrayList(); // 获得的水果
private Random random = new Random(); // 随机数生成器
private static String[] fruitsname = { // 表示水果种类的数组
"苹果", "葡萄", "香蕉", "橘子",
};
public Gamer(int money) { // 构造函数
this.money = money;
}
public int getMoney() { // 获取当前所持金钱
return money;
}
public void bet() { // 投掷骰子进行游戏
int dice = random.nextInt(6) + 1; // 掷骰子
if (dice == 1) { // 骰子结果为1…增加所持金钱
money += 100;
System.out.println("所持金钱增加了。");
} else if (dice == 2) { // 骰子结果为2…所持金钱减半
money /= 2;
System.out.println("所持金钱减半了。");
} else if (dice == 6) { // 骰子结果为6…获得水果
String f = getFruit();
System.out.println("获得了水果(" + f + ")。");
fruits.add(f);
} else { // 骰子结果为3、4、5则什么都不会发生
System.out.println("什么都没有发生。");
}
}
public Memento createMemento() { // 拍摄快照
Memento m = new Memento(money);
Iterator it = fruits.iterator();
while (it.hasNext()) {
String f = (String)it.next();
if (f.startsWith("好吃的")) { // 只保存好吃的水果
m.addFruit(f);
}
}
return m;
}
public void restoreMemento(Memento memento) { // 撤销
this.money = memento.money;
this.fruits = memento.getFruits();
}
@Override
public String toString() { // 用字符串表示主人公状态
return "[money = " + money + ", fruits = " + fruits + "]";
}
private String getFruit() { // 获得一个水果
String prefix = "";
if (random.nextBoolean()) {
prefix = "好吃的";
}
return prefix + fruitsname[random.nextInt(fruitsname.length)];
}
}
Main
public class Main {
public static void main(String[] args) {
Gamer gamer = new Gamer(100); // 最初的所持金钱数为100
Memento memento = gamer.createMemento(); // 保存最初的状态
for (int i = 0; i < 100; i++) {
System.out.println("==== " + i); // 显示掷骰子的次数
System.out.println("当前状态:" + gamer); // 显示主人公现在的状态
gamer.bet(); // 进行游戏
System.out.println("所持金钱为" + gamer.getMoney() + "元。");
// 决定如何处理Memento
if (gamer.getMoney() > memento.getMoney()) {
System.out.println(" (所持金钱增加了许多,因此保存游戏当前的状态)");
memento = gamer.createMemento();
} else if (gamer.getMoney() < memento.getMoney() / 2) {
System.out.println(" (所持金钱减少了许多,因此将游戏恢复至以前的状态)");
gamer.restoreMemento(memento);
}
// 等待一段时间
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("");
}
}
}
示例程序时序图
Memento 模式中登场的角色
1.Originator(生成者)
Originator角色会在保存自己的最新状态时生成Memento角色。当把以前保存的Memento角色传递给Originator角色时,它会将自己恢复至生成该Memento角色时的状态。在示例程序中,由Gamer类扮演此角色。
2.Memento(纪念品)
Memento角色会将Originator角色的内部信息整合在一起。在Memento角色中虽然保存了Originator角色的信息,但它不会向外公布这些信息。
Memento 角色有以下两种接口(API)。
- wide interface——宽接口(API)
Memento角色提供的“宽接口(API)”是指所有用于获取恢复对象状态信息的方法的集合。由于宽接口(API)会暴露所有Memento角色的内部信息,因此能够使用宽接口(API)的只有Originator角色。 - narrow interface——窄接口(API)
Memento角色为外部的Caretaker角色提供了“窄接口(API)”。可以通过窄接口(API)获取的Memento角色的内部信息非常有限,因此可以有效地防止信息泄漏。
通过对外提供以上两种接口(API),可以有效地防止对象的封装性被破坏。
在示例程序中,由Memento类扮演此角色。
Originator角色和Memento角色之间有着非常紧密的联系。
3.Caretaker(负责人)
当Caretaker角色想要保存当前的Originator角色的状态时,会通知Originator角色。Originator角色在接收到通知后会生成Memento角色的实例并将其返回给Caretaker角色。由于以后可能会用Memento实例来将Originator恢复至原来的状态,因此Caretaker角色会一直保存Memento实例。在示例程序中,由Main类扮演此角色。
不过,Caretaker角色只能使用Memento角色两种接口(API)中的窄接口(API),也就是说它无法访问Memento角色内部的所有信息。它只是将Originator角色生成的Memento角色当作一个黑盒子保存起来。
虽然Originator角色和Memento角色之间是强关联关系,但Caretaker角色和Memento角色之间是弱关联关系。Memento角色对Caretaker角色隐藏了自身的内部信息。
接口(API)宽窄的含义补充
“窄”的意思是指外部可以操作的类的内部的内容很少。“宽”则与之相反。