一、定义

不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样就可以将该对象恢复到原先保存的状态

二、结构

  备忘录模式中主要有三类角色:

  • 发起人角色:记录当前时刻的内部状态,负责创建和恢复备忘录数据。
  • 备忘录角色:负责存储发起人对象的内部状态,在进行恢复时提供给发起人需要的状态。
  • 管理者角色:负责保存备忘录对象,但是不能对备忘录对象的内容进行操作或检查。

三、适用场景

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

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

四、优缺点

优点:

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

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

缺点:

消耗资源。

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

2、由于备份的信息是由发起人自己提供的,所以管理者无法预知备份的信息的大小,存在一定的未知风险。

五、实现

下面以备份手机通讯录为例子来实现了备忘录模式,具体的实现代码如下所示:

单次备份

// 联系人
public class ContactPerson
{
public string Name { get; set; }
public string MobileNum { get; set; }
} // 发起人
public class MobileOwner
{
// 发起人需要保存的内部状态
public List<ContactPerson> ContactPersons { get; set; } public MobileOwner(List<ContactPerson> persons)
{
ContactPersons = persons;
} // 创建备忘录,将当期要保存的联系人列表导入到备忘录中
public ContactMemento CreateMemento()
{
// 这里也应该传递深拷贝,new List方式传递的是浅拷贝,
// 因为ContactPerson类中都是string类型,所以这里new list方式对ContactPerson对象执行了深拷贝
// 如果ContactPerson包括非string的引用类型就会有问题,所以这里也应该用序列化传递深拷贝
return new ContactMemento(new List<ContactPerson>(this.ContactPersons));
} // 将备忘录中的数据备份导入到联系人列表中
public void RestoreMemento(ContactMemento memento)
{
// 下面这种方式是错误的,因为这样传递的是引用,
// 则删除一次可以恢复,但恢复之后再删除的话就恢复不了.
// 所以应该传递contactPersonBack的深拷贝,深拷贝可以使用序列化来完成
this.ContactPersons = memento.contactPersonBack;
} public void Show()
{
Console.WriteLine("联系人列表中有{0}个人,他们是:", ContactPersons.Count);
foreach (ContactPerson p in ContactPersons)
{
Console.WriteLine("姓名: {0} 号码为: {1}", p.Name, p.MobileNum);
}
}
} // 备忘录
public class ContactMemento
{
// 保存发起人的内部状态
public List<ContactPerson> contactPersonBack; public ContactMemento(List<ContactPerson> persons)
{
contactPersonBack = persons;
}
} // 管理角色
public class Caretaker
{
public ContactMemento ContactM { get; set; }
} class Program
{
static void Main(string[] args)
{
List<ContactPerson> persons = new List<ContactPerson>()
{
new ContactPerson() { Name= "Learning Hard", MobileNum = ""},
new ContactPerson() { Name = "Tony", MobileNum = ""},
new ContactPerson() { Name = "Jock", MobileNum = ""}
};
MobileOwner mobileOwner = new MobileOwner(persons);
mobileOwner.Show(); // 创建备忘录并保存备忘录对象
Caretaker caretaker = new Caretaker();
caretaker.ContactM = mobileOwner.CreateMemento(); // 更改发起人联系人列表
Console.WriteLine("----移除最后一个联系人--------");
mobileOwner.ContactPersons.RemoveAt();
mobileOwner.Show(); // 恢复到原始状态
Console.WriteLine("-------恢复联系人列表------");
mobileOwner.RestoreMemento(caretaker.ContactM);
mobileOwner.Show(); Console.Read();
}
}

