设计模式的征途—20.备忘录(Memento)模式

相信每个人都有后悔的时候,但是人生并无后悔药,有些错误一旦发生就无法再挽回,有些事一旦错过就不会再重来,有些话一旦说出口也就不可能再收回,这就是人生。为了不让自己后悔,我们总是需要三思而后行。这里我们要学习一种可以在软件中实现后悔机制的设计模式—备忘录模式,它是软件中的“后悔药”。

备忘录模式(Memento) 学习难度:★★☆☆☆ 使用频率:★★☆☆☆

一、可悔棋的中国象棋游戏

Background:M公司欲开发一款可以运行在Android平台的触摸式中国象棋软件,如下图所示。由于考虑到有些用户是新手,经常不小心走错棋;还有些用户因为不习惯使用手指在手机屏幕上拖动棋子,常常出现操作失误,因此该中国象棋软件要提供“悔棋”功能,用户走错棋或操作失误后可恢复到前一个步骤。

  如何实现“悔棋”功能是M公司开发人员需要面对的一个重要问题。“悔棋”就是让系统恢复到某个历史状态,在很多软件中称之为“撤销”。

  在实现撤销时,首先需要保存系统的历史状态,当用户需要取消错误操作并且返回到某个历史状态时,可以取出事先保存的历史状态来覆盖当前状态,如下图所示。

  备忘录正是为解决此类撤销问题而诞生,它为软件提供了“后悔药”。

二、备忘录模式概述

2.1 备忘录模式简介

  备忘录模式提供了一种状态恢复的机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂存的备忘录将状态恢复。

备忘录(Memento)模式:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为型模式,其别名为Token。  

2.2 备忘录模式结构

  备忘录模式的核心在于备忘录类以及用于管理备忘录的负责任类的设计,其结构如下图所示:

  (1)Originator(原发器):它是一个普通类,可以创建一个备忘录,并存储其当前内部状态,也可以使用备忘录来恢复其内部状态,一般需要保存内部状态的类设计为原发器。

  (2)Memento(备忘录):存储原发器的状态,根据原发器来决定保存哪些内部状态。

  (3)Caretaker(负责任):负责任又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。

三、可悔棋的中国象棋实现

3.1 基本设计结构

  为了实现撤销功能,M公司开发人员决定使用备忘录模式来设计中国象棋,其基本结构如下图所示:

  其中,Chessman充当原发器,ChessmanMemento充当备忘录,而MementoCaretaker充当负责人,在MementoCaretaker中定义了一个ChessmanMemento的对象,用于存储备忘录。

3.2 具体代码实现

  (1)原发器:Chessman

    /// <summary>
    /// 原发器:Chessman
    /// </summary>
    public class Chessman
    {
        public string Label { get; set; }
        public int X { get; set; }
        public int Y { get; set; }

        public Chessman(string label, int x, int y)
        {
            Label = label;
            X = x;
            Y = y;
        }

        // 保存状态
        public ChessmanMemento Save()
        {
            return new ChessmanMemento(Label, X, Y);
        }

        // 恢复状态
        public void Restore(ChessmanMemento memento)
        {
            Label = memento.Label;
            X = memento.X;
            Y = memento.Y;
        }
    }

  (2)备忘录:ChessmanMemento

    /// <summary>
    /// 备忘录:ChessmanMemento
    /// </summary>
    public class ChessmanMemento
    {
        public string Label { get; set; }
        public int X { get; set; }
        public int Y { get; set; }

        public ChessmanMemento(string label, int x, int y)
        {
            Label = label;
            X = x;
            Y = y;
        }
    }

  (3)负责人:MementoCaretaker

    /// <summary>
    /// 负责人:MementoCaretaker
    /// </summary>
    public class MementoCaretaker
    {
        public ChessmanMemento Memento { get; set; }
    }

  (4)客户端测试

    public static void Main()
    {
        MementoCaretaker mc = new MementoCaretaker();
        Chessman chess = new Chessman("车", 1, 1);
        Display(chess);
        // 保存状态
        mc.Memento = chess.Save();
        chess.Y = 4;
        Display(chess);
        // 保存状态
        mc.Memento = chess.Save();
        Display(chess);
        chess.X = 5;
        Display(chess);

        Console.WriteLine("---------- Sorry,俺悔棋了 ---------");

        // 恢复状态
        chess.Restore(mc.Memento);
        Display(chess);
    }

  这里定义了一个辅助显示的方法Display

    public static void Display(Chessman chess)
    {
        Console.WriteLine("棋子 {0} 当前位置为:第 {1} 行 第 {2} 列", chess.Label, chess.X, chess.Y);
    }

  编译运行后结果如下图所示:

  

