一、动机(Motivate)

我们看上图,一个对象肯定会有很多状态,这些状态肯定会相互转变而促进对象的发展,如果要想在某一时刻把当前对象回复到以前某一时刻的状态,这个情况用“备忘录模式”就能很好解决该问题。

在软件构建过程中,某些对象的状态在转换的过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。
  如何实现对象状态的良好保存与恢复,但同时又不会因此而破坏对象本身的封装性?

二、意图(Intent)

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态(如果没有这个关键点,其实深拷贝就可以解决问题)。这样以后就可以将该对象恢复到原先保存的状态。                                 ——《设计模式》GoF

三、结构图(Structure)



四、模式的组成

可以看出,在备忘录模式的结构图有以下角色:
(1)、发起人角色(Originator):记录当前时刻的内部状态,负责创建和恢复备忘录数据。负责创建一个备忘录Memento,用以记录当前时刻自身的内部状态,并可使用备忘录恢复内部状态。Originator【发起人】可以根据需要决定Memento【备忘录】存储自己的哪些内部状态。
(2)、备忘录角色(Memento):负责存储发起人对象的内部状态,在进行恢复时提供给发起人需要的状态,并可以防止Originator以外的其他对象访问备忘录。备忘录有两个接口:Caretaker【管理角色】只能看到备忘录的窄接口,他只能将备忘录传递给其他对象。Originator【发起人】却可看到备忘录的宽接口,允许它访问返回到先前状态所需要的所有数据。
(3)、管理者角色(Caretaker):负责保存备忘录对象。负责备忘录Memento,不能对Memento的内容进行访问或者操作。

五、备忘录模式的代码实现

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

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

六、备忘录模式的实现要点:

备忘录(Memento)存储原发器(Originator)对象的内部状态,在需要时恢复原发器状态。Memento模式适用于“由原发器管理,却又必须存储在原发器之外的信息”。
  在实现Memento模式中,要防止原发器以外的对象访问备忘录对象。备忘录对象有两个接口,一个为原发器使用的宽接口;一个为其他对象使用的窄接口。在实现Memento模式时,要考虑拷贝对象状态的效率问题,如果对象开销比较大,可以采用某种增量式改变(即只记住改变的状态)来改进Memento模式。
  我们也可以用序列化的方式实现备忘录。序列化之后,我们可以把它临时性保存到数据库、文件、进程内、进程外等地方。

1、备忘录模式的主要优点有:

1】、如果某个操作错误地破坏了数据的完整性,此时可以使用备忘录模式将数据恢复成原来正确的数据。
            2】、备份的状态数据保存在发起人角色之外,这样发起人就不需要对各个备份的状态进行管理。而是由备忘录角色进行管理,而备忘录角色又是由管理者角色管理,符合单一职责原则。
            3】、提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用先前存储起来的备忘录将状态复原。
            4】、实现了信息的封装,一个备忘录对象是一种原发器对象的表示,不会被其他代码改动,这种模式简化了原发器对象,备忘录只保存原发器的状态,采用堆栈来存储备忘录对象可以实现多次撤销操作,可以通过在负责人中定义集合对象来存储多个备忘录。
            5】、本模式简化了发起人类。发起人不再需要管理和保存其内部状态的一个个版本,客户端可以自行管理他们所需要的这些状态的版本。
            6】、当发起人角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原。

2、备忘录模式的主要缺点有:

1】、在实际的系统中,可能需要维护多个备份,需要额外的资源,这样对资源的消耗比较严重。资源消耗过大,如果类的成员变量太多,就不可避免占用大量的内存,而且每保存一次对象的状态都需要消耗内存资源,如果知道这一点大家就容易理解为什么一些提供了撤销功能的软件在运行时所需的内存和硬盘空间比较大了。
            2】、如果发起人角色的状态需要完整地存储到备忘录对象中,那么在资源消耗上面备忘录对象会很昂贵。
     3】、当负责人角色将一个备忘录 存储起来的时候,负责人可能并不知道这个状态会占用多大的存储空间,从而无法提醒用户一个操作是否很昂贵。
     4】、当发起人角色的状态改变的时候,有可能这个协议无效。如果状态改变的成功率不高的话,不如采取“假如”协议模式。

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

