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

备忘录模式(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. Java基础语法<七> 对象与类

    笔记整理 来源于<Java核心技术卷 I > <Java编程思想> 1. 类之间的关系 依赖 users– a 是一种最明显的.最常见的关系.如果一个类的方法操作另一个类的对象 ...

  2. mongoDB 学习笔记纯干货(mongoose、增删改查、聚合、索引、连接、备份与恢复、监控等等)

    最后更新时间:2017-07-13 11:10:49 原始文章链接:http://www.lovebxm.com/2017/07/13/mongodb_primer/ MongoDB - 简介 官网: ...

  3. 利用HTML5判断用户是否正在浏览页面技巧

    现在,HTML5里页面可见性接口就提供给了程序员一个方法,让他们使用visibilitychange页面事件来判断当前页面可见性的状态,并针对性的执行某些任务.同时还有新的document.hidde ...

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

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

  5. rsync技术报告(翻译)

    本篇为rsync官方推荐技术报告rsync technical report的翻译,主要内容是Rsync的算法原理以及rsync实现这些原理的方法.翻译过程中,在某些不易理解的地方加上了译者本人的注释 ...

  6. JS遍历属性和方法

    引用原文:http://www.cnblogs.com/lishenglyx/archive/2008/12/08/1350573.html#undefined <script language ...

  7. Neo4j图数据库

    01. 图数据库 图数据库是专门存储和检索大量信息网络的存储引擎.它可以有效地将数据存储为节点和关系,并允许高性能检索和查询这些结构.属性可以添加到节点和关系.节点可以用零个或多个标签标注,关系总是定 ...

  8. sql必知必会-总结篇

    总结: 1.全书总览:数据查询.新增.删除:表的新增.更新操作:视图.存储过程.事务.索引的描述,高级sql功能:约束.触发器.索引 2.特色:术语简明定义,讲述最简单化.简而全面. 3.长进的地方: ...

  9. docke镜像上传到dockerhub仓库和阿里云docker仓库的方法

    操作指南   1.  登录阿里云docker registry: $ sudo docker login --username=linjiaxin897591495 registry.cn-hangz ...

  10. oAuth 认证

    这段时间公司开发项目用到oAuth2协议,现在做一下梳理. CORS即Cross Origin Resouce Share,跨域资源共享:是W3C为防止脚本攻击,而制定的安全标准之一,它云溪浏览器向跨 ...