备忘录模式(Memento Pattern)

该文章的最新版本已迁移至个人博客【比特飞】,单击链接 https://www.byteflying.com/archives/421 访问。

备忘录模式属于行为型模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

备忘录模式为我们提供了“后悔药”的机制,为我们在需要的时候,可以将对对象的修改撤销甚至重做。

角色:

1、原发器(Originator)

创建一个备忘录,用以记录当前时刻它的内部状态,在需要时使用备忘录恢复内部状态;

2、备忘录(Memento)

将原发器对象的内部状态存储起来;

3、备忘录管理者(Caretaker)

负责保存好备忘录,不能对备忘录的内容进行操作或检查。

示例:

命名空间MementoPattern中包含Memento备忘录类,Caretaker管理者类,象棋Chessman类。本案例将向大家演示如何撤销或重做对象棋位置的修改操作。本案例使用栈以支持多次撤销,并且重做支持前1次的多次撤销。本案例不支持重新设置棋子位置时所产生的分支。

namespace MementoPattern
public partial class Chessman {

	private Point _position;

	private Caretaker _caretaker = null;

	public Point Position {
get => _position;
set {
_position = value;
_caretaker.Memento.Position = value; Console.WriteLine(
String.Format(Const.POSITION_INFO, _position.X, _position.Y));
}
} public Chessman() : this(new Point(0, 0)) { } public Chessman(Point point) {
_caretaker = new Caretaker(new Memento());
Position = point;
} }

象棋棋子类Chessman,内部维持棋子的位置,在设置棋子位置时将信息保存到管理者所管理的备忘录中。

public partial class Chessman {

	public Chessman Undo(int step) {
try {
Console.WriteLine(Const.ARROW_LEFT);
Console.WriteLine($"Undo({step})!");
this._position = _caretaker.Memento.Undo(step);
Console.WriteLine(
String.Format(Const.POSITION_INFO, _position.X, _position.Y));
Console.WriteLine(Const.ARROW_RIGHT);
return this;
} catch(Exception ex) {
Console.WriteLine(ex.Message);
Console.WriteLine(Const.ARROW_RIGHT);
return this;
}
} public Chessman Redo() {
try {
Console.WriteLine(Const.ARROW_LEFT);
Console.WriteLine("Redo()!");
this._position = _caretaker.Memento.Redo();
Console.WriteLine(
String.Format(Const.POSITION_INFO, _position.X, _position.Y));
Console.WriteLine(Const.ARROW_RIGHT);
return this; } catch(Exception ex) {
Console.WriteLine(ex.Message);
Console.WriteLine(Const.ARROW_RIGHT);
return this;
}
} }

象棋棋子类Chessman的第2部分(partial),支持按步数撤销位置,并且支持重做命令。

public partial class Memento {

	private Point _position;

	public Point Position {
get => _position;
set {
_position = value;
_history.Push(new RedoInfo { Position = value });
_redoList.Clear();
}
} public Memento() {
_history = new Stack<RedoInfo>();
_redoList = new Stack<RedoInfo>();
} private Stack<RedoInfo> _history = null;
private Stack<RedoInfo> _redoList = null; public Point Undo(int step) {
int totalCount = 0;
List<string> temp = new List<string>();
foreach(var item in _history) {
if(string.IsNullOrWhiteSpace(item.GUID)) {
totalCount++;
} else {
if(!temp.Contains(item.GUID)) {
totalCount++;
temp.Add(item.GUID);
}
}
}
if(step >= totalCount) {
throw new InvalidOperationException("Too much steps!");
}
var guid = Guid.NewGuid().ToString("B");
for(int i = 1; i <= step; i++) {
Undo(guid);
}
return _position;
} }

备忘录类Memento,内部维持对位置的引用并用2个栈分别管理历史和重做数据。

public partial class Memento {

	private void UndoLoop(string guid) {
var history = _history.Pop();
history.GUID = guid;
_redoList.Push(history);
_position = _history.Peek().Position;
} private void Undo(string guid) {
var temp = _history.Peek().GUID;
if(string.IsNullOrWhiteSpace(temp)) {
UndoLoop(guid);
} else {
while(_history.Peek().GUID == temp) {
UndoLoop(guid);
}
}
} public Point Redo() {
if(_redoList.Count == 0) {
throw new InvalidOperationException("You can not redo now!");
}
var guid = _redoList.Peek().GUID;
while(_redoList.Count != 0 && _redoList.Peek().GUID == guid) {
_history.Push(_redoList.Pop());
_position = _history.Peek().Position;
}
return _position;
} }

