Memento模式

情景引入

我们在使用文本编辑器编写文件时,如果不小心删除了某句话,可以通过撤销(undo)功能将
文件恢复至之前的状态。有些文本编辑器甚至支持多次撤销,能够恢复至很久之前的版本。

使用面向对象编程的方式实现撤销功能时,需要事先保存实例的相关状态信息。然后,在撤销
时,还需要根据所保存的信息将实例恢复至原来的状态。

要想恢复实例,需要-个可以自由访问实例内部结构的权限。但是,如果稍不注意,又可能会
将依赖于实例内部结构的代码分散地编写在程序中的各个地方,导致程序变得难以维护。这种情况
就叫作“破坏了封装性”。

通过引入表示实例状态的角色,可以在保存和恢复实例时有效地防止对象的封装性遭到破坏。
这就是我们在本章中要学习的Memento模式。

使用Memento模式可以实现应用程序的以下功能。

  • Undo(撤销)
  • Redo(重做)
  • History(历史记录)
  • Snapshot (快照)

Memento有“纪念品”“遗物”“备忘录”的意思。

当大家从抽屉中拿出让人怀念的照片时,肯定会感慨万千,感觉仿佛又回到了照片中的那个时
候。Memento模式就是一个这样的设计模式, 它事先将某个时间点的实例的状态保存下来,之后在
有必要时,再将实例恢复至当时的状态。

示例程序

功能描述

这是一个收集水果和获取金钱数的掷骰子游戏,游戏规则很简单,具体如下。

  • 游戏是自动进行的
  • 游戏的主人公通过掷骰子来决定下一个状态
  • 当骰子点数为1的时候,主人公的金钱会增加
  • 当骰子点数为2的时候,主人公的金钱会减少
  • 当骰子点数为6的时候,主人公会得到水果
  • 主人公没有钱时游戏就会结束

在程序中,如果金钱增加,为了方便将来恢复状态,我们会生成Memento类的实例,将现在
的状态保存起来。所保存的数据为当前持有的金钱和水果。如果不断掷出了会导致金钱减少的点
数,为了防止金钱变为0而结束游戏,我们会使用Memento的实例将游戏恢复至之前的状态。
(memento 应用于存档/快照)

类的一览表

名字 说明
game Memento 表示 Gamer的状态的类
game Gamer 表示游戏主人公的类。它会生成 Memento的实例
Main 进行游戏的类。它会事先保存 Memento的,之后会根据需要恢复 Gamer的状态

UML

备忘录模式

主要代码

Memento 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Memento {
int money;
ArrayList fruits;
public int getMoney(){
return money;
}

public Memento(int money) {
this.money = money;
this.fruits = new ArrayList();
}

void addFruit(String fruit){
fruits.add(fruit);
}

List getFruits() {
return (List)fruits.clone();
}
}

Gamer 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class Gamer {
private int money;
private List fruits = new ArrayList();
private Random random = new Random();
private static String[] FRUIT_NAMES = {
"苹果", "葡萄", "香蕉", "橘子"
};

public Gamer(int money) {
this.money = money;
}

public int getMoney() {
return money;
}

public void bet() {
int dice = random.nextInt(6) + 1;
if (1 == dice) {
money += 100;
System.out.println("所持金钱增加了。");
} else if (2 == dice) {
money /= 2;
System.out.println("所持金钱减少了。");
} else if (6 == dice) {
String f = getFruit();
System.out.println("获得了水果(" + f + ").");
}else {
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.getMoney();
this.fruits = memento.getFruits();
}

@Override
public String toString() {
return "Gamer{" +
"money=" + money +
", fruits=" + fruits +
'}';
}

private String getFruit() {
String prefix = "";
if (random.nextBoolean()) {
prefix = "好吃的";
}
return prefix + FRUIT_NAMES[random.nextInt(FRUIT_NAMES.length)];
}
}

Main 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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模式
http://lhystutest.top/2022/10/26/设计模式/Memento 模式/Memento模式/
作者
lhy
发布于
2022年10月26日
许可协议