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