二十三种设计模式[18] - 备忘录模式(Memento Pattern)

前言

       在日常生活中我们经常会接触到类似回滚或撤销的功能,比如单机游戏中的存档、windows中的备份/恢复、手机的恢复出厂设置以及编辑器中的撤销功能。本质上这些功能都是将系统某个瞬间的状态备份,以便在需要时将系统恢复至备份的状态。而备忘录模式就是一种帮助我们实现这一系列操作的设计模式。

       备忘录模式又名快照模式,对象行为型模式的一种。在《设计模式 - 可复用的面向对象软件》一书中将之描述为“ 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态 ”。

结构

Memento_1

  • Memento(备忘录):用来存储原发器对象的内部状态以及保证备忘录内部只对原发器开放;
  • Originator(原发器):需要备份的对象类型,提供一个创建备忘录实体的工厂方法以及一个通过备忘录来恢复备份的函数;
  • Caretaker(负责人):只负责备忘录的保存工作;

       在备忘录模式中,一个备忘录是一个对象,它是一个用来存储另一对象在某个瞬间的状态的类,而后者则称为该备忘录的原发器或发起人。为了保证原发器的封装性,备忘录中存储的数据只能由原发器访问,而负责人只能对备忘录进行存储和传递操作。在《设计模式》一书中表示,备忘录对外部对象提供了两个接口,分别是只能存储和传递对象的窄接口以及可以访问备忘录中存储的所有数据的宽接口。

示例

Memento_2

public interface IMemento { }

public class Originator
{
    public string State { set; get; } = string.Empty;

    public IMemento CreateMemento()
    {
        return new Memento(this.State);
    }

    public void Restore(IMemento obj)
    {
        var memento = obj as Memento;

        if(memento == null)
        {
            throw new TypeLoadException();
        }

        this.State = memento.State;
    }

    private class Memento : IMemento
    {
        public string State { set; get; } = string.Empty;

        public Memento(string state)
        {
            this.State = state;
        }
    }
}

public class Caretaker
{
    public IMemento Memento { set; get; } = null;
}

static void Main(string[] args)
{
    Originator originator = new Originator();
    originator.State = "v1";

    Caretaker caretaker = new Caretaker();
    caretaker.Memento = originator.CreateMemento();

    originator.State = "v2";
    Console.WriteLine($"[Originator.State]:{originator.State}");

    originator.Restore(caretaker.Memento);
    Console.WriteLine($"[Originator.State]:{originator.State}");

    Console.ReadKey();
}

image

       在结构中提到,为了不破坏原发器的封装性,需要对备忘录存储的数据做出一定的访问限制。示例中的做法是将备忘录Memento类作为原发器Originator类的内部类,Memento实现了一个不做任何定义的接口IMemento作为暴露给负责人的类型,使负责人不能对备忘录的内部数据做出操作。

  • 备忘录模式 + 命令模式

       命令模式(Command Pattern)的应用场景之一是实现一个可撤销的操作。备忘录模式与命令模式的组合使用,可以使撤销操作的职责更明确。如下。

public interface IMemento { }

public class Originator
{
    public string State { set; get; } = string.Empty;

    public IMemento CreateMemento()
    {
        return new Memento(this.State);
    }

    public void Restore(IMemento obj)
    {
        var memento = obj as Memento;

        if(memento == null)
        {
            throw new TypeLoadException();
        }

        this.State = memento.State;
    }

    private class Memento : IMemento
    {
        public string State { set; get; } = string.Empty;

        public Memento(string state)
        {
            this.State = state;
        }
    }
}

public class ChangeStateCommand
{
    private Originator OriginatorObj { set; get; } = null;
    private IMemento MementoObj { set; get; } = null;

    public ChangeStateCommand(Originator originator)
    {
        this.OriginatorObj = originator;
    }

    public void Execute(string state)
    {
        this.MementoObj = this.OriginatorObj.CreateMemento();
        this.OriginatorObj.State = state;
    }

    public void Undo()
    {
        this.OriginatorObj.Restore(this.MementoObj);
    }
}

static void Main(string[] args)
{
    Originator originator = new Originator();
    ChangeStateCommand command = new ChangeStateCommand(originator);
    command.Execute("v1");
    command.Execute("v2");
               
    Console.WriteLine($"[Originator.State]:{originator.State}");

    command.Undo();
    Console.WriteLine($"[Originator.State]:{originator.State}");

    Console.ReadKey();
}

       在与命令模式组合使用时,我们将命令类看做负责人来存储原发器的内部状态,示例中在执行ChangeStateCommand类中的Execute函数时备份Originator的内部状态以便在执行Undo函数时恢复。

总结

       备忘录模式给我们提供了一种可以恢复对象的机制,能够方便我们将某一对象回退至某一瞬间的状态。也在某种程度上简化了原发器的设计,使原发器不必为了回滚机制而保留一些与之无关的引用。如果原发器在生成备忘录时必须存储大量的信息或客户非常频繁的创建备忘录和恢复原发器时会导致非常大的内存开销。


       以上,就是我对备忘录模式的理解,希望对你有所帮助。

       示例源码:https://gitee.com/wxingChen/DesignPatternsPractice

       系列汇总:https://www.cnblogs.com/wxingchen/p/10031592.html

       本文著作权归本人所有,如需转载请标明本文链接(https://www.cnblogs.com/wxingchen/p/10101563.html)

posted @ 2018-12-11 13:09  王兴Chen  阅读(141)  评论(0编辑  收藏  举报