动机:在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的封装性。
意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之前保存这个状态,这样以后就可以将该对象恢复到原先保存的状态。
基本Code:
//长方形
public class Rectangle:ICloneable
{
int x;
int y;
int weight;
int height;
public Rectangle(int x, int y, int weight, int height)
{
this.x = x;
this.y = y;
this.weight = weight;
this.height = height;
}
public void SetValue(Rectangle r)
{
this.x = r.x;
this.y = r.y;
this.weight = r.weight;
this.height = r.height;
}
public object Clone()
{
return this.MemberwiseClone();
}
public void MoveTo(Point p)
{
}
public void ChangeWidth(int width)
{
this.weight = width;
}
public void ChangeHeight(int height)
{
this.height = height;
}
public void Draw(Graphics graphic)
{
}
}
public class GraphicSystem
{
//原发器对象,有必要对自身内部状态(实例字段)进行保存,然后在某个点处又需要恢复内部状态的对象
Rectangle r = new Rectangle(0, 0, 10, 10);
//备忘录对象,保存原发器对象内部状态,但是不提供原发器对象提供的操作,如Clone()等
Rectangle rSaved = new Rectangle(0, 0, 10, 10);
public static void Main()
{
Process(r);
}
public void Process(Rectangle r)
{
rSaved = r.Clone();
//...
}
public void Saved_Click(object sender, EventArgs e)
{
r.SetValue(rSaved);
}
}
在上述代码中,rSaved是保存对象状态,而不能对其进行修改,但由于rSaved存在SetValue接口,说明我们可以修改,故不能满足单一职责原则,所以在使用克隆的时候,提供了太多的接口。我们使用Memento模式改写代码如下:
//长方形
public class Rectangle:ICloneable
{
int x;
int y;
int weight;
int height;
public Rectangle(int x, int y, int weight, int height)
{
this.x = x;
this.y = y;
this.weight = weight;
this.height = height;
}
public void MoveTo(Point p)
{
}
public void ChangeWidth(int width)
{
this.weight = width;
}
public void ChangeHeight(int height)
{
this.height = height;
}
public void Draw(Graphics graphic)
{
}
//存储备忘录对象
public RectangleMemoento CreateMemoento()
{
RectangleMemoento rm = new RectangleMemoento();
rm.SetState(this.x, this.y, this.weight, this.height);
return rm;
}
//恢复备忘录对象
public void SetMemoento(RectangleMemoento rm)
{
this.x = rm.x;
this.y = rm.y;
this.weight = rm.weight;
this.height = rm.height;
}
}
//长方形备忘录对象
public class RectangleMemoento
{
internal int x;
internal int y;
internal int weight;
internal int height;
internal void SetState(int x, int y, int weight, int height)
{
this.x = x;
this.y = y;
this.weight = weight;
this.height = height;
}
}
//在另外一个程序集中
public class GraphicSystem
{
//原发器对象,有必要对自身状态进行保存,然后在某个点处又需要恢复的对象
Rectangle r = new Rectangle(0, 0, 10, 10);
//备忘录对象,保存原发器对象状态,但是不提供原发器对象提供的操作,如SetValue等
RectangleMemoento rSaved;
public void Process(Rectangle r)
{
rSaved = r.CreateMemoento();
//...
}
public void Saved_Click(object sender, EventArgs e)
{
r.SetMemoento(rSaved);
}
}
Memoento模式结构图如下:
Memento模式要点:
1、 备忘录(Memento)存储原发器(Originator)对象的内部状态,在需要时恢复原发器状态。Memento模式适用于“由原发器管理,却又必须存储在原发器之外的信息”。
2、 在实现Memento模式中,要防止原发器以外的对象访问备忘录对象。备忘录对象有两个接口,一个为原发器使用的宽接口;一个为其他对象使用的窄接口。
3、 在实现Memento模式时,要考虑拷贝对象状态的效率问题,如果对象开销比较大,可以采用某种增量式改变来改进Memento模式。
使用序列化的方式的Memento模式Code如下:
//长方形
[Serializable]
public class Rectangle : ICloneable
{
int x;
int y;
int weight;
int height;
public Rectangle(int x, int y, int weight, int height)
{
this.x = x;
this.y = y;
this.weight = weight;
this.height = height;
}
public void MoveTo(Point p)
{
}
public void ChangeWidth(int width)
{
this.weight = width;
}
public void ChangeHeight(int height)
{
this.height = height;
}
public void Draw(Graphics graphic)
{
}
//存储备忘录对象
public GeneralMemoento CreateGeneralMemoentor()
{
GeneralMemoentor gm = new GeneralMemoentor();
gm.SetState(this);
return gm;
}
//恢复备忘录对象
public void SetMemoento(GeneralMemoento gm)
{
Rectangle r = (Rectangle)gm.GetState();
this.x = r.x;
this.y = r.y;
this.weight = r.weight;
this.height = r.height;
}
}
//采用抽象工厂可以支持多种流
public abstract class Factory
{
public Stream CreateStream();
}
public class StreamFactory:Factory
{
public override Stream CreateStream()
{
return new MemoryStream();
}
}
//采用序列化做的通用Memoento备忘录对象
public class GeneralMemoento
{
Stream rSaved;
public GeneralMemoento(Factory factory)
{
rSaved = factory.CreateStream();
}
internal void SetState(object obj)
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(rSaved, obj);
}
internal object GetState()
{
BinaryFormatter bf = new BinaryFormatter();
rSaved.Seek(0, SeekOrigin.Begin);
object obj = bf.Deserialize(rSaved);
return obj;
}
}