备忘录类Memento的第2部分(partial),包含了按步数撤销和重做的具体逻辑。

public class Caretaker {

    public Memento Memento { get; set; }

    public Caretaker(Memento memento) {
Memento = memento;
} }

管理者Caretaker类,管理者负责维持备忘录。

public class RedoInfo {

    public Point Position { get; set; }

    public string GUID { get; set; }

}

重做RedoInfo类,按GUID的值判断是否是同一“批次”被撤销的。

public class Const {

    public const string POSITION_INFO = "Current position is ({0},{1})!";

    public const string ARROW_LEFT = "<---------------------------";

    public const string ARROW_RIGHT = "--------------------------->";

}

常量类Const,维护一些本案例中经常用到的字符串。在实际开发过程中不应当有此类,应该将相应的常量放在具体要使用的类中。2017年,阿里发布《阿里巴巴Java开发手册》,其中有一节提到此准则,所有使用面向对象编程语言的开发人员都应当遵从。

public class Program {

    private static Chessman _chessman = null;

    public static void Main(string[] args) {
_chessman = new Chessman(new Point(1, 10));
_chessman.Position = new Point(2, 20);
_chessman.Position = new Point(3, 30);
_chessman.Position = new Point(4, 40);
_chessman.Position = new Point(5, 50);
_chessman.Position = new Point(9, 40); _chessman.Undo(1)
.Undo(2)
.Undo(1)
.Redo()
.Redo()
.Redo()
.Redo()
.Redo()
.Undo(6)
.Undo(5)
.Undo(4); Console.ReadKey();
} }

以上是调用方的代码演示,撤销和重做方法经过特殊处理以支持方法链。以下是这个案例的输出结果:

Current position is (1,10)!
Current position is (2,20)!
Current position is (3,30)!
Current position is (4,40)!
Current position is (5,50)!
Current position is (9,40)!
<---------------------------
Undo(1)!
Current position is (5,50)!
--------------------------->
<---------------------------
Undo(2)!
Current position is (3,30)!
--------------------------->
<---------------------------
Undo(1)!
Current position is (2,20)!
--------------------------->
<---------------------------
Redo()!
Current position is (3,30)!
--------------------------->
<---------------------------
Redo()!
Current position is (5,50)!
--------------------------->
<---------------------------
Redo()!
Current position is (9,40)!
--------------------------->
<---------------------------
Redo()!
You can not redo now!
--------------------------->
<---------------------------
Redo()!
You can not redo now!
--------------------------->
<---------------------------
Undo(6)!
Too much steps!
--------------------------->
<---------------------------
Undo(5)!
Too much steps!
--------------------------->
<---------------------------
Undo(4)!
Current position is (1,10)!
--------------------------->

优点:

该文章的最新版本已迁移至个人博客【比特飞】,单击链接 https://www.byteflying.com/archives/421 访问。

1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态;

2、实现了信息的封装,使得用户不需要关心状态的保存细节。

缺点:

1、如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

使用场景:

1、需要保存/恢复数据的相关状态场景;

2、提供一个可回滚的操作。

