您现在的位置是:网站首页 > 备忘录模式(Memento)的状态恢复机制文章详情

备忘录模式(Memento)的状态恢复机制

备忘录模式(Memento)的状态恢复机制

备忘录模式是一种行为设计模式,允许在不破坏封装性的前提下捕获并外部化对象的内部状态,以便稍后可以将对象恢复到该状态。这种模式特别适用于需要提供撤销操作或历史记录功能的场景。

核心概念与参与者

备忘录模式包含三个主要角色:

  1. Originator(发起人):需要保存状态的对象
  2. Memento(备忘录):存储Originator内部状态的对象
  3. 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(); // 显示恢复后的状态

实现细节与注意事项

  1. 封装边界:备忘录对象应该只对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');
    }
  };
})();
  1. 增量备忘录:对于大型对象,可以只保存变化的部分而不是整个状态:
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;
  }
}
  1. 性能考虑:频繁创建备忘录可能导致内存问题,可以:
    • 限制历史记录数量
    • 使用对象池复用备忘录实例
    • 实现懒加载机制

实际应用场景

  1. 文本编辑器撤销/重做
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;
  }
}
  1. 表单状态保存与恢复
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];
    });
  }
}
  1. 游戏状态保存
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);
    });
  }
}

与其他模式的关系

  1. 与命令模式结合:实现可撤销的操作时,常将备忘录模式与命令模式结合使用:
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);
  }
}
  1. 与原型模式对比:备忘录存储特定时刻的状态,而原型模式用于克隆整个对象。备忘录通常更轻量,只关注需要保存的状态。

  2. 与状态模式区别:状态模式关注对象行为的改变,而备忘录模式关注对象状态的保存和恢复。

浏览器环境中的特殊考虑

在浏览器环境中实现备忘录模式时,需要考虑:

  1. 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);
      }
    });
  }
}
  1. 性能优化:对于复杂的UI状态,可以使用差异算法减少内存使用:
class OptimizedDOMStateMemento {
  constructor(before, after) {
    this.diff = this.calculateDiff(before, after);
  }

  calculateDiff(before, after) {
    // 实现DOM差异比较算法
    // 返回变化的部分
  }

  applyDiff(element) {
    // 应用差异到元素
  }
}
  1. 与浏览器历史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) {
    // 恢复应用状态
  }
}

高级应用与变体

  1. 持久化备忘录:将备忘录保存到本地存储或服务器:
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);
  1. 时间旅行调试:实现完整的状态历史记录和回放:
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}`
    }));
  }
}
  1. 选择性状态恢复:只恢复对象的部分状态:
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']));

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步