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

备忘录模式(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("车", , );
Display(chess);
// 保存状态
mc.Memento = chess.Save();
chess.Y = ;
Display(chess);
// 保存状态
mc.Memento = chess.Save();
Display(chess);
chess.X = ;
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 = -;
private static NewMementoCaretaker mementoCaretaker = new NewMementoCaretaker(); public static void Main()
{
Chessman chess = new Chessman("车", , );
Play(chess);
chess.Y = ;
Play(chess);
chess.X = ;
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 - )); 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 + )); 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

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

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

  1. 设计模式C++描述----17.备忘录(Memento)模式

    一. 备忘录模式 定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可将该对象恢复到原先保存的状态. 结构图: 使用范围: Memento 模式比较适用于功能 ...

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

    设计模式之第20章-访问者模式(Java实现) “嘿,你脸好红啊.”“精神焕发.”“怎么又黄了?”“怕冷,涂的,涂的,蜡.”“身上还有酒味,露馅了吧,原来是喝酒喝的啊.”“嘿嘿,让,让你发现了,今天来 ...

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

    一. 备忘录模式 定义:在不破坏封装性的前提下,捕获一个对象的内部状态.并在该对象之外保存这个状态. 这样以后就可将该对象恢复到原先保存的状态. 结构图: 使用范围: Memento 模式比較适用于功 ...

  4. 设计模式(十八)Memento模式

    在使用面向对象编程的方式实现撤销功能时,需要事先保存实例的相关状态信息.然后,在撤销时,还需要根据所保存的信息将实例恢复至原来的状态. 要想恢复实例,需要一个可以自由访问实例内部结构的权限.但是,如果 ...

  5. 备忘录(Memento)模式

    备忘录模式又叫做快照模式或者Token模式. 备忘录对象是一个用来存储另一个对象内部状态的快照的对象.备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉住,并外部化,存储起来,从而可以在将来 ...

  6. 《图解设计模式》读书笔记8-2 MEMENTO模式

    目录 Memento模式 示例代码 程序类图 代码 角色和类图 模式类图 角色 思路拓展 接口可见性 保存多少个Memento 划分Caretaker和Originator的意义 Memento模式 ...

  7. Java设计模式之从[暗黑破坏神存档点]分析备忘录(Memento)模式

    在大部分游戏中,都有一个"存档点"的概念.比如,在挑战boss前,游戏会在某个地方存档,假设玩家挑战boss失败,则会从这个存档点開始又一次游戏.因此,我们能够将这个"存 ...

  8. 设计模式C++描述----20.迭代器(Iterator)模式

    一. 举例说明 我们知道,在 STL 里提供 Iterator 来遍历 Vector 或者 List 数据结构. Iterator 模式也正是用来解决对一个聚合对象的遍历问题,将对聚合的遍历封装到一个 ...

  9. 设计模式的征途(C#实现)—文章目录索引

    1.预备篇 UML类图10分钟快速入门 2.创建型模式 ① 设计模式的征途-01.单例(Singleton)模式 ② 设计模式的征途-02.简单工厂(Simple Factory)模式 ③ 设计模式的 ...

随机推荐

  1. 浅谈Ajax 异步的几点细节

    1.浏览器执行到Ajax代码的这行语句的时候,发出了一个HTTP请求,欲想请求服务器上的数据.服务器此时开始I/O,所谓的I/O就是磁盘的读写,需要花费一些时间,所以不会立即产生下行的HTTP报文: ...

  2. 详解Java API之正则表达式

    正则表达式描述的是一种规则,符合这种限定规则的字符串我们认为它某种满足条件的,是我们所需的.在正则表达式中,主要有两种字符,一种描述的是普通的字符,另一种描述的是元字符.其中元字符是整个正则表达式的核 ...

  3. Ubuntu tty中文字符乱码

    默认的tty只能显示一个字节,我们可以用setfont命令去改tty字体,但仅仅局限在一个字节内,不支持UTF-8多字节,所以我们就没办法使用汉字.但是我们可以使用FbTerm啊!FbTerm是支持中 ...

  4. 51nod_1831: 小C的游戏(Bash博弈 找规律)

    题目链接 此类博弈不需要考虑sg函数,只需要确定必胜态和必败态,解题思路一般为打败先打表找规律,而后找规律给出统一的公式.打表方式:给定初始条件(此题中为ok[0]=ok[1]=0),然后从低到高枚举 ...

  5. 51nod_1298:圆与三角形(计算几何)

    题目链接 判断圆和三角形是否相交   可以转化为   判断三条线段是否和圆相交 #include<iostream> #include<cstdio> #include< ...

  6. 配置PLSQL,提升工作效率

    界面模板的配置: 方便用户快速点击需要的功能.如打开SQL Window 1.打开customize,用户自定义Toolbars对话框. 2.在Commands命令标签页,选中要添加的命令,拖动到工具 ...

  7. Java映射

    1.Student类 package com.zdsofe.javaweb.lianxi1; public class Student { public String stuName; private ...

  8. C#导入导出Excele数据

    注:对于实体类对象最好新建一个并且继承原有实体类,这样可以将类型进行修改: 方法一:此种方法是用EPPLUS中的FileInfo流进行读取的(是不是流我还真不太了解,若有懂得请留言,非常感谢了) us ...

  9. Android学习笔记-ScrollView(滚动条)

    本节引言: 本节带来的是Android基本UI控件中的第十个:ScrollView(滚动条),或者我们应该叫他 竖直滚动条,对应的另外一个水平方向上的滚动条:HorizontalScrollView, ...

  10. 关于SGA与memory_target 大小冲突照成数据库无法挂载问题

    关于SGA与memory_target 大小冲突照成数据库无法挂载问题   错误信息: ORA-00844: Parameter not taking MEMORY_TARGET into accou ...