Memento
备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,在该对象之外保存这个状态,以便以后需要时能将这个对象恢复到原先保存的状态,又称为 快照模式
备忘录模式就是一种“后悔药”,当撤销当前更改的时候能恢复更改之前的数据。其对应的UML图如下:
3-1Q119130413927.gif

  • 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
  • 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
  • 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

    1、场景描述

    这里就以修改和撤销一个人的简历对象的数据为例子, 用备忘录模式试下。

    2、实现

    ```csharp /// /// 备忘录 — 简历 /// public class ResumeMemento { public string UserName { get; set; } public int Age { get; set; } public string Sex { get; set; } public ResumeMemento(ResumeOriginator originator) {
    1. if (originator == null) throw new ArgumentNullException();
    2. this.UserName = originator.UserName;
    3. this.Age = originator.Age;
    4. this.Sex = originator.Sex;
    } } /// /// 发起人角色 — 简历 /// public class ResumeOriginator { public string UserName { get; set; } public int Age { get; set; } public string Sex { get; set; } /// /// 打印简历信息 /// public void Show() {
      Console.WriteLine($"姓名:{UserName}, 年龄:{Age}, 性别:{Sex}");
    
    } /// /// 创建备忘录 /// /// public ResumeMemento CreateMemento() {
      return new ResumeMemento(this);
    
    } /// /// 恢复备份数据 /// public void RestoreMemento(ResumeMemento memento) {
      if (memento == null) throw new ArgumentNullException();
      this.UserName = memento.UserName;
      this.Age = memento.Age;
      this.Sex = memento.Sex;
    
    } } /// /// 管理者 /// public class Caretaker { public ResumeMemento memento { get; set; } }

// 客户端 ResumeOriginator originator = new ResumeOriginator() { UserName = “张三”, Age = 18, Sex = “男” }; Console.WriteLine(“初始状态:”); originator.Show(); Caretaker caretaker = new Caretaker(); caretaker.memento = originator.CreateMemento(); // 修改数据 originator.Age = 21; originator.Sex = “女”; Console.WriteLine(“修改数据:”); originator.Show(); Console.WriteLine(“恢复数据:”); originator.RestoreMemento(caretaker.memento); originator.Show();

运行结果:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/12691433/1636335966237-b7cb0c84-703c-453f-aa95-2158b738e1ee.png#clientId=u8a09f8ff-9072-4&from=paste&height=119&id=uf57b674d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=119&originWidth=511&originalType=binary&ratio=1&size=8705&status=done&style=none&taskId=u2b314780-c4a7-495d-8c6b-f7e1bf31514&width=511)<br />上面的代码中,管理者类中只存在一个属性memento,这是很简单的一种实现,但是也只能存储一份备份数据。 之所以需要独立出一个管理者类, 是因为可以方便我们队备份对象数据的管理, 比如当我们要存储很多备份数据的时候, 我们在管理者类中完全可以使用栈这种数据结构来存储我们的备份数据,当恢复数据的时候,弹出最新备份数据进行恢复,这就类似于word这些编辑器的撤销功能了,下面重新来实现下这个管理者类:
```csharp
/// <summary>
/// 管理者 -- 多次备份数据的存储
/// </summary>
public class Caretaker2
{
    private Stack<ResumeMemento> mementos;
    public Caretaker2()
    {
        mementos = new Stack<ResumeMemento>();
    }
    /// <summary>
    /// 备份数据入栈
    /// </summary>
    /// <param name="memento"></param>
    public void Push(ResumeMemento memento)
    {
        mementos.Push(memento);
    }
    /// <summary>
    /// 备份数据出栈
    /// </summary>
    /// <param name="memento"></param>
    /// <returns></returns>
    public ResumeMemento Pop()
    {
        if (mementos.TryPop(out ResumeMemento memento))
            return memento;
        return null;
    }
}
// 客户端调用
Console.WriteLine("初始状态:");
originator.Show();
Console.WriteLine("**********************************");
Caretaker2 caretaker2 = new Caretaker2();
caretaker2.Push(originator.CreateMemento());
// 修改数据
originator.Age = 21;
originator.Sex = "女";
caretaker2.Push(originator.CreateMemento());
originator.Age = 19;
originator.Sex = "未知";
caretaker2.Push(originator.CreateMemento());
originator.UserName = "李四";
originator.Sex = "中性";
caretaker2.Push(originator.CreateMemento());

// 恢复数据
var memento = caretaker2.Pop();
while(memento != null)
{
    Console.WriteLine("恢复数据...");
    originator.RestoreMemento(memento);
    originator.Show();
    memento = caretaker2.Pop();
}

运行结果:
image.png

3、优缺点

优点:

  • 给用户提供了一种可以恢复状态的机制,可以使用能够比较方便地回到某个历史的状态
  • 发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则

缺点:

  • 当保存的备份信息太多或者太频繁的时候,会消耗比较大的内存资源。这时候可以考虑将数据序列化后保存到磁盘上。

    4、应用场景

  • 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。

  • 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,Eclipse 等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。