您现在的位置是:网站首页 > 备忘录模式(Memento)的状态恢复机制文章详情
备忘录模式(Memento)的状态恢复机制
陈川
【
JavaScript
】
36384人已围观
9749字
备忘录模式(Memento)的状态恢复机制
备忘录模式是一种行为设计模式,允许在不破坏封装性的前提下捕获并外部化对象的内部状态,以便稍后可以将对象恢复到该状态。这种模式特别适用于需要提供撤销操作或历史记录功能的场景。
核心概念与参与者
备忘录模式包含三个主要角色:
- Originator(发起人):需要保存状态的对象
- Memento(备忘录):存储Originator内部状态的对象
- Caretaker(管理者):负责保存和恢复备忘录的对象
在JavaScript中,备忘录模式通常通过对象组合来实现,而不是继承。这种模式的关键在于Originator可以生成备忘录对象,也可以使用备忘录对象恢复自身状态,同时不暴露其内部实现细节。
JavaScript实现示例
// Originator
class Editor {
constructor() {
this.content = '';
this.fontSize = 14;
this.color = '#000000';
}
// 创建备忘录
createMemento() {
return new EditorMemento({
content: this.content,
fontSize: this.fontSize,
color: this.color
});
}
// 从备忘录恢复
restoreFromMemento(memento) {
const state = memento.getState();
this.content = state.content;
this.fontSize = state.fontSize;
this.color = state.color;
}
// 其他业务方法
type(text) {
this.content += text;
}
setFontSize(size) {
this.fontSize = size;
}
setColor(color) {
this.color = color;
}
display() {
console.log(`Content: ${this.content}`);
console.log(`Font Size: ${this.fontSize}px`);
console.log(`Color: ${this.color}`);
}
}
// Memento
class EditorMemento {
constructor(state) {
this.state = state;
}
getState() {
return this.state;
}
}
// Caretaker
class History {
constructor() {
this.states = [];
}
push(memento) {
this.states.push(memento);
}
pop() {
return this.states.pop();
}
}
// 使用示例
const editor = new Editor();
const history = new History();
editor.type('Hello, ');
editor.setFontSize(16);
editor.setColor('#3366FF');
history.push(editor.createMemento()); // 保存状态1
editor.type('world!');
editor.setFontSize(20);
editor.setColor('#FF0000');
history.push(editor.createMemento()); // 保存状态2
editor.display(); // 显示当前状态
// 恢复到状态1
editor.restoreFromMemento(history.states[0]);
editor.display(); // 显示恢复后的状态
实现细节与注意事项
- 封装边界:备忘录对象应该只对Originator开放其内部状态,对其他对象保持私有。在JavaScript中,可以通过闭包或Symbol实现:
const EditorMemento = (function() {
const _state = Symbol('state');
return class {
constructor(state) {
this[_state] = state;
}
getState(originator) {
// 只有Editor实例可以访问状态
if (originator instanceof Editor) {
return this[_state];
}
throw new Error('Unauthorized access');
}
};
})();
- 增量备忘录:对于大型对象,可以只保存变化的部分而不是整个状态:
class Editor {
// ...其他代码
createIncrementalMemento(change) {
return new EditorMemento({
change,
timestamp: Date.now()
});
}
applyIncrementalMemento(memento) {
const { change } = memento.getState(this);
// 应用增量变化
if (change.content) this.content += change.content;
if (change.fontSize) this.fontSize = change.fontSize;
if (change.color) this.color = change.color;
}
}
- 性能考虑:频繁创建备忘录可能导致内存问题,可以:
- 限制历史记录数量
- 使用对象池复用备忘录实例
- 实现懒加载机制
实际应用场景
- 文本编辑器撤销/重做:
class TextEditor {
constructor() {
this.text = '';
this.caretPosition = 0;
this.history = [];
this.currentState = -1;
}
saveState() {
// 如果当前不是最新状态,截断历史
if (this.currentState < this.history.length - 1) {
this.history = this.history.slice(0, this.currentState + 1);
}
const memento = {
text: this.text,
caretPosition: this.caretPosition
};
this.history.push(memento);
this.currentState++;
}
undo() {
if (this.currentState <= 0) return;
this.currentState--;
const memento = this.history[this.currentState];
this.text = memento.text;
this.caretPosition = memento.caretPosition;
}
redo() {
if (this.currentState >= this.history.length - 1) return;
this.currentState++;
const memento = this.history[this.currentState];
this.text = memento.text;
this.caretPosition = memento.caretPosition;
}
}
- 表单状态保存与恢复:
class FormStateManager {
constructor(formId) {
this.form = document.getElementById(formId);
this.states = [];
this.setupListeners();
}
setupListeners() {
this.form.addEventListener('change', () => {
this.saveState();
});
}
saveState() {
const inputs = this.form.querySelectorAll('input, select, textarea');
const state = {};
inputs.forEach(input => {
state[input.name] = input.value;
});
this.states.push(state);
}
restoreState(index) {
if (index < 0 || index >= this.states.length) return;
const state = this.states[index];
Object.keys(state).forEach(name => {
const input = this.form.querySelector(`[name="${name}"]`);
if (input) input.value = state[name];
});
}
}
- 游戏状态保存:
class Game {
constructor() {
this.player = { health: 100, position: { x: 0, y: 0 }, inventory: [] };
this.enemies = [];
this.checkpoints = [];
}
createCheckpoint() {
this.checkpoints.push({
player: JSON.parse(JSON.stringify(this.player)),
enemies: JSON.parse(JSON.stringify(this.enemies))
});
}
restoreCheckpoint(index) {
if (index < 0 || index >= this.checkpoints.length) return;
const checkpoint = this.checkpoints[index];
this.player = JSON.parse(JSON.stringify(checkpoint.player));
this.enemies = JSON.parse(JSON.stringify(checkpoint.enemies));
}
// 深度克隆的替代方案
deepClone(obj) {
return new Promise(resolve => {
const channel = new MessageChannel();
channel.port2.onmessage = ev => resolve(ev.data);
channel.port1.postMessage(obj);
});
}
}
与其他模式的关系
- 与命令模式结合:实现可撤销的操作时,常将备忘录模式与命令模式结合使用:
class Command {
constructor(editor) {
this.editor = editor;
this.backup = null;
}
saveBackup() {
this.backup = this.editor.createMemento();
}
undo() {
if (this.backup) {
this.editor.restoreFromMemento(this.backup);
}
}
execute() {
throw new Error('execute() must be implemented');
}
}
class AddTextCommand extends Command {
constructor(editor, text) {
super(editor);
this.text = text;
}
execute() {
this.saveBackup();
this.editor.type(this.text);
}
}
-
与原型模式对比:备忘录存储特定时刻的状态,而原型模式用于克隆整个对象。备忘录通常更轻量,只关注需要保存的状态。
-
与状态模式区别:状态模式关注对象行为的改变,而备忘录模式关注对象状态的保存和恢复。
浏览器环境中的特殊考虑
在浏览器环境中实现备忘录模式时,需要考虑:
- DOM状态保存:保存和恢复DOM元素状态需要特殊处理:
class DOMStateMemento {
constructor(element) {
this.state = {
html: element.innerHTML,
classes: [...element.classList],
styles: getComputedStyle(element)
};
}
restore(element) {
element.innerHTML = this.state.html;
element.className = this.state.classes.join(' ');
// 恢复内联样式
Object.entries(this.state.styles).forEach(([key, value]) => {
if (typeof value === 'string' && key.startsWith('--')) {
element.style.setProperty(key, value);
}
});
}
}
- 性能优化:对于复杂的UI状态,可以使用差异算法减少内存使用:
class OptimizedDOMStateMemento {
constructor(before, after) {
this.diff = this.calculateDiff(before, after);
}
calculateDiff(before, after) {
// 实现DOM差异比较算法
// 返回变化的部分
}
applyDiff(element) {
// 应用差异到元素
}
}
- 与浏览器历史API集成:
class HistoryIntegration {
constructor() {
this.states = {};
window.addEventListener('popstate', (event) => {
if (event.state && this.states[event.state.id]) {
this.restoreState(this.states[event.state.id]);
}
});
}
pushState(state) {
const id = Date.now();
this.states[id] = state;
window.history.pushState({ id }, '');
}
restoreState(state) {
// 恢复应用状态
}
}
高级应用与变体
- 持久化备忘录:将备忘录保存到本地存储或服务器:
class PersistentMemento {
constructor(key) {
this.key = key;
}
save(state) {
localStorage.setItem(this.key, JSON.stringify(state));
}
load() {
const data = localStorage.getItem(this.key);
return data ? JSON.parse(data) : null;
}
}
// 使用
const editorMemento = new PersistentMemento('editor-state');
editorMemento.save(editor.createMemento());
// 页面刷新后
const savedState = editorMemento.load();
if (savedState) editor.restoreFromMemento(savedState);
- 时间旅行调试:实现完整的状态历史记录和回放:
class TimeTravelDebugger {
constructor() {
this.history = [];
this.currentIndex = -1;
this.recording = false;
}
startRecording() {
this.recording = true;
this.history = [];
this.currentIndex = -1;
}
recordState(state) {
if (!this.recording) return;
// 如果当前不是最新状态,截断历史
if (this.currentIndex < this.history.length - 1) {
this.history = this.history.slice(0, this.currentIndex + 1);
}
this.history.push(JSON.parse(JSON.stringify(state)));
this.currentIndex++;
}
goTo(index) {
if (index < 0 || index >= this.history.length) return;
this.currentIndex = index;
return this.history[index];
}
getTimeline() {
return this.history.map((state, index) => ({
index,
timestamp: state.timestamp,
label: state.label || `State ${index}`
}));
}
}
- 选择性状态恢复:只恢复对象的部分状态:
class SelectiveMemento {
constructor(state, options = {}) {
this.state = state;
this.options = options;
}
getState(originator, fields) {
if (!fields) return this.state;
const partialState = {};
fields.forEach(field => {
if (field in this.state) {
partialState[field] = this.state[field];
}
});
return partialState;
}
}
// 使用
const memento = new SelectiveMemento({
content: 'Hello',
fontSize: 16,
color: '#000'
});
// 只恢复字体大小
editor.restoreFromMemento(memento.getState(editor, ['fontSize']));