3.3 多次撤销重构

  刚刚我们实现的是单次撤销,那么如果要实现多次撤销呢?这里我们在负责人类中将原来的单一对象改为集合来存储多个备忘录,每个备忘录负责保存一个历史状态,在撤销时可以对备忘录集合进行逆向遍历,回到一个指定的历史状态,而且还可以对备忘录集合进行正向遍历,实现重做(ReDo)或恢复操作。

  这里我们设计一个新的负责人类NewMementoCaretaker类进行小修改,其代码如下:

    /// <summary>
    /// 负责人:NewMementoCaretaker
    /// </summary>
    public class NewMementoCaretaker
    {
        private IList<ChessmanMemento> mementoList = new List<ChessmanMemento>();

        public ChessmanMemento GetMemento(int i)
        {
            return mementoList[i];
        }

        public void SetMemento(ChessmanMemento memento)
        {
            mementoList.Add(memento);
        }
    }

  客户端测试代码如下:

    private static int index = -1;
    private static NewMementoCaretaker mementoCaretaker = new NewMementoCaretaker();

    public static void Main()
    {
        Chessman chess = new Chessman("车", 1, 1);
        Play(chess);
        chess.Y = 4;
        Play(chess);
        chess.X = 5;
        Play(chess);

        Undo(chess, index);
        Undo(chess, index);
        Redo(chess, index);
        Redo(chess, index);
    }

    // 下棋
    public static void Play(Chessman chess)
    {
        // 保存备忘录
        mementoCaretaker.SetMemento(chess.Save());
        index++;

        Console.WriteLine("棋子 {0} 当前位置为 第 {1} 行 第 {2} 列", chess.Label, chess.X, chess.Y);
    } 

    // 悔棋
    public static void Undo(Chessman chess, int i)
    {
        Console.WriteLine("---------- Sorry,俺悔棋了 ---------");
        index--;
        // 撤销到上一个备忘录
        chess.Restore(mementoCaretaker.GetMemento(i - 1));

        Console.WriteLine("棋子 {0} 当前位置为 第 {1} 行 第 {2} 列", chess.Label, chess.X, chess.Y);
    }

    // 撤销悔棋
    public static void Redo(Chessman chess, int i)
    {
        Console.WriteLine("---------- Sorry,撤销悔棋 ---------");
        index++;
        // 恢复到下一个备忘录
        chess.Restore(mementoCaretaker.GetMemento(i + 1));

        Console.WriteLine("棋子 {0} 当前位置为 第 {1} 行 第 {2} 列", chess.Label, chess.X, chess.Y);
    }

  编译运行后的结果如下图所示:

  

四、备忘录模式小结

4.1 主要优点

  (1)提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤。

  (2)实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。

4.2 主要缺点

  资源消耗过大,资源消耗过大,资源消耗过大 => 说三遍!因为每保存一次对象状态都需要消耗一定系统资源。

4.3 应用场景

  (1)需要保存一个对象在某一个时刻的全部状态或部分状态状态,以便需要在后面需要时可以恢复到先前的状态。

  (2)防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象。

参考资料

  

  刘伟,《设计模式的艺术—软件开发人员内功修炼之道》

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

时间: 2024-12-18 11:21:31

设计模式的征途—20.备忘录(Memento)模式的相关文章

C++设计模式实现--备忘录(Memento)模式

