zoukankan      html  css  js  c++  java
  • 设计模式(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#的工程化实现及扩展》

  • 相关阅读:
    Oracle SQL 函数
    j2me MIDP2.0 下实现split函数
    Linux Oracle 增量恢复时错误 ORA19573: 无法获得 exclusive 入队 (数据文件 5 的)
    Linux Oracle10 建立归档模式的详细过程
    j2me MIDP2.0 下实现的图片缩放函数
    linux下oracle10g建立归档模式 接连出现错误:ORA19905 ORA01078 LRM00109
    j2me下 触摸屏的开发 NetBeans 模拟器支持触摸屏
    高级程序员:你不可不知的20条编程经验(转载)
    生成规定大小的图片(缩略图生成)
    asp.net简单实现用button做按钮图片
  • 原文地址:https://www.cnblogs.com/zhixin9001/p/13556465.html
Copyright © 2011-2022 走看看