设计模式的征途—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("车", , );
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)防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象。
参考资料
刘伟,《设计模式的艺术—软件开发人员内功修炼之道》
设计模式的征途—20.备忘录(Memento)模式的更多相关文章
- 设计模式C++描述----17.备忘录(Memento)模式
一. 备忘录模式 定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可将该对象恢复到原先保存的状态. 结构图: 使用范围: Memento 模式比较适用于功能 ...
- 设计模式之第20章-访问者模式(Java实现)
设计模式之第20章-访问者模式(Java实现) “嘿,你脸好红啊.”“精神焕发.”“怎么又黄了?”“怕冷,涂的,涂的,蜡.”“身上还有酒味,露馅了吧,原来是喝酒喝的啊.”“嘿嘿,让,让你发现了,今天来 ...
- C++设计模式实现--备忘录(Memento)模式
一. 备忘录模式 定义:在不破坏封装性的前提下,捕获一个对象的内部状态.并在该对象之外保存这个状态. 这样以后就可将该对象恢复到原先保存的状态. 结构图: 使用范围: Memento 模式比較适用于功 ...
- 设计模式(十八)Memento模式
在使用面向对象编程的方式实现撤销功能时,需要事先保存实例的相关状态信息.然后,在撤销时,还需要根据所保存的信息将实例恢复至原来的状态. 要想恢复实例,需要一个可以自由访问实例内部结构的权限.但是,如果 ...
- 备忘录(Memento)模式
备忘录模式又叫做快照模式或者Token模式. 备忘录对象是一个用来存储另一个对象内部状态的快照的对象.备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉住,并外部化,存储起来,从而可以在将来 ...
- 《图解设计模式》读书笔记8-2 MEMENTO模式
目录 Memento模式 示例代码 程序类图 代码 角色和类图 模式类图 角色 思路拓展 接口可见性 保存多少个Memento 划分Caretaker和Originator的意义 Memento模式 ...
- Java设计模式之从[暗黑破坏神存档点]分析备忘录(Memento)模式
在大部分游戏中,都有一个"存档点"的概念.比如,在挑战boss前,游戏会在某个地方存档,假设玩家挑战boss失败,则会从这个存档点開始又一次游戏.因此,我们能够将这个"存 ...
- 设计模式C++描述----20.迭代器(Iterator)模式
一. 举例说明 我们知道,在 STL 里提供 Iterator 来遍历 Vector 或者 List 数据结构. Iterator 模式也正是用来解决对一个聚合对象的遍历问题,将对聚合的遍历封装到一个 ...
- 设计模式的征途(C#实现)—文章目录索引
1.预备篇 UML类图10分钟快速入门 2.创建型模式 ① 设计模式的征途-01.单例(Singleton)模式 ② 设计模式的征途-02.简单工厂(Simple Factory)模式 ③ 设计模式的 ...
随机推荐
- 单片机C语言基础编程源码六则2
1.某单片机系统的P2口接一数模转换器DAC0832输出模拟量,现在要求从DAC0832输出连续的三角波,实现的方法是从P2口连续输出按照三角波变化的数值,从0开始逐渐增大,到某一最大值后逐渐减小,直 ...
- Win Linux 双系统安装指南
双系统安装指南 环境说明 硬件:一块240G NVMe,一块240G SSD,一块2T的HDD. 系统:Linux Mint 18.2,Windows 10 Enterprise Version 17 ...
- firefox插件开发及源码下载
在个别情况下,由于数据量巨大,造成显示性能的明显下降,此时使用c++开发firefox插件,可以提高用户使用体验. test.html: 在插件中,我们导出3个函数给js:AddStr, Pause, ...
- Ubuntu16.04修改内核启动
写这篇文章一是为了对遇到同样问题的人提供一个参考,二来也是为了自己便于总结和查阅.希望大神勿喷. 好了,废话不多说了,转入正题. 前几天给自己的电脑装了个Ubuntu16.04LTS,自己顺手就把里边 ...
- ARM开发(2)基于STM32的蜂鸣器
基于STM32的蜂鸣器 一 蜂鸣器原理: 1.1 本实验实现1个蜂鸣器间隔1S鸣叫. 1.2 实验思路:根据电路图原理,给蜂鸣器相关引脚赋予高低电平,实现电路的导通,使蜂鸣器实现鸣叫或不鸣. 1 ...
- css的背景background的相关属性
今天需要做一个占满设备宽度的轮播图,这里作为demo仅展示一张图,下面分别是要操作的图片(这里做了缩放处理,实际的图比较大),以及要实现的效果图,很明显两者是不成比例的: (图一) ...
- Objective-C AVPlayer播放视频的使用与封装
大致效果 不要介意.界面有点丑... 界面搭建 看下成员变量就知道我怎么搭建的了,这里我将video播放层的size作为参照量,对所有控件的size按照其video的size宽高进行比例缩放 @int ...
- Spring源码情操陶冶-AbstractApplicationContext#postProcessBeanFactory
阅读源码有利于陶冶情操,承接前文Spring源码情操陶冶-AbstractApplicationContext#prepareBeanFactory 约定:web.xml中配置的contextClas ...
- 【JAVASCRIPT】React学习- 杂七杂八
摘要 记录 React 学习中的小细节 setState setState 有一定的时间延迟,如果需要保证 setState 之后执行某些动作,可以采用以下方法 this.setState({ vis ...
- super 与 this 同时使用问题
大家都知道this 和 super 调用构造函数时都必须放在第一句,今天同学问我的一个问题有点意思. 那么:我怎么在子类中 显式的用 super 初始化父类同时用 this 初始化子类? ------ ...