动机
在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。
- 比如,对象级别的redo,undo操作
如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。
- 维持封装性的前提下,怎么样实现对象的状态
如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的封装性。
代码
//需要保存Originator状态的对象
class Originator
{
//状态有很多
string state; //这里只存放了一个string
//.... //可能还有很多别的属性,也要保存它的状态
public:
Originator() {}
//创建一个备忘录(快照)
Memento createMomento() {
Memento m(state);
return m;
}
//设置一个备忘录
void setMomento(const Memento & m) {
state = m.getState();
}
};
//我们希望能够将Originator某个时刻的状态,拍成一个快照(内存快照、状态快照),把它存起来
//所以,我们设计了一个Memento类,里面存的状态与Originator相同
class Memento
{
//Memento内部存了很多状态,这些状态是与Originator相同的
string state;
//..
//1. 如果有些状态是不需要做快照的,那也就不用存起来
//2. 也可以不需要一一对应,Memento可能存的只是Originator某个状态的内存流、字符串
public:
Memento(const string & s) : state(s) {}
//提供一些方法给Originator访问
string getState() const { return state; }
void setState(const string & s) { state = s; }
};
int main()
{
Originator orginator;
//捕获对象状态,存储到备忘录
Memento mem = orginator.createMomento();
//改变orginator状态
//...
//从备忘录中恢复
orginator.setMomento(memento);
}
到具体业务当中,如果你可能要保存50次内存快照,那就要考虑性能问题了
- 比如,使用序列化技术,内存编码的技术,实现对快照高效的存储
模式定义
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。——《设计模式》GoF
- 不破坏
Originator
的封装性 - 捕获对象内部状态:
orginator.createMomento
- 在该对象之外保存这个状态:
Memento mem
- 恢复状态:
orginator.setMomento(memento)
结构
Carewaker
使用备忘录的人,比如例子中的main
函数
要点总结
备忘录(Memento)存储原发器(Originator)对象的内部状态,在需要时恢复原发器的状态。
Memento模式的核心是信息隐藏,即Originator需要向外界隐藏信息,保持其封装性。但同时又需要将状态保持到外界(Memenoto)。
由于现代语言运行时(如C#、Java等)都具有相当的对象序列化支持,因此往往采用效率较高、又较容器正确实现的序列化方案来实现Memento模式。
- 有了序列化技术的支持,目前已经不需要使用面向对象的方式来实现了。不需要再定义一个
Memento
类,来对Originator
类做一一映射了,这样是很低效的 - 但思想是什么。就是要对外界做信息隐藏,不能将
Originator
的信息暴露给了外界,从而保持Originator
的封装性。在这个基础上,再要保存Originator
的快照
总结
GoF设计模式是94年问世的,有一些设计模式当下已经不流形了。有一些设计模式在当时的内存环境、语言条件,是很适用的。但在当前的环境下,可能在实践上就显得非常非常的容易,而且也变得不再适用了。
如果这个Originator
是一个很复杂的对象,里面套了好几层的指针,那备忘录方式就很鸡肋了。当下可以使用序列化的方式来做。