概念

备忘录模式(Memento Pattern)是一种行为型设计模式,它允许在不暴露对象实现细节的情况下,捕获并存储对象的内部状态,并在需要时恢复到先前的状态


备忘录模式通过引入三个核心角色来实现这一点:发起人(Originator)、备忘录(Memento)和管理者(Caretaker)

  • 发起人(Originator)

    负责创建备忘录对象,并在需要时使用备忘录对象恢复其状态

    发起人可以访问其内部状态,并在需要时将其保存到备忘录中或从备忘录中恢复状态

  • 备忘录(Memento)

    用于存储发起人对象的内部状态

    备忘录对象可以是不可变的,以确保其状态不会被外部修改

  • 管理者(Caretaker)

    负责存储和管理备忘录对象

    管理者通常不直接操作备忘录的内容,而是将备忘录对象传递给发起人,以便发起人可以恢复其状态


备忘录模式的核心思想是将对象的状态保存到备忘录对象中,并在需要时将状态恢复到先前的状态,从而实现对象状态的回溯

这种模式的优点在于,可以避免在对象内部暴露其状态的细节,同时也使得状态的存储和恢复变得更加灵活和可控

备忘录模式适用于以下情况:

  • 当需要保存和恢复对象的状态,并且不希望暴露对象内部状态细节时,可以使用备忘录模式
  • 当需要实现撤销/重做功能或者历史记录功能时,备忘录模式也是一个很好的选择


举个简单的例子,考虑一个文本编辑器

用户在编辑文本时可以进行多次修改,而备忘录模式可以用来保存每次修改前的文本状态,以便用户可以撤销到先前的状态或者查看编辑历史记录


实现条件

  1. 需要保存和恢复对象的状态

    备忘录模式适用于需要保存对象状态,并在需要时将其恢复到先前状态的情况

    这通常涉及到对象的状态频繁变化,需要在某个时间点保存状态以便后续恢复

  2. 需要实现状态保存和恢复的封装

    备忘录模式将状态的保存和恢复封装到备忘录对象中,因此需要定义备忘录对象和相应的接口来实现状态的保存和恢复

  3. 需要保持备忘录对象与原发器对象之间的隔离

    备忘录模式通过备忘录对象来保存原发器对象的状态,因此需要保持备忘录对象与原发器对象之间的隔离,以防止原发器对象直接访问备忘录对象的内部状态

  4. 需要提供恢复状态的接口

    备忘录模式通常需要提供一个接口或方法来将对象恢复到之前保存的状态

    这样,在需要恢复状态时,可以通过调用该接口或方法来实现状态的恢复


优点

  1. 状态保存和恢复的封装

    备忘录模式将对象状态的保存和恢复封装到备忘录对象中,使得原发器对象可以专注于自身的业务逻辑,而不需要关心状态的保存和恢复

  2. 状态的可控性

    备忘录模式允许在任意时间点保存对象的状态,并在需要时将其恢复到先前的状态,从而提供了一种灵活的方式来管理对象的状态

  3. 隔离备忘录对象与原发器对象

    备忘录模式通过备忘录对象来保存原发器对象的状态,从而实现了备忘录对象与原发器对象之间的隔离,使得原发器对象无法直接访问备忘录对象的内部状态,保证了对象状态的封装性和安全性

  4. 支持多次撤销和重做操作

    备忘录模式允许保存多个状态快照,并支持多次撤销和重做操作,从而提供了一种有效的方式来管理对象状态的变化历史


缺点

  1. 可能导致内存消耗过大

    备忘录模式需要在备忘录对象中保存对象的状态信息,如果对象的状态信息较大或者状态信息保存的备忘录对象过多,可能会导致内存消耗过大

  2. 可能导致性能下降

    备忘录模式需要在备忘录对象中保存对象的状态信息,如果频繁保存状态或者频繁恢复状态,可能会导致性能下降

  3. 可能增加代码复杂度

    备忘录模式需要定义备忘录对象、原发器对象和负责人对象等多个角色,并且需要正确管理这些对象之间的关系,可能会增加代码的复杂度


实现方式

