本笔记摘抄自:https://www.cnblogs.com/PatrickLiu/p/8176974.html,记录一下学习过程以备后续查用。

一、引言

今天我们要讲行为型设计模式的第十个模式--备忘录模式,先从名称上来看。备忘录模式可以理解为对某个对象的状态进行保存,等到需要恢复的时

候,可以从备忘录中进行恢复。生活中这样的例子也能经常看到,如备份电话通讯录、操作系统、数据库等。如果我们想恢复对象的状态,那么我们可

能首先想到的是把对象保存下来,但是这样会破坏对象的封装性。因为对象有状态有操作,如果我们为了保存状态而留着原来的对象,做一个深拷贝,

那么其它对象也能通过这个对象的接口来访问这个对象状态,这并不是我们所希望的。我们需要它的职责只是保存和恢复对象状态,而不应在上面支持

对对象状态访问的接口,这就产生了Memento模式。

看上图,一个对象会有很多状态。这些状态会相互转变而促进对象的发展,如果要想在某一时刻把当前对象恢复到以前某一时刻的状态,这个情况用

“备忘录模式”就能很好解决。

    二、备忘录模式介绍

备忘录模式:英文名称--Memento Pattern;分类--行为型。

二、备忘录模式的详细介绍

2.1、动机(Motivate)

在软件构建过程中,某些对象的状态在转换的过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接

口来让其它对象得到对象的状态,便会暴露对象的细节实现。如何实现对象状态的良好保存与恢复,但同时又不会因此而破坏对象本身的封装性?

2.2、意图(Intent)

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态(如果没有这个关键点,其实深拷贝就可以解决问题)。这样以

后就可以将该对象恢复到原先保存的状态。——《设计模式》GoF

2.3、结构图(Structure)

2.4、模式的组成

可以看出,在备忘录模式的结构图有以下角色:

1)发起人角色(Originator):记录当前时刻的内部状态,负责创建和恢复备忘录数据。负责创建一个备忘录Memento,用以记录当前时刻自身的内

部状态,并可使用备忘录恢复内部状态。Originator(发起人)可以根据需要决定Memento(备忘录)存储自己的哪些内部状态。

2)备忘录角色(Memento):负责存储发起人对象的内部状态,在进行恢复时提供给发起人需要的状态,并可以防止Originator以外的其它对象访问

备忘录。备忘录有两个接口:Caretaker(管理角色)只能看到备忘录的窄接口,它只能将备忘录传递给其它对象。Originator(发起人)却可看到备忘录的宽

接口,允许它访问返回到先前状态时所需要的所有数据。

3)管理者角色(Caretaker):负责保存备忘录对象。负责备忘录Memento,不能对Memento的内容进行访问或者操作。

2.5、备忘录模式的具体实现