多次备份

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks; namespace DesignPatterns.Mememto
{
// 联系人
public class ContactPerson
{
public string Name { get; set; }
public string MobileNum { get; set; }
} // 发起人
public class MobileOwner
{
public List<ContactPerson> ContactPersons { get; set; }
public MobileOwner(List<ContactPerson> persons)
{
ContactPersons = persons;
} // 创建备忘录,将当期要保存的联系人列表导入到备忘录中
public ContactMemento CreateMemento()
{
// 这里也应该传递深拷贝,new List方式传递的是浅拷贝,
// 因为ContactPerson类中都是string类型,所以这里new list方式对ContactPerson对象执行了深拷贝
// 如果ContactPerson包括非string的引用类型就会有问题,所以这里也应该用序列化传递深拷贝
return new ContactMemento(new List<ContactPerson>(this.ContactPersons));
} // 将备忘录中的数据备份导入到联系人列表中
public void RestoreMemento(ContactMemento memento)
{
if (memento != null)
{
// 下面这种方式是错误的,因为这样传递的是引用,
// 则删除一次可以恢复,但恢复之后再删除的话就恢复不了.
// 所以应该传递contactPersonBack的深拷贝,深拷贝可以使用序列化来完成
this.ContactPersons = memento.ContactPersonBack;
}
}
public void Show()
{
Console.WriteLine("联系人列表中有{0}个人,他们是:", ContactPersons.Count);
foreach (ContactPerson p in ContactPersons)
{
Console.WriteLine("姓名: {0} 号码为: {1}", p.Name, p.MobileNum);
}
}
} // 备忘录
public class ContactMemento
{
public List<ContactPerson> ContactPersonBack { get; set; }
public ContactMemento(List<ContactPerson> persons)
{
ContactPersonBack = persons;
}
} // 管理角色
public class Caretaker
{
// 使用多个备忘录来存储多个备份点
public Dictionary<string, ContactMemento> ContactMementoDic { get; set; }
public Caretaker()
{
ContactMementoDic = new Dictionary<string, ContactMemento>();
}
} class Program
{
static void Main(string[] args)
{
List<ContactPerson> persons = new List<ContactPerson>()
{
new ContactPerson() { Name= "Learning Hard", MobileNum = ""},
new ContactPerson() { Name = "Tony", MobileNum = ""},
new ContactPerson() { Name = "Jock", MobileNum = ""}
}; MobileOwner mobileOwner = new MobileOwner(persons);
mobileOwner.Show(); // 创建备忘录并保存备忘录对象
Caretaker caretaker = new Caretaker();
caretaker.ContactMementoDic.Add(DateTime.Now.ToString(), mobileOwner.CreateMemento()); // 更改发起人联系人列表
Console.WriteLine("----移除最后一个联系人--------");
mobileOwner.ContactPersons.RemoveAt();
mobileOwner.Show(); // 创建第二个备份
Thread.Sleep();
caretaker.ContactMementoDic.Add(DateTime.Now.ToString(), mobileOwner.CreateMemento()); // 恢复到原始状态
Console.WriteLine("-------恢复联系人列表,请从以下列表选择恢复的日期------");
var keyCollection = caretaker.ContactMementoDic.Keys;
foreach (string k in keyCollection)
{
Console.WriteLine("Key = {0}", k);
}
while (true)
{
Console.Write("请输入数字,按窗口的关闭键退出:"); int index = -;
try
{
index = Int32.Parse(Console.ReadLine());
}
catch
{
Console.WriteLine("输入的格式错误");
continue;
} ContactMemento contactMentor = null;
if (index < keyCollection.Count && caretaker.ContactMementoDic.TryGetValue(keyCollection.ElementAt(index), out contactMentor))
{
mobileOwner.RestoreMemento(contactMentor);
mobileOwner.Show();
}
else
{
Console.WriteLine("输入的索引大于集合长度!");
}
}
}
}
}

参考

http://www.cnblogs.com/JsonShare/p/7283972.html

http://www.runoob.com/design-pattern/memento-pattern.html

http://www.cnblogs.com/zhili/p/MementoPattern.html

欢迎阅读本系列文章:Head First设计模式之目录

