第21讲:Memento 备忘录模式

2006.8.29 李建忠

对象状态的回溯

对象状态的变化无端,如何回溯/恢复对象在某个点的状态

image

如果我们想恢复对象的状态,那么我们可能首先想到的是把对象保存下来,但是这样会破坏对象的封装性。因为对象有状态有操作,如果我们为了保存对象而留着原来的对象,做一个深拷贝,那么其他对象也能通过这个对象的接口访问这个对象状态,这并不是我们所希望的。而我们需要它的职责只是保存和恢复对象状态,而不应在上面支持对对象状态访问的接口,这就产生了Memento模式。

 

动机(Motivation)

在软件构建过程中,某些对象的状态在转换的过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。

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

 

意图(Intent)

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态(如果没有这个关键点,其实深拷贝就可以解决问题)。这样以后就可以将该对象恢复到原先保存的状态。

——《设计模式》GoF

 

例说Memento应用

image

image

image

备忘录模式适用于对象状态有频繁的改变和恢复的需求。

image

image

这种设计方式可以解决一部分问题,但是它用的一个rSaved对象除了拥有Rectangle对象的状态之外,还拥有Rectangle对象的操作。从对象的责任来讲,拿着rSaved字段就可以更改rSaved,但是rSave对象的作用只是用来保存对象的状态,我们不应该允许更改rSaved的内容。即使我们保证自己不改rSaved的内容,但是纯OO的理念要求,只要对象上提供了操作,那么它的含义就表示做这样的操作是没有问题的,这也是单一职责原则。

改进后的代码

image

image

Rectangle类不需要Clone操作,我们新增一个RectangleMomento类。

image

我们在外部程序中,拿到RectangleMomento类型的rSaved实例,什么都不能做,因为它都是internal的方法。所以这种方式使得我们既内部保存了状态,外部又不提供操作。

image

image

其实这个和克隆很相似,只不过是克隆出来的对象可以拥有对象的接口,而Memento没有。Memento只封装状态,而不再提供其它操作。

 

结构(Structure)

image

state是需要存储的状态字段,CreateMemento负责存储状态,SetMemento负责恢复状态。

 

Memento模式的几个要点

备忘录(Memento)存储原发器(Originator)对象的内部状态,在需要时恢复原发器状态。Memento模式适用于“由原发器管理,却又必须存储在原发器之外的信息”。

在实现Memento模式中,要防止原发器以外的对象访问备忘录对象。备忘录对象有两个接口,一个为原发器使用的宽接口;一个为其他对象使用的窄接口。

在实现Memento模式时,要考虑拷贝对象状态的效率问题,如果对象开销比较大,可以采用某种增量式改变(即只记住改变的状态)来改进Memento模式。

 

我们也可以用序列化的方式实现备忘录。序列化之后,我们可以把它临时性保存到数据库、文件、进程内、进程外等地方。ASP.Net的Session其实就有这种影子。
image

image

image

类需要加上可序列化标记。MemoryStream是一个内存流,可以对对象进行序列化和反序列化,内存流是保存到当前进程里面。操作的时候,对对象做序列化;保存的时候,对对象做反序列化。内存流的好处是操作方便,避免了我们写很多字段来保存。

 

Memento模式与Command模式的异同

虽然两者都支持Undo操作,但是Command是对行为的封装,Memento是对对象状态的保留,这是目的上的不同。它们支持的也是Undo操作的不同层面,Command是对行为序列的操作,Memento是对行为状态的操作

2010.10.23

posted @ 2010-10-23 22:53  山天大畜  阅读(899)  评论(0编辑  收藏  举报