今天我们就用备份电话本的实例来说明备忘录模式的实现,实现代码如下:

    class Program
{
/// <summary>
/// 联系人--需要备份的数据,是状态数据,没有操作。
/// </summary>
public sealed class ContactPerson
{
//姓名
public string Name { get; set; } //电话号码
public string MobileNumber { get; set; }
} /// <summary>
/// 发起人--相当于发起人角色
/// </summary>
public sealed class MobileBackOriginator
{
//发起人需要保存的内部状态
public List<ContactPerson> ContactPersonList { get; set; } //初始化需要备份的电话名单
public MobileBackOriginator(List<ContactPerson> contactPeopleList)
{
if (contactPeopleList != null)
{
ContactPersonList = contactPeopleList;
}
else
{
throw new ArgumentNullException("参数不能为空。");
}
} //创建备忘录对象实例,将当期要保存的联系人列表保存到备忘录对象中。
public ContactPersonMemento CreateMemento()
{
return new ContactPersonMemento(new List<ContactPerson>(ContactPersonList));
} //将备忘录中的数据备份还原到联系人列表中
public void RestoreMemento(ContactPersonMemento memento)
{
ContactPersonList = memento.ContactPersonListBack;
} public void Show()
{
Console.WriteLine("联系人列表中共有{0}个人,他们分别是:", ContactPersonList.Count);
foreach (ContactPerson p in ContactPersonList)
{
Console.WriteLine("姓名: {0} 号码: {1}", p.Name, p.MobileNumber);
}
}
} /// <summary>
/// 备忘录对象,用于保存状态数据,保存的是当时对象具体状态数据--相当于备忘录角色
/// </summary>
public sealed class ContactPersonMemento
{
//保存发起人创建的电话名单数据,就是所谓的状态。
public List<ContactPerson> ContactPersonListBack { get; private set; } public ContactPersonMemento(List<ContactPerson> contactPeopleList)
{
ContactPersonListBack = contactPeopleList;
}
} /// <summary>
/// 管理角色,它可以管理备忘录对象。如果是保存多个备忘录对象,可以对其进行增、删除等处理--相当于管理者角色
/// </summary>
public sealed class MementoManager
{
//如果想保存多个备忘录对象,可以通过字典或者堆栈来保存,堆栈对象可以反映保存对象的先后顺序。
//比如:public Dictionary<string, ContactPersonMemento> ContactPersonMementoDictionary { get; set; }
public ContactPersonMemento ContactPersonMemento { get; set; }
} static void Main(string[] args)
{
#region 备忘录模式
List<ContactPerson> persons = new List<ContactPerson>()
{
new ContactPerson() { Name="黄飞鸿", MobileNumber = ""},
new ContactPerson() { Name="方世玉", MobileNumber = ""},
new ContactPerson() { Name="洪熙官", MobileNumber = ""}
}; //手机名单发起人
MobileBackOriginator mobileOriginator = new MobileBackOriginator(persons);
mobileOriginator.Show();
Console.WriteLine(); //创建备忘录并保存备忘录对象
MementoManager manager = new MementoManager
{
ContactPersonMemento = mobileOriginator.CreateMemento()
}; //更改发起人联系人列表
Console.WriteLine("移除最后一个联系人。");
mobileOriginator.ContactPersonList.RemoveAt();
mobileOriginator.Show();
Console.WriteLine(); //恢复到原始状态
Console.WriteLine("恢复联系人列表。");
mobileOriginator.RestoreMemento(manager.ContactPersonMemento);
mobileOriginator.Show(); Console.Read();
#endregion
}
}

运行结果如下:

三、备忘录模式的实现要点

备忘录(Memento)存储原发器(Originator)对象的内部状态,在需要时恢复原发器状态。Memento模式适用于“由原发器管理,却又必须存储在原

发器之外的信息”。

在实现Memento模式中,要防止原发器以外的对象访问备忘录对象。备忘录对象有两个接口,一个为原发器使用的宽接口;一个为其他对象使用的窄

接口。在实现Memento模式时,要考虑拷贝对象状态的效率问题,如果对象开销比较大,可以采用某种增量式改变来改进Memento模式。

我们也可以用序列化的方式实现备忘录。序列化之后,我们可以把它临时性保存到数据库、文件、进程内、进程外等地方。

3.1、备忘录模式的主要优点

1)如果某个操作错误地破坏了数据的完整性,此时可以使用备忘录模式将数据恢复成原来正确的数据。

2)备份的状态数据保存在发起人角色之外,这样发起人就不需要对各个备份的状态进行管理,而是由备忘录角色进行管理,而备忘录角色又是由管理

者角色管理,符合单一职责原则。

3)提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤。当新的状态无效或者存在问题时,可以使用之前存储起来的备忘

录将状态复原。

4)实现了信息的封装,一个备忘录对象是一种原发器对象的表示,不会被其它代码改动。这种模式简化了原发器对象,备忘录只保存原发器的状态,

采用堆栈来存储备忘录对象可以实现多次撤销操作,可以通过在管理者角色中定义集合对象来存储多个备忘录。

5)本模式简化了发起人类。发起人不再需要管理和保存其内部状态的一个个版本,客户端可以自行管理他们所需要的这些状态的版本。

6)当发起人角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原。

 3.2、备忘录模式的主要缺点

1)在实际的系统中,可能需要维护多个备份,需要额外的资源,这样对资源的消耗比较严重。如果类的成员变量太多,就不可避免占用大量的内存,而

且每保存一次对象的状态都需要消耗内存。如果知道这一点,大家就容易理解为什么一些提供了撤销功能的软件在运行时所需的内存和硬盘空间比较大了。

2)如果发起人角色的状态需要完整地存储到备忘录对象中,那么在资源消耗上面备忘录对象会很昂贵。