Head First设计模式之备忘录模式的更多相关文章

  1. 乐在其中设计模式(C#) - 备忘录模式(Memento Pattern)

    原文:乐在其中设计模式(C#) - 备忘录模式(Memento Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 备忘录模式(Memento Pattern) 作者:webabc ...

  2. 折腾Java设计模式之备忘录模式

    原文地址:折腾Java设计模式之备忘录模式 备忘录模式 Without violating encapsulation, capture and externalize an object's int ...

  3. C#设计模式:备忘录模式(Memento Pattern)

    一,C#设计模式:备忘录模式(Memento Pattern) 1.发起人角色(Originator):记录当前时刻的内部状态,负责创建和恢复备忘录数据.负责创建一个备忘录Memento,用以记录当前 ...

  4. js设计模式——7.备忘录模式

    js设计模式——7.备忘录模式 /*js设计模式——备忘录模式*/ // 备忘类 class Memento { constructor(content) { this.content = conte ...

  5. java设计模式之备忘录模式

    备忘录模式 备忘录模式是一种软件设计模式:在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可将该对象恢复到原先保存的状态.一听到备忘录这个字的时候想起了小小时打的游 ...

  6. 【GOF23设计模式】备忘录模式

    来源:http://www.bjsxt.com/ 一.[GOF23设计模式]_备忘录模式.多点备忘.事务操作.回滚数据底层架构 package com.test.memento; /** * 源发器类 ...

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

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

  8. 再起航,我的学习笔记之JavaScript设计模式24(备忘录模式)

    备忘录模式 概念介绍 备忘录模式(Memento): 在不破坏对象的封装性的前提下,在对象之外捕获并保存该对象内部的状态以便日后对象使用或者对象恢复到以前的某个状态. 简易分页 在一般情况下我们需要做 ...

  9. 【Unity与23种设计模式】备忘录模式(Memento)

    GoF中定义: "在不违反封装的原则下,获取一个对象的内部状态并保留在外部,让对象可以在日后恢复到原先保留时的状态." 对于一些需要存储的数据,比如历史最高分 当与得分减分系统写入 ...

随机推荐

  1. calc() ---一个会计算的css属性

    最近这个月一直在赶项目开发,遇到的问题和学到的前端知识没有更新到博客园,现在闲了下来,就整理一下前端知识. 在项目开发中,在样式这方面花费的时间较多,因为针对于数字的变化特别多,本人不爱记数字,在看设 ...

  2. 响应式框架Bootstrap栅格系统

    <!DOCTYPE html><html><head lang="en">    <meta charset="UTF-8&qu ...

  3. Python爬虫(十)_正则表达式

    本篇将介绍python正则表达式,更多内容请参考:[python正则表达式] 什么是正则表达式 正则表达式,又称规则表达式,通常被用来检索.替换那些符合某个模式(规则)的文本. 正则表达式是对字符串操 ...

  4. Linux定义变量的脚本

    现有两段基本一样的代码,只是变量进行改变,其他都没有变化,但是执行过程中出现了不一样的结果 代码一: vi back.sh #backup import file,such as /etc/rc.lo ...

  5. JDK与JRE的关系

    JDK  =   JRE + Java语言 + 工具及工具API JRE  =    程序部署发布 + 用户界面工作集 + 集成库 + 其他基础库 + 语言和工具基础库 + Java虚拟机 简单讲:J ...

  6. openstack初始化Glance数据库时报错解决方式

    环境为win7+virtualbox 中的centos6.5 安装Glance 的包 yum install openstack-glance python-glanceclient -y 配置Gla ...

  7. ThoughtWorks 2017技术雷达

    前言: ThoughtWorks人酷爱技术.我们对技术进行构建.研究. 测试.开源.记述,并始终致力于对其进行改进-以求造福 大众.我们的使命是支持卓越软件并掀起IT革命.我们创建 并分享Though ...

  8. 【转载】JAVA中综合接口和抽象类实现的一种“抽象接口”

    Muscleape个人总结:(这里的抽象接口是指:使用一个抽象类实现一个接口,是两部分结构) 使用一个抽象类直接实现接口,将接口中的方法区分为实现类必须要实现的和选择性实现的,其他需要实现接口的类型通 ...

  9. Java Web Session设置

    一.前言 在做 java web项目时,我们很多时候都要用到 Session,那么我就简单的写一下 Session 的写法. 二.代码实现 Servlet Session 的设置 package co ...

  10. 使用Flink时从Kafka中读取Array[Byte]类型的Schema

    使用Flink时,如果从Kafka中读取输入流,默认提供的是String类型的Schema: val myConsumer = new FlinkKafkaConsumer08[String](&qu ...