备忘录模式(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、提供一个可回滚的操作。

最新文章

  1. 解析大型.NET ERP系统 设计异常处理模块
  2. C#设置输入框只输入数字
  3. IE 中单元格的 colspan 属性在某些情况下会影响 TABLE 元素的自动布局
  4. ZeroMQ接口函数之 :zmq_msg_send – 从一个socket发送一个消息帧
  5. 在Ubuntu14.04安装torch7笔记
  6. Mac怎么读写NTFS格式?Mac读写NTFS格式硬盘教程
  7. Spring3 整合Hibernate3.5 动态切换SessionFactory (切换数据库方言)
  8. SGU 170 Particles(规律题)
  9. Struts2的配置
  10. Windows Azure 实操 —— 迁移本地SharePoint服务器到Azure
  11. (图解版)SQL Server数据库备份与还原
  12. Linux Kernel Makefile Test
  13. spring mvc的跨域解决方案
  14. linux kvm虚拟机快速构建及磁盘类型
  15. 洛谷P4204 [NOI2006]神奇口袋 数论
  16. MySQL正则表达式 REGEXP详解
  17. &lt;泛&gt; C++3D数学库设计详解 简单光学几何 &amp;&amp; 随机向量生成
  18. Query实例的ajax应用之二级联动的后台是采用php来做的
  19. [转载]查询json数据结构的8种方式
  20. 解决phpexcel保存时文件命中文出现 乱码 (这个真的有用)

热门文章

  1. Ethical Hacking - POST EXPLOITATION(1)
  2. 洛谷 P5350 序列 珂朵莉树
  3. vue : 检测用户上传的图片的宽高
  4. 使用ImpromptuInterface反射库方便的创建自定义DfaGraphWriter
  5. windows如何解决Error: Registry key &#39;Software\JavaSoft\Java Runtime Environment&#39;\CurrentVersion&#39;
  6. justoj connect(边的处理)
  7. web自动化 -- Keys(键盘操作)
  8. Java基础单词总结
  9. iOS 高效灵活地配置可复用视图组件的主题
  10. Python os.stat_float_times() 方法