C#面向对象设计模式纵横谈 学习笔记21 Memento备忘录模式(行为型模式)

动机:

在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态.如果使用一些共有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。我们需要实现对象状态的良好保存与恢复,但同时不会因此而破坏对象本身的封装性。

意图:

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

我们首先看看不适用设计模式来解决对象状态恢复的情况。

  1.  public class Rectangle : ICloneable
  2.     {
  3.         int x;
  4.         int y;
  5.         int width;
  6.         int height;
  7.         
  8.         public void SetValue(Rectangle r)
  9.         {
  10.             this.x = r.x;
  11.             this.y = r.y;
  12.             this.width = r.width;
  13.             this.height = r.height;
  14.         }
  15.         
  16.         public Rectangle(int x, int y, int width, int height)
  17.         {
  18.             this.x = x;
  19.             this.y = y;
  20.             this.width = width;
  21.             this.height = height;
  22.         }
  23.         
  24.         public void MoveTo(Point p)
  25.         {
  26.             //....
  27.         }
  28.         
  29.         public void ChangeWidth(int width)
  30.         {
  31.             
  32.         }
  33.         
  34.         public void ChangeHeight(int height)
  35.         {
  36.             
  37.         }
  38.         
  39.         public void Draw(Graphics graphic)
  40.         {
  41.             
  42.         }
  43.         #region ICloneable 成员
  44.         public object Clone()
  45.         {
  46.             return this.MemberwiseClone();
  47.         }
  48.         #endregion
  49.     }
  50.     
  51.     public class GraphicsSystem
  52.     {
  53.         //原发器对象:
  54.         //有必要对自身内部状态进行保存,然后在某个点处又需要恢复内部状态的对象
  55.         Rectangle r = new Rectangle(0, 0, 10, 10);
  56.           
  57.         //备忘录对象:
  58.         //保存原发器对象的内部状态,但不提供原发器对象支持的操作
  59.         Rectangle rSaved = new Rectangle(0, 0, 10, 10);
  60.          
  61.         public void Process()
  62.         {
  63.             rSaved = r.Clone();
  64.             //....
  65.         }
  66.         
  67.         public void Saved_Click(object sender, EventArgs e)
  68.         {
  69.             r.SetValue(rSaved);
  70.             //....
  71.         }
  72.     }
  73.     
  74.     class Program
  75.     {
  76.         static void Main(string[] args)
  77.         {
  78.             Rectangle r = new Rectangle(0, 0, 10, 10);
  79.             
  80.             GraphicsSystem g = new GraphicsSystem();
  81.             g.Process(r);
  82.         }
  83.     }

 

上面的代码中Rectangle类实现了ICloneable接口,这个接口利用浅拷贝返回一个新的Rectangle对象。

在GraphicsSystem类中,我们定义了一个原发器对象r,和备忘录对象rSaved,在Process的时候,我们将原发器对象进行克隆保存在rSaved引用中。在Saved_Click方法中,将备忘录对象rSaved保存的值还原给原发器对象r。但这样来做,备忘录对象提过了原发器对象的一些操作,那么我们现在需要将备忘录对象抽象出来。

  1. public class Rectangle 
  2.     {
  3.         int x;
  4.         int y;
  5.         int width;
  6.         int height;
  7.         
  8.         public void SetValue(Rectangle r)
  9.         {
  10.             this.x = r.x;
  11.             this.y = r.y;
  12.             this.width = r.width;
  13.             this.height = r.height;
  14.         }
  15.         
  16.         public Rectangle(int x, int y, int width, int height)
  17.         {
  18.             this.x = x;
  19.             this.y = y;
  20.             this.width = width;
  21.             this.height = height;
  22.         }
  23.         
  24.         public void MoveTo(Point p)
  25.         {
  26.             //....
  27.         }
  28.         
  29.         public void ChangeWidth(int width)
  30.         {
  31.             
  32.         }
  33.         
  34.         public void ChangeHeight(int height)
  35.         {
  36.             
  37.         }
  38.         
  39.         public void Draw(Graphics graphic)
  40.         {
  41.             
  42.         }
  43.         
  44.         internal RectangleMemento Creatememento()
  45.         {
  46.             RectangleMemento memento = new RectangleMemento();
  47.             memento.SetState(this.x, this.y, this.width, this.height);
  48.             
  49.             return memento;
  50.         }
  51.         
  52.         internal void SetMemento(RectangleMemento memento)
  53.         {
  54.             this.x = memento.x;
  55.             this.y = memento.y;
  56.             this.width = memento.width;
  57.             this.height = memento.height;
  58.         }
  59.     }
  60.     
  61.     internal class RectangleMemento
  62.     {
  63.         internal int x;
  64.         internal int y;
  65.         internal int width;
  66.         internal int height;
  67.         
  68.         internal void SetState(int x, int y, int width, int height)
  69.         {
  70.             this.x = x;
  71.             this.y = y;
  72.             this.width = width;
  73.             this.height = height;
  74.         }
  75.     }
  76.     
  77.     public class GraphicsSystem
  78.     {
  79.         //原发器对象:
  80.         //有必要对自身内部状态进行保存,然后在某个点处又需要恢复内部状态的对象
  81.         Rectangle r = new Rectangle(0, 0, 10, 10);
  82.           
  83.         //备忘录对象:
  84.         //保存原发器对象的内部状态,但不提供原发器对象支持的操作
  85.         RectangleMemento rSaved = new RectangleMemento();
  86.          
  87.         public void Process(Rectangle r)
  88.         {
  89.             rSaved = r.Creatememento();
  90.             //....
  91.         }
  92.         
  93.         public void Saved_Click(object sender, EventArgs e)
  94.         {
  95.             r.SetMemento(rSaved);
  96.             //....
  97.         }
  98.     }

 

