前言
今天我们来看看备忘录模式【MementoPattern】,我们平时写文档的时候一不小心写错了一些字或者删除了一些东西怎么办呢?不用怕、Windows里面提供了Ctrl+Z,后退一步,可以一直后退。这个东西怎么实现的呢?我们记得之前讲过一个命令模式。命令保存的是发起人的具体命令(对应的行为)、我们今天讲的这个备忘录跟这个有点相似,但是备忘录模式保存的是发起人的状态(对应的数据结构、如属性)。我们没做一步操作就保存一步操作之前的数据。当我们Ctrl+Z后退时恢复前一步数据、似乎就达到了我们需要的目的。
备忘录模式介绍
一、来由
在软件系统中我们经常会遇到一些状态的转变。在某些时刻我们需要恢复、回溯之前的某个时间点的状态。如果我们使用一个公共的接口来使其他对象得到获取这个对象、会暴露对象封装的细节。那么我们如何在不破坏对象的封装性的同时恢复对象的某一时刻的状态呢?
二、意图
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
三、案例图

四、备忘录模式代码示例
我们看上面的案例图,主要包含以下三个部分:
发起人:发起人角色负责对状态的记录,包含创建和恢复备忘数据。
备忘录:负责储存发起人对象的状态、在恢复备忘数据的时候提供发起人需要的状态。
管理员:负责保存备忘录对象、负责备忘录对象、使其不能被其他对象进行访问及操作。
接下来我们看一个案例、关于手机照片备份的问题,有些时候因为操作失误引起的数据遗失问题。怎么去避免呢?对照片备份,然后在需要的时候进行备份数据恢复。我们看下如歌通过代码来实现吧:
- namespace Memento_Pattern
- {
- class MementoPattern
- {
- }
- #region 照片数据
-
- public class Photo
- {
- /// <summary>
- /// 名称
- /// </summary>
- public string Name { get; set; }
- /// <summary>
- /// 地址
- /// </summary>
- public string Address { get; set; }
- }
- #endregion
-
- #region 发起人角色
-
- public sealed class Sponsor
- {
- private List<Photo> _photo;
- public Sponsor(List<Photo> photos)
- {
- if (photos == null)
- throw new Exception("请传入正确的数据源!");
- this._photo = photos;
- }
- public List<Photo> GetPhotos
- {
- get { return this._photo; }
- set { this._photo = value; }
- }
- /// <summary>
- /// 创建备忘录,保存状态数据
- /// </summary>
- /// <returns></returns>
- public Memento CreateMemento()
- {
- return new Memento(new List<Photo>(this._photo));
- }
- /// <summary>
- /// 获取备忘录数据、恢复状态数据
- /// </summary>
- /// <param name="memento"></param>
- public void RestoreMemento(Memento memento)
- {
- GetPhotos = memento._mementoList;
- }
- /// <summary>
- /// 展示数据
- /// </summary>
- public void ShowPhoto()
- {
- Console.WriteLine($"目前用有照片{GetPhotos.Count}张:");
- foreach (var item in GetPhotos)
- {
- Console.WriteLine($"照片名称:{item.Name}。照片地址:{item.Address}");
- }
- }
- }
- #endregion
-
- #region 备忘录
- public sealed class Memento
- {
- public List<Photo> _mementoList { get; private set; }
- /// <summary>
- /// 初始化存储数据
- /// </summary>
- /// <param name="MementoList"></param>
- public Memento(List<Photo> MementoList)
- {
- this._mementoList = MementoList;
- }
- }
- #endregion
-
- #region 管理员
- /// <summary>
- /// 一个备忘录数据处理
- /// </summary>
- public sealed class MementoManager
- {
- public Memento memento { get; set; }
- }
- #endregion
- }
- namespace Memento_Pattern
- {
- class Program
- {
- static void Main(string[] args)
- {
- ///初始化数据
- List<Photo> photos = new List<Photo>();
- photos.Add(new Photo { Name = "第一张.jpg", Address = "https://img2018.cnblogs.com/blog/1470432/201910/11.jpg" });
- photos.Add(new Photo { Name = "第二张.jpg", Address = "https://img2018.cnblogs.com/blog/1470432/201910/22.jpg" });
- photos.Add(new Photo { Name = "第三张.jpg", Address = "https://img2018.cnblogs.com/blog/1470432/201910/33.jpg" });
- Sponsor sponsor = new Sponsor(photos);
- ///展示数据
- sponsor.ShowPhoto();
- ///保存状态数据到备忘录
- MementoManager mementoManager = new MementoManager();
- mementoManager.memento = sponsor.CreateMemento();
- ///删除一张照片
- Console.WriteLine();
- Console.WriteLine();
- photos.RemoveAt(0);
- sponsor.GetPhotos = photos;
- Console.WriteLine("删除后");
- sponsor.ShowPhoto();
- ///恢复备忘录数据
- ///
- Console.WriteLine();
- Console.WriteLine();
- sponsor.RestoreMemento(mementoManager.memento);
- Console.WriteLine("恢复后");
- sponsor.ShowPhoto();
- }
- }
- }
这里我们可以看到 对照片的备份、然后删除之后完成恢复操作。这里针对的是一个备忘录的操作。

