設計模式(19) 備忘錄模式

備忘錄模式可以在不破壞封裝的前提下,將一個對象的狀態捕捉(Capture)住,並在外部存儲,從而可以在需要的時候把這個對象還原到存儲起來的狀態。備忘錄模式常常與命令模式和迭代器模式一同使用。

GOF對備忘錄模式的描述爲:
Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.
— Design Patterns : Elements of Reusable Object-Oriented Software

備忘錄模式的UML類圖:

備忘錄模式所涉及的角色有三個:
備忘錄(Memento)
備忘錄角色用來存儲發起人(Originator)內部狀態的快照,而且可以保護這些內容不被髮起人對象之外的任何對象所讀取。

發起人(Originator)
發起人負責創建一個含有當前的內部狀態的備忘錄對象,並保存到備忘錄中。

負責人(Caretaker)
負責人用來維護髮起人保存的一個或多個備忘錄,並在需要回滾狀態的時候提供保存了相應狀態的備忘錄。

寬接口與窄接口

窄接口:負責人(Caretaker)對象(和其他除發起人對象之外的任何對象)看到的是備忘錄的窄接口(narrow interface),這個窄接口只允許它把備忘錄對象傳給其他的對象。
寬接口:與負責人對象看到的窄接口相反的是,發起人對象可以看到一個寬接口(wide interface),這個寬接口允許它讀取所有的數據,以便根據這些數據恢復這個發起人對象的內部狀態。

備忘錄與負責人之間除了使用窄接口,也有直接使用了寬接口的實現方式,這種方式原則上是破壞了封裝性的。但是開發者之間的約定,同樣可以在一定程度上實現備忘錄模式的大部分用意。

寬接口實現

public class Memento
{
    public string State { get; set; }

    public Memento(string state)
    {
        this.State = state;
    }
}
public class Originator
{
    private string state;
    public string State
    {
        get
        {
            return state;
        }
        set
        {
            state = value;
            Console.WriteLine(state);
        }
    }

    public Memento CreateMemento()
    {
        return new Memento(state);
    }
    //將發起人恢復到備忘錄對象所記載的狀態
    public void RestoreMemento(Memento memento)
    {
        this.State = memento.State;
    }
}

public class Caretaker
{

    private Memento memento;
    //備忘錄的取值方法
    public Memento RetrieveMemento()
    {
        return this.memento;
    }
    //備忘錄的賦值方法
    public void SaveMemento(Memento memento)
    {
        this.memento = memento;
    }
}

調用端

public class WildClient
{
    public static void Entry()
    {
        Originator originator = new Originator();
        originator.State = "ON";
        Caretaker caretaker = new Caretaker();
        caretaker.SaveMemento(originator.CreateMemento());
        originator.State = "OFF";
        originator.RestoreMemento(caretaker.RetrieveMemento());
    }
}

在這段代碼中,首先將發起人對象的狀態設置成“ON”,並創建一個備忘錄對象將這個狀態存儲起來;然後將發起人對象的狀態改成“OFF”;最後又將發起人對象恢復到備忘錄對象所存儲起來的狀態,即“ON”狀態。

但這種寬接口實現方式,負責人維護的Memento,是任何類型都可以訪問或修改的。

窄接口實現

窄接口要求備忘錄角色對發起人(Originator)角色對象提供一個寬接口,而爲其他對象提供一個窄接口。爲了實現這裏要求的雙重接口,在C#中可以採用將備忘錄角色類設計成發起人角色類的內部類的方式。
將Memento設成Originator類的內部類,從而將Memento對象封裝在Originator裏面;在外部提供一個標識接口IMemento給Caretaker以及其他對象。這樣,Originator類看到的是Menmento的所有接口,而Caretaker以及其他對象看到的僅僅是標識接口MementoIF所暴露出來的接口。

public interface IMemento { }

public class Originator
{
    private class Memento : IMemento
    {
        public string State { get; set; }

        public Memento(string state)
        {
            this.State = state;
        }
    }
    private string state;
    public string State
    {
cccxcxcxxc         }
        se
        {
            state = value;
            Console.WriteLine(state);
        }
    }

    public IMemento CreateMemento()
    {
        return new Memento(state);
    }
    //將發起人恢復到備忘錄對象所記載的狀態
    public void RestoreMemento(IMemento memento)
    {
        if (memento == null)
        {
            return;
        }
        this.State = (memento as Memento).State;
    }
}

public class Caretaker
{

    private IMemento memento;
    //備忘錄的取值方法
    public IMemento RetrieveMemento()
    {
        return this.memento;
    }
    //備忘錄的賦值方法
    public void SaveMemento(IMemento memento)
    {
        this.memento = memento;
    }
}

由於IMemento只是一個標識接口,並沒有任何方法,所以CareTaker無法修改其維護的IMemento實例。

多個檢查點

前面的實現中備忘錄只保存了發起人的一個狀態,但很多時候這是無法滿足需求的,比如遊戲不可能只讓玩家保存一個檢查點。前面的代碼只需修改CareTaker就可以實現多個檢查點,將備忘錄對象壓入棧中,恢復時彈出即可:

public class MultiCaretaker
{
    private Stack<IMemento> mementos = new Stack<IMemento>();
    //備忘錄的取值方法
    public IMemento RetrieveMemento()
    {
        if (mementos.Count == 0)
        {
            return null;
        }
        return mementos.Pop();
    }
    //備忘錄的賦值方法
    public void SaveMemento(IMemento memento)
    {
        mementos.Push(memento);
    }
}

調用端

public class MultiCheckpoint
{
    public static void Entry()
    {
        Originator originator = new Originator();
        originator.State = "ON";
        MultiCaretaker caretaker = new MultiCaretaker();
        caretaker.SaveMemento(originator.CreateMemento());
        originator.State = "Volume 1";
        caretaker.SaveMemento(originator.CreateMemento());
        originator.State = "Volume 2";
        caretaker.SaveMemento(originator.CreateMemento());
        originator.State = "Volume 3";
        caretaker.SaveMemento(originator.CreateMemento());
        originator.State = "OFF";
        originator.RestoreMemento(caretaker.RetrieveMemento());
        originator.RestoreMemento(caretaker.RetrieveMemento());
        originator.RestoreMemento(caretaker.RetrieveMemento());
        originator.RestoreMemento(caretaker.RetrieveMemento());
    }
}

參考書籍:
王翔著 《設計模式——基於C#的工程化實現及擴展》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章