3)当管理者角色将一个备忘录存储起来的时候,管理者可能并不知道这个状态会占用多大的存储空间,从而无法提醒用户一个操作是否很昂贵。

4)当发起人角色的状态改变的时候,有可能这个协议无效。

3.3、在下面的情况下可以考虑使用备忘录模式

1)如果系统需要提供回滚操作时,使用备忘录模式非常合适。例如文本编辑器的Ctrl+Z撤销操作、数据库中事务操作。

2)保存一个对象在某一个时刻的状态或部分状态,这样以后需要时它能够恢复到之前的状态。

3)如果用一个接口来让其它对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。一个对象不希望外界直接访问其内部状态,通过管理者

可以间接访问其内部状态。

4)有时一些发起人对象的内部信息必须保存在发起人对象以外的地方,但是必须要由发起人对象自己读取。这时,使用备忘录模式可以把复杂的发起人

内部信息对其它的对象屏蔽起来,从而可以恰当地保持封装的边界。

3.4、备忘录的封装性

1)为了确保备忘录的封装性,除了原发器外,其它类是不能也不应该访问备忘录类。在实际开发中,原发器与备忘录之间的关系是非常特殊的,它们要

分享信息而不让其它类知道,实现的方法因编程语言的不同而不同。

3.5、多备份实现

1)在管理者中定义一个集合对象来存储多个状态,而且可以方便地返回到某一历史状态。

2)在备份对象时可以做一些记号,这些记号称为检查点(Check Point),在使用HashMap等实现时可以使用Key来设置检查点。

四、.NET中备忘录模式的实现

在现在的.Net框架里面,还没有找到备忘录模式的实现,看来还是自己的功力不够,还需努力。个人的理解,这种模式似乎在业务系统里面使用的更多,

类似Word、Excel等工具可以有撤销功能。其实很多软件都有这个功能,软件执行的时候,时时刻刻在把自己的状态存储,如果发生错误,或者需要撤销的

时候就可以进行相关的操作。

五、总结

备忘录模式刚开始理解起来还是挺麻烦的,但是,如果我们多看几个实例代码,完全掌握也不是问题。我们是不是感觉Memento模式和Command模式有

些类似?我们要仔细把握模式之间的异同,否则使用模式的时候就会出现张冠李戴的情况或者不能确定使用哪个模式好。Memento备忘录模式和Command

命令模式其实还是有些细微的差别的,那就让我们来看看它们的异同:虽然两者都支持Undo操作,但是Command是对行为的封装,Memento是对对象状态

的保留,这是目的上的不同;它们支持的也是Undo操作的不同层面,Command是对行为序列的操作,Memento是对行为状态的操作;命令模式保存的是发

起人的具体命令(命令对应的是行为),而备忘录模式保存的是发起人的状态(而状态对应的数据结构,如属性)。

把握细节,理解模式的应用场景,这样可以让模式更好的为我们服务。