1】、如果系统需要提供回滚操作时,使用备忘录模式非常合适。例如文本编辑器的Ctrl+Z撤销操作的实现,数据库中事务操作。
          2】、保存一个对象在某一个时刻的状态或部分状态,这样以后需要时它能够恢复到先前的状态。
          3】、如果用一个接口来让其他对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过负责人可以间接访问其内部状态。
          4】、有时一些发起人对象的内部信息必须保存在发起人对象以外的地方,但是必须要由发起人对象自己读取,这时,使用备忘录模式可以把复杂的发起人内部信息对其他的对象屏蔽起来,从而可以恰当地保持封装的边界。

4、备忘录的封装性

1】、为了确保备忘录的封装性,除了原发器外,其他类是不能也不应该访问备忘录类的,在实际开发中,原发器与备忘录之间的关系是非常特殊的,它们要分享信息而不让其他类知道,实现的方法因编程语言的不同而不同。

5、多备份实现

1】、在负责人中定义一个集合对象来存储多个状态,而且可以方便地返回到某一历史状态。
         2】、在备份对象时可以做一些记号,这些记号称为检查点(Check Point)。在使用HashMap等实现时可以使用Key来设置检查点。

6、Memento模式与Command模式的异同

Memento备忘录模式和Command命令模式其实还是有些细微的差别的,那就让我们来看看他们的异同吧。虽然两者都支持Undo操作,但是Command是对行为的封装,Memento是对对象状态的保留,这是目的上的不同。它们支持的也是Undo操作的不同层面,Command是对行为序列的操作,Memento是对行为状态的操作。命令模式保存的是发起人的具体命令(命令对应的是行为),而备忘录模式保存的是发起人的状态(而状态对应的数据结构,如属性)。把握细节,理解模式的应用场景,这样可以让模式更好的为我们服务。

七、.NET 备忘录模式的实现

在现在的Net框架里面,还没有找到备忘录模式的实现,看来还是自己的功力不够,还需努力。个人的理解,这种模式似乎在业务系统里面使用的更多,类似Word,Excel等工具可以有撤销功能,其实很多软件都有这个功能,软件执行的时候,时时刻刻在把自己的状态存储,如果发生错误,或者需要撤销的时候就可以进行相关的操作。