我们看下如果我们使用备忘录进行多次状态的保存并且选择性恢复数据是如何实现的吧。
首先对管理员角色进行修改:
- /// <summary>
- /// 多个备忘录数据处理
- /// </summary>
- public sealed class MementoManagers
- {
- public Dictionary<string, Memento> mementoList { get; set; }
- public MementoManagers()
- {
- mementoList = new Dictionary<string, Memento>();
- }
- }
然后我们修改Main函数进行操作看下结果
- static void Main(string[] args)
- {
- ///初始化数据
- List<Photo> photos = new List<Photo>();
- photos.Add(new Photo { Name = "第一张.jpg", Address = "https://img2018.cnblogs.com/blog/1470432/201910/11.jpg" });
- photos.Add(new Photo { Name = "第二张.jpg", Address = "https://img2018.cnblogs.com/blog/1470432/201910/22.jpg" });
- photos.Add(new Photo { Name = "第三张.jpg", Address = "https://img2018.cnblogs.com/blog/1470432/201910/33.jpg" });
- Sponsor sponsor = new Sponsor(photos);
- ///展示数据
- sponsor.ShowPhoto();
- ///保存状态数据到备忘录
- MementoManagers mementoManagers = new MementoManagers();
- mementoManagers.mementoList.Add("1", sponsor.CreateMemento());
- ///删除一张照片
- Console.WriteLine();
- Console.WriteLine();
- photos.RemoveAt(0);
- sponsor.GetPhotos = photos;
- Console.WriteLine("删除后");
- sponsor.ShowPhoto();
- mementoManagers.mementoList.Add("2", sponsor.CreateMemento());
- ///恢复备忘录数据
- ///
- while (true)
- {
- Console.WriteLine();
- Console.WriteLine();
- Console.WriteLine($"目前有{mementoManagers.mementoList.Count}个备份数据,请输入序号选择备份数据恢复");
- var index = Console.ReadLine();
- sponsor.RestoreMemento(mementoManagers.mementoList.GetValueOrDefault(index));
- Console.WriteLine("恢复后");
- sponsor.ShowPhoto();
- Console.WriteLine("输入q退出");
- var q = Console.ReadLine();
- if (q=="q")
- {
- break;
- }
- }
-
- }

使用场景及优缺点
一、使用场景
1、需要保存/恢复数据的场景可以使用备忘录模式。
2、可提供回滚操作的场景可使用备忘录模式、例如Ctrl+Z。
二、优点
1、给用户提供了一个恢复机制,可以回退到某个历史状态。
2、备忘录的状态由备忘录角色管理,备忘录由管理角色管理,备份数据和恢复数据由发起人管理。符合单一职责原则。
三、缺点
1、会消耗大量的内存,保存一次消耗一次。最终都会消耗较多内存。
总结
到这里我们就介绍完了备忘录模式。备忘录模式将对象的状态数据进行储存,保存在备忘录角色中。然后通过管理员角色进行管理。可以将对象回退到历史某一时刻的状态数据。在游戏中的存档可使用此模式、Ctrl+Z回退可使用此模式、还有浏览器回退历史、数据库事务管理。关于回退操作都可以使用此模式进行操作。这里我们需要注意的是与命令模式进行区分。备忘录模式保存的是对象的状态数据。命令模式保存的是对象发起的命令也就是行为。备忘录模式是对行为状态的操作、命令模式是对行为序列的操作。
用爱生活,你会使自己幸福!用爱工作,你会使很多人幸福!
C#设计模式系列目录
欢迎大家扫描下方二维码,和我一起踏上设计模式的闯关之路吧!