C#设计模式之18-备忘录模式的更多相关文章

  1. 重学 Java 设计模式:实战备忘录模式「模拟互联网系统上线过程中,配置文件回滚场景」

    作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 实现不了是研发的借口? 实现不了,有时候是功能复杂度较高难以实 ...

  2. [设计模式] 18 备忘录模式Memento Pattern

    在GOF的<设计模式:可复用面向对象软件的基础>一书中对备忘录模式是这样说的:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可将该对象恢复到原先保存 ...

  3. java设计模式-----21、备忘录模式

    概念: Memento模式也叫备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是GoF的23种设计模式之一,属于行为模式,它的作用是保存对象的内部状态,并在需要的时候(u ...

  4. IOS设计模式之四(备忘录模式,命令模式)

    本文原文请见:http://www.raywenderlich.com/46988/ios-design-patterns. 由 @krq_tiger(http://weibo.com/xmuzyq) ...

  5. Java设计模式学习记录-备忘录模式

    前言 这次要介绍的是备忘录模式,也是行为模式的一种 .现在人们的智能手机上都会有备忘录这样一个功能,大家也都会用,就是为了记住某件事情,防止以后自己忘记了.那么备忘录模式又是什么样子的呢?是不是和手机 ...

  6. Java描述设计模式(24):备忘录模式

    本文源码:GitHub·点这里 || GitEE·点这里 一.生活场景 1.场景描述 常见的视频播放软件都具备这样一个功能:假设在播放视频西游记,如果这时候切换播放视频红楼梦,当再次切回播放西游记时, ...

  7. 大话设计模式Python实现-备忘录模式

    备忘录模式(Memento Pattern):不破坏封装性的前提下捕获一个对象的内部状态,并在该对象之外保存这个状态,这样已经后就可将该对象恢复到原先保存的状态 下面是一个备忘录模式的demo: #! ...

  8. 设计模式之GOF23备忘录模式

    备忘录模式Memento 场景: -Word文档编辑时,电脑死机或者断电,再次打开时,word提醒恢复之前的文档 -文件的撤回 核心: -就是保存某个对象内部状态的拷贝,这样以后就可以将该对象恢复到原 ...

  9. C#设计模式系列:备忘录模式(Memento)

    1.备忘录模式简介 1.1>.定义 备忘录模式在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可以将该对象恢复到原先保存的状态. 1.2>.使用频率 ...

  10. 设计模式学习之备忘录模式(Memento,行为型模式)(19)

    假如我们已经记录一个人的个人信息,但是发现信息写错了,然后我先备份下再去修改,结果发现原来的信息是正确的,于是我就看备份的个人信息还原到初始的状态,下面我们用代码去实现 class Program { ...

随机推荐

  1. 面试京东T5,被按在地上摩擦,鬼知道我经历了什么?

    一转眼间,光阴飞快,各大企业都开始招人,各大学校也开始准备陆续入学. 金三银四已经快结束了,有的朋友发来喜报,面试上了一线大厂,也有没有面试上的朋友跑来跟我说,被虐惨了,几天给大家分享下我一个面试京东 ...

  2. 主席树铺垫——总区间第k小

    题目描述(口糊) 先给定一个长度为n的数列,然后给m次操作,每次输入b,求第b小的数. 样例输入 5 7 4 10 9 23 5 1 2 3 4 5 样例输出 4 7 9 10 23 数据范围及温馨提 ...

  3. ES6语法——let和const

    一.let 1.定义 ES6新增了let命令,用来声明变量,用法类似于var,但是和var有一定的区别 2.let只在块级作用域内有效 首先来看一个比较简单的例子,请告诉我,他们分别输出什么 //代码 ...

  4. JAVA集合四:比较器--类自定义排序

    参考链接: HOW2J.CN 前言 对于JAVA集合,都能够用集合的工具类Collections 提供的方法: Collections.sort(List list) Collections.sort ...

  5. JAVA I/O基本操作

    JAVA I/O基本操作 JAVA文件操作 JAVA字节流 JAVA字符流 JAVA缓存流 JAVA对象流 JAVA数据流 本文主要借鉴以下博客和网站: how2j.cn 深入理解java中的I/O ...

  6. Python数据分析——numpy基础简介

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:基因学苑 NumPy(Numerical Python的简称)是高性 ...

  7. epic游戏平台如何启用认证器应用程序/二次验证码/谷歌身份验证器?

    1.登陆epic游戏平台,找到二次验证绑定界面 登陆https://www.epicgames.com/store/zh-CN/, 点右上角用户头像-[账户]. 之后点-[密码与安全] 在[双重验证] ...

  8. 亚马逊如何使用二次验证码/虚拟MFA/两步验证/谷歌验证器?

    一般点账户名——设置——安全设置中开通虚拟MFA两步验证 具体步骤见链接  亚马逊如何使用二次验证码/虚拟MFA/两步验证/谷歌验证器? 二次验证码小程序于谷歌身份验证器APP的优势 1.无需下载ap ...

  9. python-闭包和装饰器-02-装饰器(decorator)

    装饰器(decorator) 理解了上一章的闭包之后,装饰器就是闭包的一种应用,只是外部函数的参数传入的不是普通的变量类型,而是传入一个函数名.装饰器一般用于:不修改被装饰函数(即外部函数传入的参数) ...

  10. vue图片上传的简单组件

    <template> <div class="rili" id="rili"> <div class="updel&qu ...