行为型模式(十) 备忘录模式(Memento)的更多相关文章

  1. 设计模式20---设计模式之备忘录模式(Memento)(行为型)

    1.讲解备忘录模式 备忘录模式(Memento Pattern)又叫做快照模式(Snapshot Pattern)或Token模式,是GoF的23种设计模式之一,属于行为模式. 1.1定义 在不破坏封 ...

  2. 行为型设计模式之备忘录模式(Memento)

    结构 意图 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可将该对象恢复到原先保存的状态. 适用性 必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时 ...

  3. 设计模式---状态变化模式之备忘录模式(Memento)

    一:概念 用于保存对象的内部状态,并在需要的时候(undo/rollback)回复对象以前的状态 二:应用场景 如果一个对象需要保存状态并可通过undo或rollback等操作恢复到以前的状态时,可以 ...

  4. Java设计模式(21)——行为模式之备忘录模式(Memento)

    一.概述 概念 UML简图 角色 根据下图得到角色 备忘录角色(Memento).发起人角色(Originator).负责人角色(Caretaker) 二.实践 使用白箱实现,给出角色的代码: 发起人 ...

  5. 《Android源码设计模式》--状态模式--责任链模式--解释器模式--命令模式--观察者模式--备忘录模式--迭代器模式

    [状态模式] No1: Wifi设置界面是一个叫做WifiSetting的Fragment实现的 No2: 在不同的状态下对于扫描Wifi这个请求的处理是完全不一样的.在初始状态下扫描请求被直接忽略, ...

  6. Head First设计模式——中介者模式和备忘录模式

    中介者 中介者模式:使用中介者来集中相关对象之间复杂的沟通方式和控制方式. Bob有一个自动屋,当他的闹钟响了只有,闹钟会告诉咖啡壶开始煮咖啡.后来客户不断的提出许多新需求:周末不要提供咖啡.下班后需 ...

  7. 十一个行为模式之备忘录模式(Memento Pattern)

    定义: 在不破坏原有封装的情况下,捕获一个对象的内部状态,并在对象之外保存.当对象出错或者无效是,可以根据该备忘录进行恢复. 结构图: Originator:原发类,被记录的对象,包含若干内部状态.一 ...

  8. GoF23种设计模式之行为型模式之备忘录模式

    一.概述         在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象的外部保存这个状态.以便以后可以将该对象恢复到原先保存的状态. 二.适用性 1.当需要保存一个对象在某个时刻的状态( ...

  9. 七、备忘录模式Memento(行为型模式)

    其目的是,在不违反封装原则的前提下.採集和备份一个对象的内部状态以便这个对象能够在以后恢复到之前的某个状态. 在Memento模式中,有例如以下角色: 1.Memento (备忘录) * 存储Orig ...

随机推荐

  1. django models.update

    json/dict类型数据更新字段目前主流的web开放方式都讲究前后端分离,分离之后前后端交互的数据格式大都用通用的jason型,那么如何用最少的代码方便的更新json格式数据到数据库呢?同样可以使用 ...

  2. mysql高并发配置

    mysql高并发配置 要在mysqld下设置1 修改back_log参数值:由默认的50修改为500.(每个连接256kb,占用:125M)back_log=500<pre> back_l ...

  3. TP5 模型CURD

    ThinkPHP5的模型是一种对象-关系映射(Object / Relation Mapping ,简称 ORM)的封装,并且提供了简洁的ActiveRecord实现.一般来说,每个数据表会和一个“模 ...

  4. LeetCode 946. 验证栈序列(Validate Stack Sequences) 26

    946. 验证栈序列 946. Validate Stack Sequences 题目描述 Given two sequences pushed and popped with distinct va ...

  5. linux命令行模式与图形界面切换

    1.实时切换 1.1 命令行->图形 执行startx命令 1.2 图形->命令行 Ctrl+Alt+F1--F6 2.启动默认 2.1 启动进入命令行 修改/etc/inittab文件 ...

  6. DS 壹之型 头指针与头结点

    之前结合网上博客整理的笔记,希望能帮你解除疑惑!    

  7. flink ETL数据处理

    Flink ETL 实现数据清洗   一:需求(针对算法产生的日志数据进行清洗拆分) 1. 算法产生的日志数据是嵌套json格式,需要拆分 2.针对算法中的国家字段进行大区转换 3.最后把不同类型的日 ...

  8. dubbo中使用动态代理

    dubbo的动态代理也是只能代理接口 源码入口在JavassistProxyFactory中 public class JavassistProxyFactory extends AbstractPr ...

  9. quartz2.3.0(十三)数据库持久化定时器job任务和trigger触发器,在多个调度器实例情况下,由其它调度器实例恢复执行调度器宕机的job任务

    一.初始化数据库11张quartz表:qrtz_*   先从官网下载好quartz2.3.0包:http://www.quartz-scheduler.org/downloads/ 解压后进入目录:q ...

  10. [洛谷P5340][TJOI2019]大中锋的游乐场

    题目大意:有$n(n\leqslant10^4)$个点,$m(m\leqslant10^5)$条边的无向图,每个点有一个属性$A/B$,要求$|cnt_A-cnt_B|\leqslant k(k\le ...