大话设计模式C语言之备忘录模式

备忘录模式

备忘录模式 (MementoPattern) 备忘录模式是一种行为设计模式,允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

案例代码

原发器

原发器(Originator)类可以生成自身状态的快照,也可以在需要时通过快照恢复自身状态。
备忘录模式将创建状态快照(Snapshot)的工作委派给实际状态的拥有者原发器(Originator)对象。这样其他对象就不再需要从“外部”复制编辑器状态了,编辑器类拥有其状态的完全访问权,因此可以自行生成快照。

原发器接口 editor.h
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
#ifndef EDITOR_H
#define EDITOR_H

#include "memento.h"

typedef struct Editor Editor;

/* 创建 / 销毁 */
Editor *editor_create(void);
void editor_destroy(Editor *e);

/* 修改内容 */
int editor_set_text(Editor *e, const char *text);

/* 获取当前文本 */
const char *editor_get_text(const Editor *e);

/* 生成备忘录 */
Memento *editor_save(const Editor *e);

/* 从备忘录恢复 */
int editor_restore(Editor *e, const Memento *m);

#endif

原发器实现 editor.c
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
#include "editor.h"
#include <stdlib.h>
#include <string.h>

struct Editor {
char *text;
};

Editor *editor_create(void)
{
return calloc(1, sizeof(Editor));
}

void editor_destroy(Editor *e)
{
if (!e) return;

free(e->text);
free(e);
}

int editor_set_text(Editor *e, const char *text)
{
if (!e || !text) return -1;

char *copy = strdup(text);
if (!copy) return -1;

free(e->text);
e->text = copy;

return 0;
}

const char *editor_get_text(const Editor *e)
{
if (!e) return NULL;
return e->text;
}

Memento *editor_save(const Editor *e)
{
if (!e || !e->text) return NULL;
return memento_create(e->text);
}

int editor_restore(Editor *e, const Memento *m)
{
if (!e || !m) return -1;

return editor_set_text(e, memento_get_state(m));
}

备忘录

备忘录(Memento)是原发器状态快照的值对象(value object)。通常做法是将备忘录设为不可变的,并通过构造函数一次性传递数据。
模式建议将对象状态的副本存储在一个名为备忘录(Memento)的特殊对象中。除了创建备忘录的对象外,任何对象都不能访问备忘录的内容。其他对象必须使用受限接口与备忘录进行交互,它们可以获取快照的元数据(创建时间和操作名称等),但不能获取快照中原始对象的状态。

备忘录接口 memento.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef MEMENTO_H
#define MEMENTO_H

typedef struct Memento Memento;

/* 创建备忘录 */
Memento *memento_create(const char *text);

/* 销毁备忘录 */
void memento_destroy(Memento *m);

/* 内部使用接口(Originator 使用) */
const char *memento_get_state(const Memento *m);

#endif

备忘录实现 memento.c
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
#include "memento.h"
#include <stdlib.h>
#include <string.h>

struct Memento {
char *text;
};

Memento *memento_create(const char *text)
{
if (!text) return NULL;

Memento *m = calloc(1, sizeof(Memento));
if (!m) return NULL;

m->text = strdup(text);
if (!m->text) {
free(m);
return NULL;
}

return m;
}

void memento_destroy(Memento *m)
{
if (!m) return;

free(m->text);
free(m);
}

const char *memento_get_state(const Memento *m)
{
if (!m) return NULL;
return m->text;
}

负责人

负责人(Caretaker)仅知道 “何时” 和 “为何” 捕捉原发器的状态,以及何时恢复状态。负责人通过保存备忘录栈来记录原发器的历史状态。当原发器需要回溯历史状态时,负责人将从栈中获取最顶部的备忘录,并将其传递给原发器的恢复(restoration)方法。

这种限制策略允许你将备忘录保存在通常被称为负责人(Caretakers)的对象中。由于负责人仅通过受限接口与备忘录互动,故其无法修改存储在备忘录内部的状态。同时原发器拥有对备忘录所有成员的访问权限,从而能随时恢复其以前的状态。在文字编辑器的示例中,我们可以创建一个独立的历史(History)类作为负责人。编辑器每次执行操作前,存储在负责人中的备忘录栈都会生长。你甚至可以在应用的UI中渲染该栈,为用户显示之前的操作历史。当用户触发撤销操作时,历史类将从栈中取回最近的备忘录,并将其传递给编辑器以请求进行回滚。由于编辑器拥有对备忘录的完全访问权限,因此它可以使用从备忘录中获取的数值来替换自身的状态。

负责人接口 history.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef HISTORY_H
#define HISTORY_H

#include "memento.h"
#include <stdlib.h>

typedef struct History History;

History *history_create(size_t capacity);
void history_destroy(History *h);

/* 压入 */
int history_push(History *h, Memento *m);

/* 弹出 */
Memento *history_pop(History *h);

/* 是否为空 */
int history_is_empty(const History *h);

#endif

负责人实现 history.c
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
#include <stdlib.h>
#include "history.h"

struct History {
Memento **stack;
size_t capacity;
size_t top;
};

History *history_create(size_t capacity)
{
if (capacity == 0) return NULL;

History *h = calloc(1, sizeof(History));
if (!h) return NULL;

h->stack = calloc(capacity, sizeof(Memento *));
if (!h->stack) {
free(h);
return NULL;
}

h->capacity = capacity;
h->top = 0;

return h;
}

void history_destroy(History *h)
{
if (!h) return;

for (size_t i = 0; i < h->top; ++i) {
memento_destroy(h->stack[i]);
}

free(h->stack);
free(h);
}

int history_push(History *h, Memento *m)
{
if (!h || !m) return -1;

if (h->top >= h->capacity)
return -1;

h->stack[h->top++] = m;
return 0;
}

Memento *history_pop(History *h)
{
if (!h || h->top == 0)
return NULL;

return h->stack[--h->top];
}

int history_is_empty(const History *h)
{
return (!h || h->top == 0);
}

测试客户端代码 main.c

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
#include <stdio.h>
#include "editor.h"
#include "history.h"

int main(void)
{
Editor *editor = editor_create();
History *history = history_create(10);

editor_set_text(editor, "Version 1");
history_push(history, editor_save(editor));

editor_set_text(editor, "Version 2");
history_push(history, editor_save(editor));

editor_set_text(editor, "Version 3");

printf("Current: %s\n", editor_get_text(editor));

/* Undo */
if (!history_is_empty(history)) {
Memento *m = history_pop(history);
editor_restore(editor, m);
memento_destroy(m);
}

printf("After Undo: %s\n", editor_get_text(editor));

editor_destroy(editor);
history_destroy(history);

while (1);
return 0;
}

总结

   当需要创建对象状态快照来恢复其之前的状态时,可以使用备忘录模式。备忘录模式允许你复制对象中的全部状态(包括私有成员变量),并将其独立于对象进行保存。尽管大部分人因为 “撤销” 这个用例才记得该模式,但其实它在处理事务(比如需要在出现错误时回滚一个操作)的过程中也必不可少。