C#基礎(chǔ)Memento備忘錄模式(行為型模式)

字號:

在軟件構(gòu)建過程中,某些對象的狀態(tài)在轉(zhuǎn)換過程中,可能由于某種需要,要求程序能夠回溯到對象之前處于某個點時的狀態(tài).如果使用一些共有接口來讓其他對象得到對象的狀態(tài),便會暴露對象的細(xì)節(jié)實現(xiàn)。我們需要實現(xiàn)對象狀態(tài)的良好保存與恢復(fù),但同時不會因此而破壞對象本身的封裝性。
    Examda提示:在不破壞封裝性的前提下,捕獲一個對象的內(nèi)部狀態(tài),并在該對象之外保存這個狀態(tài)。這樣以后就可以將該對象恢復(fù)到原先保存的狀態(tài)。
    我們首先看看不適用設(shè)計模式來解決對象狀態(tài)恢復(fù)的情況。
    public class Rectangle : ICloneable
    {
    int x;
    int y;
    int width;
    int height;
    public void SetValue(Rectangle r)
    {
    this.x = r.x;
    this.y = r.y;
    this.width = r.width;
    this.height = r.height;
    }
    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(Graphics graphic)
    {
    }
    #region ICloneable 成員
    public object Clone()
    {
    return this.MemberwiseClone();
    }
    #endregion
    }
    public class GraphicsSystem
    {
    //原發(fā)器對象:
    //有必要對自身內(nèi)部狀態(tài)進(jìn)行保存,然后在某個點處又需要恢復(fù)內(nèi)部狀態(tài)的對象
    Rectangle r = new Rectangle(0, 0, 10, 10);
    //備忘錄對象:
    //保存原發(fā)器對象的內(nèi)部狀態(tài),但不提供原發(fā)器對象支持的操作
    Rectangle rSaved = new Rectangle(0, 0, 10, 10);
    public void Process()
    {
    rSaved = r.Clone();
    //....
    }
    public void Saved_Click(object sender, EventArgs e)
    {
    r.SetValue(rSaved);
    //....
    }
    }
    class Program
    {
    static void Main(string[] args)
    {
    Rectangle r = new Rectangle(0, 0, 10, 10);
    GraphicsSystem g = new GraphicsSystem();
    g.Process(r);
    }
    }
    上面的代碼中Rectangle類實現(xiàn)了ICloneable接口,這個接口利用淺拷貝返回一個新的Rectangle對象
    在GraphicsSystem類中,我們定義了一個原發(fā)器對象r,和備忘錄對象rSaved,在Process的時候,我們將原發(fā)器對象進(jìn)行克隆保存在rSaved引用中。在Saved_Click方法中,將備忘錄對象rSaved保存的值還原給原發(fā)器對象r。但這樣來做,備忘錄對象提過了原發(fā)器對象的一些操作,那么我們現(xiàn)在需要將備忘錄對象抽象出來。
    public class Rectangle
    {
    int x;
    int y;
    int width;
    int height;
    public void SetValue(Rectangle r)
    {
    this.x = r.x;
    this.y = r.y;
    this.width = r.width;
    this.height = r.height;
    }
    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(Graphics graphic)
    {
    }
    internal RectangleMemento Creatememento()
    {
    RectangleMemento memento = new RectangleMemento();
    memento.SetState(this.x, this.y, this.width, this.height);
    return memento;
    }
    internal void SetMemento(RectangleMemento memento)
    {
    this.x = memento.x;
    this.y = memento.y;
    this.width = memento.width;
    this.height = memento.height;
    }
    }
    internal 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;
    }
    }
    public class GraphicsSystem
    {
    //原發(fā)器對象:
    //有必要對自身內(nèi)部狀態(tài)進(jìn)行保存,然后在某個點處又需要恢復(fù)內(nèi)部狀態(tài)的對象
    Rectangle r = new Rectangle(0, 0, 10, 10);
    //備忘錄對象:
    //保存原發(fā)器對象的內(nèi)部狀態(tài),但不提供原發(fā)器對象支持的操作
    RectangleMemento rSaved = new RectangleMemento();
    public void Process(Rectangle r)
    {
    rSaved = r.Creatememento();
    //....
    }
    public void Saved_Click(object sender, EventArgs e)
    {
    r.SetMemento(rSaved);
    //....
    }
    }
    在上面這段代碼中,我們將備忘錄對象抽象出來為RectangleMemento類,這個類只保存了原發(fā)器對象基本的值,但沒有提供其他的操作,并且,我們將RectangleMemento類和內(nèi)部成員全部申明為internal只能讓程序集本身調(diào)用,保證了RectangleMemento對象的封裝性。
    實現(xiàn)要點:
    備忘錄存儲原發(fā)器(Originator)對象的內(nèi)部狀態(tài),在需要時恢復(fù)原發(fā)器狀態(tài)。Memento模式適用于由原發(fā)器管理,卻又必須存儲在原發(fā)器之外的信息
    在實現(xiàn)Memento模式中,要防止原發(fā)器以外的對象方位備忘錄對象,備忘錄對象有兩個接口,一個為原發(fā)器使用的寬接口,一個為其他對象使用的窄接口
    在實現(xiàn)Memento模式時,要考慮拷貝對象狀態(tài)的效率問題,如果對象開銷比較大,可以采用某種增量式改變來跟進(jìn)Memnto模式
    在上面的例子中Rectangle對于RectangleMemento看到是寬接口,即SetState方法,而GraphicsSystem看到的是窄接口,即RectangleMemento的構(gòu)造函數(shù)和Creatememento、SetMemento方法?!‘?dāng)一個對象比較大的時候,在.net中如DataSet,要保存對象的狀態(tài)可能會造成效率問題,占用比較大的內(nèi)存。
    下面一段代碼演示了通過使用序列化的方式來實現(xiàn)Memento模式
    [Serializable]
    public class Rectangle
    {
    int x;
    int y;
    int width;
    int height;
    public void SetValue(Rectangle r)
    {
    this.x = r.x;
    this.y = r.y;
    this.width = r.width;
    this.height = r.height;
    }
    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(Graphics graphic)
    {
    }
    public GeneralMementor CreateMemento()
    {
    GeneralMementor memento = new GeneralMementor();
    memento.SetState(this);
    return memento;
    }
    public void SetMemento(GeneralMementor memento)
    {
    Rectangle r = memento.GetState() as Rectangle;
    SetValue(r);
    }
    }
    public class GeneralMementor
    {
    MemoryStream rSaved = new MemoryStream();
    internal void SetState(object obj)
    {
    BinaryFormatter bf = new BinaryFormatter();
    bf.Serialize(rSaved, obj);
    }
    internal object GetState()
    {
    BinaryFormatter bf = new BinaryFormatter();
    rSaved.Seek(0, SeekOrigin.End);
    object obj = bf.Deserialize(rSaved);
    return obj;
    }
    }
    public class GraphicsSystem
    {
    //原發(fā)器對象:
    //有必要對自身內(nèi)部狀態(tài)進(jìn)行保存,然后在某個點處又需要恢復(fù)內(nèi)部狀態(tài)的對象
    Rectangle r = new Rectangle(0, 0, 10, 10);
    GeneralMementor memntor = null;
    //備忘錄對象:
    //保存原發(fā)器對象的內(nèi)部狀態(tài),但不提供原發(fā)器對象支持的操作
    public void Process(Rectangle r)
    {
    memntor = r.CreateMemento();
    //....
    }
    public void Saved_Click(object sender, EventArgs e)
    {
    r.SetMemento(memntor);
    //....
    }
    }
    上面的代碼中我們可以看到將Rectangle原發(fā)器保存在內(nèi)存流里,在恢復(fù)的時候,將內(nèi)存流里的對象進(jìn)行還原,我們可以更進(jìn)一步的抽象,可以將對象保存在任何的流里,這里就不做演示了。