C#设计模式之18-备忘录模式
备忘录模式(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、提供一个可回滚的操作。
最新文章
- 解析大型.NET ERP系统 设计异常处理模块
- C#设置输入框只输入数字
- IE 中单元格的 colspan 属性在某些情况下会影响 TABLE 元素的自动布局
- ZeroMQ接口函数之 :zmq_msg_send – 从一个socket发送一个消息帧
- 在Ubuntu14.04安装torch7笔记
- Mac怎么读写NTFS格式?Mac读写NTFS格式硬盘教程
- Spring3 整合Hibernate3.5 动态切换SessionFactory (切换数据库方言)
- SGU 170	Particles(规律题)
- Struts2的配置
- Windows Azure 实操 —— 迁移本地SharePoint服务器到Azure
- (图解版)SQL Server数据库备份与还原
- Linux Kernel Makefile Test
- spring mvc的跨域解决方案
- linux kvm虚拟机快速构建及磁盘类型
- 洛谷P4204 [NOI2006]神奇口袋 数论
- MySQL正则表达式 REGEXP详解
- <;泛>; C++3D数学库设计详解 简单光学几何 &;&; 随机向量生成
- Query实例的ajax应用之二级联动的后台是采用php来做的
- [转载]查询json数据结构的8种方式
- 解决phpexcel保存时文件命中文出现 乱码 (这个真的有用)
热门文章
- Ethical Hacking - POST EXPLOITATION(1)
- 洛谷 P5350 序列 珂朵莉树
- vue : 检测用户上传的图片的宽高
- 使用ImpromptuInterface反射库方便的创建自定义DfaGraphWriter
- windows如何解决Error: Registry key &#39;Software\JavaSoft\Java Runtime Environment&#39;\CurrentVersion&#39;
- justoj connect(边的处理)
- web自动化 -- Keys(键盘操作)
- Java基础单词总结
- iOS 高效灵活地配置可复用视图组件的主题
- Python os.stat_float_times() 方法