背景:对象状态的回溯
对象状态的变化无端,如何回溯/恢复对象在某个点的状态?
动机(Motivation)
在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接口来让其它对象得到对象的状态,便会暴露对象的细节实现。
如何实现对象状态的良好保存与恢复同时又不破坏对象本身的封装性?
意图(Intent)
在不破坏封装性的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态。这样以后就可以将该对象恢复到原来保存的状态。
——《设计模式》GoF
示例代码
public class Rectangle:ICloneable
{
int x;
int y;
int width;
int height;
public object Clone()
{
//由于成员全是值类型,用以下方法就可以达到深拷贝了
return this.MemberwiseClone();
}
public Rectangle(int x,int y,int width,int height)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public void MoveTo(Point p)
{
//...
}
public void ChangeWidth(int width)
{
//...
}
public void ChangeHeight(int height)
{
//...
}
public void Draw(Graphic graphic)
{
//...
}
public RectangleMemento CreateMemento()
{
RectangleMemento rm = new RectangleMemento();
rm.SetState(this.x,this.y,this.width,this.height);
return rm;
}
public void SetMemento(RectangleMemento rm)
{
this.x = rm.x;
this.y = rm.y;
this.width = rm.width;
this.height = rm.height;
}
}
public class RectangleMemento
{
internal int x;
internal int y;
internal int width;
internal int height;
internal void SetState(int x,int y,int width,int height)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
}
//GraphicsSystem应当处于另一个程序集中
public class GraphicsSystem
{
//原发器对象——有必要对自身状态进行保存然后在某个点恢复的对象
Rectangle r = new Rectangle(0,0,10,10);
//备忘录对象——保存原发器对象状态但不支持该对象支持的操作
RectangleMemento rSaved= new RectangleMemento(0,0,10,10);
public static void Main()
{
Process(r);
}
public static void Process()
{
rSaved = r.CreateMemento();
//...
}
//恢复状态
public static void SavedClick(object sender,EventArgs e)
{
r.SetMemento(rSaved);
}
}
Memento模式的几个要点
1)备忘录(Memento)存储原发器(Originator)对象的内部状态,在需要时恢复原发器状态。Memento模式适用于“由原发器管理,但又必须存储在原发器之外的信息”。
2)在实现Memento模式中,要防止原发器以外的对象访问备忘录对象。备忘录对象有两个接口,一个为原发器使用的宽接口,一个为其它对象使用的窄接口。
3)在实现Memento模式时,要考虑拷贝对象状态的效率问题,如果对象开销比较大,可以采用某种增量式改变来改进Memento模式。
4)在.NET中也可以使用序列化的形式来实现通用的备忘录。如下代码所示,要求原生器对象可以序列化,该示例也可以做成泛型的。
class General Mementor
{
MemorStream oSaved = new MemoryStream() ;
internal void SetState(object obj)
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(obj,oSaved);
}
internal object GetState()
{
BinaryFormatter bf = new BinaryFormatter();
oSaved.Seek(Seek.Original,0);
object o = (Object)bf.Deserialize(oSaved);
return o;
}
}