在上面这段代码中,我们将备忘录对象抽象出来为RectangleMemento类,这个类只保存了原发器对象基本的值,但没有提供其他的操作,并且,我们将RectangleMemento类和内部成员全部申明为internal只能让程序集本身调用,保证了RectangleMemento对象的封装性。

实现要点:

备忘录存储原发器(Originator)对象的内部状态,在需要时恢复原发器状态。Memento模式适用于由原发器管理,却又必须存储在原发器之外的信息
在实现Memento模式中,要防止原发器以外的对象方位备忘录对象,备忘录对象有两个接口,一个为原发器使用的宽接口,一个为其他对象使用的窄接口
在实现Memento模式时,要考虑拷贝对象状态的效率问题,如果对象开销比较大,可以采用某种增量式改变来跟进Memnto模式

在上面的例子中Rectangle对于RectangleMemento看到是宽接口,即SetState方法,而GraphicsSystem看到的是窄接口,即RectangleMemento的构造函数和Creatememento、SetMemento方法。

当一个对象比较大的时候,在.net中如DataSet,要保存对象的状态可能会造成效率问题,占用比较大的内存。

下面一段代码演示了通过使用序列化的方式来实现Memento模式

  1. [Serializable]
  2.     public class Rectangle 
  3.     {
  4.         int x;
  5.         int y;
  6.         int width;
  7.         int height;
  8.         
  9.         public void SetValue(Rectangle r)
  10.         {
  11.             this.x = r.x;
  12.             this.y = r.y;
  13.             this.width = r.width;
  14.             this.height = r.height;
  15.         }
  16.         
  17.         public Rectangle(int x, int y, int width, int height)
  18.         {
  19.             this.x = x;
  20.             this.y = y;
  21.             this.width = width;
  22.             this.height = height;
  23.         }
  24.         
  25.         public void MoveTo(Point p)
  26.         {
  27.             //....
  28.         }
  29.         
  30.         public void ChangeWidth(int width)
  31.         {
  32.             
  33.         }
  34.         
  35.         public void ChangeHeight(int height)
  36.         {
  37.             
  38.         }
  39.         
  40.         public void Draw(Graphics graphic)
  41.         {
  42.             
  43.         }
  44.         
  45.         public GeneralMementor CreateMemento()
  46.         {
  47.             GeneralMementor memento = new GeneralMementor();
  48.             memento.SetState(this);
  49.             return memento;
  50.         }
  51.         
  52.         public void SetMemento(GeneralMementor memento)
  53.         {
  54.             Rectangle r = memento.GetState() as Rectangle;
  55.             
  56.             SetValue(r);
  57.         }
  58.     }
  59.     
  60.     public class GeneralMementor
  61.     {
  62.         MemoryStream rSaved = new MemoryStream();
  63.         
  64.         internal void SetState(object obj)
  65.         {
  66.             BinaryFormatter bf = new BinaryFormatter();
  67.             bf.Serialize(rSaved, obj);
  68.         }
  69.         
  70.         internal object GetState()
  71.         {
  72.             BinaryFormatter bf = new BinaryFormatter();
  73.             rSaved.Seek(0, SeekOrigin.End);
  74.             object obj = bf.Deserialize(rSaved);
  75.             
  76.             return obj;
  77.         }
  78.     }
  79.     
  80.     public class GraphicsSystem
  81.     {
  82.         //原发器对象:
  83.         //有必要对自身内部状态进行保存,然后在某个点处又需要恢复内部状态的对象
  84.         Rectangle r = new Rectangle(0, 0, 10, 10);
  85.         GeneralMementor memntor = null;
  86.         //备忘录对象:
  87.         //保存原发器对象的内部状态,但不提供原发器对象支持的操作
  88.          
  89.         public void Process(Rectangle r)
  90.         {
  91.             memntor = r.CreateMemento();
  92.              
  93.             //....
  94.         }
  95.         
  96.         public void Saved_Click(object sender, EventArgs e)
  97.         {
  98.             r.SetMemento(memntor);
  99.             //....
  100.         }
  101.     }
  102.     

 

上面的代码中我们可以看到将Rectangle原发器保存在内存流里,在恢复的时候,将内存流里的对象进行还原,我们可以更进一步的抽象,可以将对象保存在任何的流里,这里就不做演示了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值