C#设计模式学习笔记:(22)备忘录模式的更多相关文章

  1. 设计模式学习笔记-Adapter模式

    Adapter模式,就是适配器模式,使两个原本没有关联的类结合一起使用. 平时我们会经常碰到这样的情况,有了两个现成的类,它们之间没有什么联系,但是我们现在既想用其中一个类的方法,同时也想用另外一个类 ...

  2. Java-马士兵设计模式学习笔记-装饰者模式

    Java装饰者模式简介 一.假设有一个Worker接口,它有一个doSomething方法,Plumber和Carpenter都实现了Worker接口,代码及关系如下: 1.Worker.java p ...

  3. 研磨设计模式学习笔记2--外观模式Facade

    需求:客户端需要按照需求,执行一个操作,操作包括一个系统中的3个模块(根据配置选择是否全部执行). 外观模式优点: 客户端无需知道系统内部实现,,只需要写好配置文件,控制那些模块执行,简单易用. 外观 ...

  4. 设计模式学习笔记 1.factory 模式

    Factory 模式 用户不关心工厂的具体类型,只知道这是一个工厂就行. 通过工厂的实现推迟到子类里面去来确定工厂的具体类型. 工厂的具体类型来确定生产的具体产品. 同时用户不关心这是一个什么样子的产 ...

  5. 设计模式学习笔记——Composite 组合模式

    用于描述无限层级的复杂对象,类似于描述资源管理器,抽象出每一个层级的共同特点(文件夹和文件,展开事件) 以前描述一个对象,是将整个对象的全部数据都描述清楚,而组合模式通过在对象中定义自己,描述自己的下 ...

  6. 设计模式学习笔记——Bridge 桥接模式

    先说一下我以前对桥接模式的理解:当每个类中都使用到了同样的属性或方法时,应该将他们单独抽象出来,变成这些类的属性和方法(避免重复造轮子),当时的感觉是和三层模型中的model有点单相似,也就是让mod ...

  7. 设计模式学习笔记——Visitor 访问者模式

    1.定义IVisitor接口,确定变化所涉及的方法 2.封装变化类.实现IVisitor接口 3.在实体类的变化方法中传入IVisitor接口,由接口确定使用哪一种变化来实现(封装变化) 4.在使用时 ...

  8. Java-马士兵设计模式学习笔记-责任链模式-FilterChain功能

    一.目标 增加filterchain功能 二.代码 1.Filter.java public interface Filter { public String doFilter(String str) ...

  9. Java-马士兵设计模式学习笔记-责任链模式-处理数据

    一.目标 数据提交前做各种处理 二.代码 1.MsgProcessor.java public class MsgProcessor { private List<Filter> filt ...

  10. Java-马士兵设计模式学习笔记-责任链模式-模拟处理Reques Response

    一.目标 1.用Filter模拟处理Request.Response 2.思路细节技巧: (1)Filter的doFilter方法改为doFilter(Request,Resopnse,FilterC ...

随机推荐

  1. 客户端TNSPING通 连接出现ORA-12514错误

    ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务,这是一个经常遇到的问题,可以按照以下步骤一步步解决 1.使用tnsping检测 tnsping可判断出以下两点(1)判断网络 ...

  2. 最强PostMan使用教程

    最近需要测试产品中的REST API,无意中发现了PostMan这个chrome插件,把玩了一下,发现postman秉承了一贯以来google工具强大,易用的特质.独乐乐不如众乐乐,特此共享出来给大伙 ...

  3. 英语学习app——Alpha发布2

    英语学习app--Alpha发布1 这个作业属这个作业属于哪个课程 https://edu.cnblogs.com/campus/xnsy/GeographicInformationScience/ ...

  4. Python学习,第三课 - 数据类型

    前言. 本次针对Python中的数据类型,做详细的总结 1.数字 2 是一个整数的例子. 长整数 不过是大一些的整数. 3.23和52.3E-4是浮点数的例子.E标记表示10的幂.在这里,52.3E- ...

  5. Kdenlive-开始

    版权声明:原创文章,未经博主允许不得转载 这是 Kdenlive 系列文章的第一篇 说明 在 Linux 下的视频编辑的软件并不多,作为其中之一的 kdenlive 在网上的教程就更少了.于是自己琢磨 ...

  6. tcpdump用法说明

    tcpdump采用命令行方式对接口的数据包进行筛选抓取,其丰富特性表现在灵活的表达式上. 不带任何选项的tcpdump,默认会抓取第一个网络接口,且只有将tcpdump进程终止才会停止抓包. 例如: ...

  7. Mysql Innodb cluster集群搭建

    之前搭建过一个Mysql Ndb cluster集群,但是mysql版本是5.7的,看到官网上mysql8的还是开发者版本,所以尝试搭建下mysql Innodb cluster集群. MySQL的高 ...

  8. Unreal Engine 4 蓝图完全学习教程(四)—— 变量与计算

    Ⅰ.值的基础类型 ①文本.字符串(Text.String):文本类型的值. ②整型.浮点型(Int.Float):数字类型的值. ③布尔型(Bool):表示“真或假”二者选其一的状态. Ⅱ.加法运算 ...

  9. 浅谈C语言的数据存储(一)

    作者:冯老师,华清远见嵌入式学院讲师. 程序由指令和数据组成,C语言程序亦是如此.开发者在编写程序的时候往往需要根据不同数据的特点以及程序需求来选择不同的数据存储方式,那么在C语言中数据的存储分为哪些 ...

  10. 教你5分钟做个手机APP[视频]

    天天宅在家里,没什么事做,录个教学视频吧! 发到了视频网站上去根本没人看,伤心ing啊! 不知cnblogs上面是否让我发! 先上一张效果图看看哈: 如果播放不正常请点这里:https://www.b ...