备忘录模式

备忘录模式

一、什么是备忘录模式?

备忘录模式(Memento Desidn Pattern):在不违背封装性原则的前提下,捕获一个对象的内部状态,并在对象之外保存,以便之后恢复对象为先前的状态。

它的几个特点是:

  • 要求不要违背封装性原则
  • 保存的是对象的内部状态,意味着对象外部不应该对保存的数据进行操作,而是假设不知道它的数据结构
  • 保存的数据,相当于快照,可以用于撤销恢复

备忘录模式,也称为快照模式。

二、为什么要用备忘录模式?

备忘录模式,使用的场景比较有限,主要就是用在防丢失、撤销、恢复上。

比如说,

  • 编辑软件一般都会提供撤回操作 Ctrl + Z,回退到修改之前的状态
  • 数据库也提供了回滚操作,可以回滚到之前的状态
  • 玩游戏时,游戏也可以存档,失败后可以从存档状态重新开始

很多地方都可以用备忘录模式,但是场景大部分都是快照、撤销、恢复等。

三、怎么用备忘录模式?

备忘录模式中的角色:

  • 生成者(Originator):生成和恢复快照的角色
  • 快照(Memento):生成者生成的内部状态快照数据
  • 负责人(Caretaker):负责通知生成者进行快照的生成和恢复,快照也是在此管理

备忘录模型结构:

备忘录模式结构

示例程序结构:

备忘录模式结构

生成者(Originator):

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
64
65
66
public class Gamer {

private static String[] fruitsName = {
"苹果", "葡萄", "香蕉", "橘子"
};

private int money;
private List<String> fruits = new ArrayList<>();
private Random random = new Random();

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

public int getMoney() {
return money;
}

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

public void bet() {
int dice = random.nextInt(6) + 1;
if (dice == 1) {
money += 100;
System.out.println("所持金钱增加了。");
} else if (dice == 2) {
money /= 2;
System.out.println("所持金钱减半了。");
} else if (dice == 6) {
String f = getFruit();
System.out.println("获得了水果(" + f + ")。");
fruits.add(f);
} else {
System.out.println("什么都没有发生。");
}
}

public Memento createMemento() {
Memento memento = new Memento(money);
for (String f : fruits) {
if (f.startsWith("好吃的")) {
memento.addFruit(f);
}
}
return memento;
}

public void restoreMemento(Memento memento) {
this.money = memento.getMoney();
this.fruits = memento.getFruits();
}

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

快照(Memento):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Memento {

private int money;
private ArrayList<String> fruits;

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

public int getMoney() {
return money;
}

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

@SuppressWarnings("unchecked")
ArrayList<String> getFruits() {
return (ArrayList<String>) fruits.clone();
}

}

负责人(Caretaker):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) {
Gamer gamer = new Gamer(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() + " 元。");

// 保存状态
if (gamer.getMoney() > memento.getMoney()) {
System.out.println("所持金钱增加了,保存当前状态。");
memento = gamer.createMemento();
} else if (gamer.getMoney() < memento.getMoney() / 2) {
System.out.println("所持金钱减半了,保存当前状态。");
gamer.restoreMemento(memento);
}
}
}

四、备忘录模式有什么优缺点?

优点:

  • 实现了内部状态的封装,避免被外部破坏
  • 提供了快速备份、撤销、恢复的操作
  • 将快照单独管理,避免生成者报错过多数据,符合单一职责

缺点:

  • 快照会占用较多内存资源

五、备忘录模式的优化?

  • 全量备份和增量备份相结合,低频全量备份,高频增量备份,两者结合来做恢复
  • 比如,每天进行一次全量备份,没小时进行一次增量备份
  • 恢复备份时,可以用前一天的全量备份 + 当天的增量备份
作者

jiaduo

发布于

2022-01-23

更新于

2023-04-03

许可协议