一. 备忘录模式 定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可将该对象恢复到原先保存的状态. 结构图: 使用范围: Memento 模式比较适用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时,Originator 可以根据保存的 Memento 信息还原到前一状态. 代码: [cpp] view plaincopy //备忘录类 //负责存储 Originator 对象的内部状态 class Meme

Java 实现备忘录(Memento)模式

/** * 数据对象 * @author stone * */ public class DataState { private String action; public void setAction(String action) { this.action = action; } public String getAction() { return action; } } /** * 一个保存另外一个对象内部状态拷贝 的对象.这样以后就可以将该对象恢复到原先保存的状态. * * @autho

设计模式之第20章-访问者模式(Java实现)

设计模式之第20章-访问者模式(Java实现) “嘿,你脸好红啊.”“精神焕发.”“怎么又黄了?”“怕冷,涂的,涂的,蜡.”“身上还有酒味,露馅了吧,原来是喝酒喝的啊.”“嘿嘿,让,让你发现了,今天来几个朋友,然后就小聚一下,小饮,几杯啦.”“小日子过得不错嘛.”“那是自然,要不然,再去喝两杯.”“别介,我还有要事要做呢,鱼哥你别坑我.”“什么,什么要紧事,能比的上,喝酒啊”.“走,陪我,陪我喝两杯去.”(作者已被拉走.)访问者登场. 访问者模式之自我介绍 累的死俺的杰特们(ladies and

备忘录(Memento)模式

备忘录模式又叫做快照模式或者Token模式. 备忘录对象是一个用来存储另一个对象内部状态的快照的对象.备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态.备忘录模式常常与命令模式和迭代子模式一起使用. 常见的系统往往不止存储一个状态,而是需要存储多个状态.这些状态常常是一个对象历史发展的不同阶段的快照,存储这些快照的备忘录对象叫做此对象的历史:某一个快照所处的位置叫做检查点. 1.角色 1.备忘录角色 备忘录

设计模式的征途—文章目录索引

1.预备篇 UML类图10分钟快速入门 2.创建型模式 ① 设计模式的征途-01.单例(Singleton)模式 ② 设计模式的征途-02.简单工厂(Simple Factory)模式 ③ 设计模式的征途-03.工厂方法(Factory Method)模式 ④ 设计模式的征途-04.抽象工厂(Abstract Factory)模式 ⑤ 设计模式的征途-05.原型(Prototype)模式 ⑥ 设计模式的征途-06.建造者(Builder)模式 3.结构型模式 ① 设计模式的征途-07.适配器(A

设计模式20:Memento 备忘录模式(行为型模式)

Memento 备忘录模式(行为型模式) 对象状态的回溯 对象状态的变化无端,如何回溯.恢复对象在某个点的状态? 动机(Motivation) 在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态.如果使用一些共有接口来让其他对象得到对象的状态,便会暴露对象的细节实现. 如何实现对象状态良好保存与恢复?同时又不会因而破坏对象本身的封装性. 意图(Intent) 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样

设计模式(行为型)之备忘录模式(Memento Pattern)

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbober] 阅读前一篇<设计模式(行为型)之中介者模式(Mediator Pattern)>http://blog.csdn.net/yanbober/article/details/45533335 概述 备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无

设计模式之十四:备忘录模式(Memento)

备忘录模式: 在不破换封装性的前提下,捕获一个对象的内部状态并将这个状态保存到对象外部,这样这个对象之后可以恢复到保存的状态. Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later UML图: 主要包括: Memento(Memento):存储Originator的内部状态.

Memento模式(备忘录设计模式)

Memento模式? 使用面向对象编程的方式实现撤销功能时,需要事先保存实例的相关状态信息.然后,在撤销时,还需要根据所保存的信息将实例恢复至原来的状态.这个时候你需要使用Memento设计模式.(以及实例实现对状态的保存) 关键字: 1.·Undo(撤销) 2.·Redo(重做) 3.·History(历史记录) 4.·Snapshot(快照) 破坏封装性: 将依赖于实例内部结构的代码分散地编写在程序中的各个地方,导致程序变得难以维护. 宽窄接口 wide interface--宽接口(APl