设计模式(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#的工程化实现及扩展》

posted @ 2020-08-24 21:32  zhixin9001  阅读(218)  评论(0编辑  收藏  举报