编辑器类

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
90
91
92
93
94
/**
* EditorMemento 类用于保存编辑器的状态(内容)。
*/
class EditorMemento {
/**
* 构造函数
* @param {string} content 编辑器的内容
*/
constructor(content) {
this.content = content
}
}

/**
* TextEditor 类表示一个文本编辑器,支持打字、保存状态、撤销操作等功能。
*/
class TextEditor {
/**
* 构造函数
*/
constructor() {
this.content = '' // 编辑器当前的内容
this.history = [] // 编辑器的状态历史记录
this.historySize = 10 // 限制历史记录的最大大小
}

/**
* 在当前内容后添加文字。
* @param {string} words 要添加的文字
*/
type(words) {
this.content += words
}

/**
* 保存当前编辑器状态到历史记录。
*/
save() {
this.history.push(new EditorMemento(this.content)) // 将当前内容状态保存到历史记录
if (this.history.length > this.historySize) {
this.history.shift() // 如果历史记录超过限制,则移除最旧的状态
}
}

/**
* 撤销最后的操作,恢复之前的状态。
*/
undo() {
if (this.history.length > 0) {
this.history.pop() // 移除最新保存的状态
// 如果历史记录为空,则内容为空字符串;否则,内容恢复为最新保存的状态
this.content = this.history.length === 0 ? '' : this.history[this.history.length - 1].content
} else {
console.error('没有更多的历史状态可以撤销')
}
}

/**
* 恢复到指定历史记录的状态。
* @param {number} index 要恢复的历史记录的索引
*/
restore(index) {
if (index >= 0 && index < this.history.length) {
this.content = this.history[index].content // 恢复指定索引的内容状态
} else {
console.error('无效的历史索引')
}
}

/**
* 获取当前编辑器的内容。
* @return {string} 编辑器的内容
*/
getContent() {
return this.content
}

/**
* 获取编辑器的历史记录(状态内容的副本)。
* @return {string[]} 历史记录中内容的数组
*/
getHistory() {
return this.history.map(memento => memento.content) // 返回历史记录中内容的副本,而非引用
}

/**
* 清除编辑器的历史记录。
*/
clearHistory() {
this.history = []
}
}

export default TextEditor


怎么使用

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
// 导入TextEditor类
import TextEditor from '../MediatorPattern'

// 创建一个TextEditor实例
const editor = new TextEditor()

// 输入文本并保存
editor.type('Hello, ')
editor.save()
// 继续输入文本并保存
editor.type('world!')
editor.save()

// 打印当前编辑器内容
console.log(editor.getContent()) // 输出:Hello, world!

// 执行撤销操作
editor.undo()
// 打印撤销后的内容
console.log(editor.getContent()) // 输出:Hello,

// 再次执行撤销操作
editor.undo()
// 打印再次撤销后的内容
console.log(editor.getContent()) // 输出:(空字符串)

// 尝试使用无效的历史索引进行恢复操作
editor.restore(1) // 输出:无效的历史索引

// 打印编辑器的历史记录
console.log(editor.getHistory())


场景

  1. 文本编辑器和IDE:

    文本编辑器可以使用备忘录模式来保存编辑历史记录,以便用户可以撤销到先前的状态或者查看编辑历史

  2. 图形编辑器

    图形编辑器(如绘图软件)可以使用备忘录模式来保存图形对象的状态,以便用户可以撤销/重做操作,或者在需要时恢复到先前的绘图状态

  3. 电子邮件应用

    在电子邮件应用中,用户可以使用备忘录模式保存草稿状态,以便在稍后继续编辑或者恢复到先前的编辑状态

  4. 游戏应用

    游戏应用中可以使用备忘录模式来保存游戏进度或者关卡状态,以便玩家可以在需要时回溯到先前的游戏状态

  5. 事务管理

    在数据库系统中,备忘录模式可以用于事务管理,允许在执行事务时保存数据库状态,并在需要时回滚到先前的状态

  6. 撤销/重做功能

    许多应用程序都提供了撤销/重做功能,备忘录模式是实现这种功能的常见方式之一

    它可以记录每次操作前的状态,以便用户可以在需要时撤销或者重做操作


源代码