设计模式之备忘录模式

1. 概述

备忘录模式是一种行为型设计模式,它允许在不暴露对象内部状态的情况下保存对象的内部状态,并在以后可以恢复到先前的状态。 它主要用于撤销操作、版本控制等场景。

2. 使用场景

  • 需要保存和恢复对象内部状态,且不希望暴露对象内部结构时。 这是备忘录模式的核心应用场景。
  • 需要实现撤销/重做 (Undo/Redo) 功能时。 例如,文本编辑器、图像编辑器等。
  • 需要进行事务回滚时。 在数据库操作中,可以使用备忘录模式来保存事务开始前的状态,以便在事务失败时回滚到初始状态。
  • 需要创建对象的快照 (Snapshot) 用于历史记录或审计时。 例如,游戏中的存档功能。
  • 工作流或者流程审批:在工作流系统中,一个流程实例可能需要经历多个状态,并且可能需要回退到之前的某个状态。备忘录模式可以用来保存流程实例的状态,以便在需要时进行回退。

3. 优缺点

备忘录模式的优点:

  • 保存和恢复对象的状态: 备忘录模式允许在不破坏封装性的前提下,保存和恢复对象的状态。 发起人可以完全控制存储和恢复状态的细节。
  • 实现撤销/重做功能: 备忘录模式可以用来实现撤销/重做功能。 允许多个状态的保存和恢复,实现多级撤销。
  • 支持版本控制: 备忘录模式可以用来实现版本控制。
  • 符合单一职责原则: 将状态的保存和恢复的职责分离到备忘录类中,符合单一职责原则。

备忘录模式的缺点:

  • 存储成本: 如果需要保存的状态很多,或者状态对象很大,那么存储备忘录对象的成本可能会很高。 需要考虑备忘录对象的生命周期管理,避免内存泄漏。
  • 复杂性: 备忘录模式可能会增加代码的复杂性,特别是当状态对象比较复杂时。 需要仔细设计备忘录类的结构,以及发起人和负责人之间的交互。
  • 负责人职责不明确: 负责人只负责存储备忘录对象,不参与状态的修改,这可能导致负责人类的职责不够明确。

4. 代码示例

示例:文本编辑器

还是以一个简单的文本编辑器为例。 文本编辑器允许用户输入文本,并提供撤销功能。 我们可以使用备忘录模式来保存文本编辑器的状态(即文本内容),并在用户需要撤销时,恢复到之前的状态。

代码实现

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// 1. 备忘录 (Memento) 类
class EditorMemento {
private final String text;

public EditorMemento(String text) {
this.text = text;
}

public String getText() {
return text;
}
}

// 2. 发起人 (Originator) 类
class TextEditor {
private String text;

public TextEditor() {
this.text = ""; // 初始文本为空
}

public void setText(String text) {
this.text = text;
}

public String getText() {
return text;
}

// 创建备忘录,保存当前状态
public EditorMemento save() {
System.out.println("保存状态: " + text);
return new EditorMemento(text);
}

// 从备忘录恢复状态
public void restore(EditorMemento memento) {
this.text = memento.getText();
System.out.println("恢复状态: " + text);
}
}

// 3. 负责人 (Caretaker) 类
class History {
private final List<EditorMemento> mementos = new ArrayList<>();

public void push(EditorMemento memento) {
mementos.add(memento);
}

public EditorMemento pop() {
if (mementos.isEmpty()) {
return null; // 或者抛出异常
}
EditorMemento lastState = mementos.remove(mementos.size() - 1);
return lastState;
}
}

// 4. 客户端代码 (演示)
public class MementoPatternDemo {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
History history = new History();

editor.setText("这是第一版内容");
history.push(editor.save());

editor.setText("这是第二版内容");
history.push(editor.save());

editor.setText("这是第三版内容");
System.out.println("当前内容: " + editor.getText()); // 当前内容: 这是第三版内容

// 撤销到第二版
EditorMemento memento = history.pop();
if (memento != null) {
editor.restore(memento);
}
System.out.println("当前内容: " + editor.getText()); // 当前内容: 这是第二版内容

// 撤销到第一版
memento = history.pop();
if (memento != null) {
editor.restore(memento);
}
System.out.println("当前内容: " + editor.getText()); // 当前内容: 这是第一版内容
}
}

5. 运行结果

1
2
3
4
5
6
7
保存状态: 这是第一版内容
保存状态: 这是第二版内容
当前内容: 这是第三版内容
恢复状态: 这是第二版内容
当前内容: 这是第二版内容
恢复状态: 这是第一版内容
当前内容: 这是第一版内容

关键点说明:

  1. EditorMemento (备忘录类):

    • 负责存储 TextEditor 的内部状态 (text)。
    • EditorMemento 对象只包含状态信息,并且不提供修改状态的方法 (只读)。 这保证了 TextEditor 的状态只能通过 TextEditor 本身来修改,避免了外部直接修改状态导致的问题.
  2. TextEditor (发起人类):

    • TextEditor 是我们要保存状态的对象。
    • setText() 方法用于设置文本内容。
    • save() 方法创建一个 EditorMemento 对象,保存当前 TextEditor 的状态 (创建一个快照)。
    • restore() 方法从 EditorMemento 对象恢复 TextEditor 的状态。
  3. History (负责人):

    • History 负责保存和管理备忘录对象。
    • 它使用 List 来存储 EditorMemento 对象。
    • push() 方法将 EditorMemento 对象添加到 List 中。
    • pop() 方法从 List 中移除并返回最后一个 EditorMemento 对象 (实现撤销功能)。
  4. MementoPatternDemo (客户端):

    • 客户端代码创建 TextEditorHistory 对象。
    • 客户端代码设置 TextEditor 的文本内容,并使用 history.push(editor.save()) 保存状态。
    • 客户端代码可以使用 history.pop() 获取之前的 EditorMemento 对象,并使用 editor.restore() 恢复到之前的状态。

6. 注意事项

  • 备忘录的不可变性: 备忘录对象一旦创建,就不应该被修改。这保证了状态的安全性。 可以将备忘录类设计为不可变类。
  • 负责人不修改备忘录: 负责人只负责存储和管理备忘录对象,不应该修改备忘录对象的内容。 负责人不应该持有备忘录对象的引用,只应该持有备忘录对象的副本。
  • 发起人创建和恢复备忘录: 只有发起人才能创建备忘录对象,并且只有发起人才能从备忘录对象恢复状态。 可以使用内部类来实现备忘录类,限制备忘录类的访问权限。

7. 总结

备忘录模式是一种行为型设计模式,它通过将对象的状态封装到独立的备忘录对象中,实现了在不破坏封装性的前提下保存和恢复对象状态的功能。 这种模式特别适用于需要实现撤销/重做、版本控制、事务回滚等功能的场景。