动机

在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。

  • 比如,对象级别的redo,undo操作

如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。

  • 维持封装性的前提下,怎么样实现对象的状态

如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的封装性。

代码

  1. //需要保存Originator状态的对象
  2. class Originator
  3. {
  4. //状态有很多
  5. string state; //这里只存放了一个string
  6. //.... //可能还有很多别的属性,也要保存它的状态
  7. public:
  8. Originator() {}
  9. //创建一个备忘录(快照)
  10. Memento createMomento() {
  11. Memento m(state);
  12. return m;
  13. }
  14. //设置一个备忘录
  15. void setMomento(const Memento & m) {
  16. state = m.getState();
  17. }
  18. };
  19. //我们希望能够将Originator某个时刻的状态,拍成一个快照(内存快照、状态快照),把它存起来
  20. //所以,我们设计了一个Memento类,里面存的状态与Originator相同
  21. class Memento
  22. {
  23. //Memento内部存了很多状态,这些状态是与Originator相同的
  24. string state;
  25. //..
  26. //1. 如果有些状态是不需要做快照的,那也就不用存起来
  27. //2. 也可以不需要一一对应,Memento可能存的只是Originator某个状态的内存流、字符串
  28. public:
  29. Memento(const string & s) : state(s) {}
  30. //提供一些方法给Originator访问
  31. string getState() const { return state; }
  32. void setState(const string & s) { state = s; }
  33. };
  34. int main()
  35. {
  36. Originator orginator;
  37. //捕获对象状态,存储到备忘录
  38. Memento mem = orginator.createMomento();
  39. //改变orginator状态
  40. //...
  41. //从备忘录中恢复
  42. orginator.setMomento(memento);
  43. }

到具体业务当中,如果你可能要保存50次内存快照,那就要考虑性能问题了

  • 比如,使用序列化技术,内存编码的技术,实现对快照高效的存储

模式定义

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。——《设计模式》GoF

  1. 不破坏Originator的封装性
  2. 捕获对象内部状态:orginator.createMomento
  3. 在该对象之外保存这个状态:Memento mem
  4. 恢复状态:orginator.setMomento(memento)

结构

image.png

  • Carewaker使用备忘录的人,比如例子中的main函数

要点总结

备忘录(Memento)存储原发器(Originator)对象的内部状态,在需要时恢复原发器的状态。

Memento模式的核心是信息隐藏,即Originator需要向外界隐藏信息,保持其封装性。但同时又需要将状态保持到外界(Memenoto)。

由于现代语言运行时(如C#、Java等)都具有相当的对象序列化支持,因此往往采用效率较高、又较容器正确实现的序列化方案来实现Memento模式。

  • 有了序列化技术的支持,目前已经不需要使用面向对象的方式来实现了。不需要再定义一个Memento类,来对Originator类做一一映射了,这样是很低效的
  • 但思想是什么。就是要对外界做信息隐藏,不能将Originator的信息暴露给了外界,从而保持Originator的封装性。在这个基础上,再要保存Originator的快照

总结

GoF设计模式是94年问世的,有一些设计模式当下已经不流形了。有一些设计模式在当时的内存环境、语言条件,是很适用的。但在当前的环境下,可能在实践上就显得非常非常的容易,而且也变得不再适用了。

如果这个Originator是一个很复杂的对象,里面套了好几层的指针,那备忘录方式就很鸡肋了。当下可以使用序列化的方式来做。