0.参考资料



1.概述

  • 在不破坏封装性的前提下, 捕获一个对象的内部状态 ,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。 —《设计模式》GoF
  • 可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情,或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作.

1.1动机

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

1.2结构

  1. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630153049015-6673ffa8-c0b0-41c7-ac67-5440ac19e7c0.png#clientId=u9b952b2d-c941-4&from=paste&height=251&id=u9eb5686d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=502&originWidth=1464&originalType=binary&ratio=1&size=294242&status=done&style=none&taskId=u5f576fdb-425d-4c80-a960-6f79973f7a4&width=732)
  2. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630156992788-5ae88485-a48f-4065-94b7-60ca96555d85.png#clientId=u9b952b2d-c941-4&from=paste&height=301&id=u0a7bd908&margin=%5Bobject%20Object%5D&name=image.png&originHeight=401&originWidth=762&originalType=binary&ratio=1&size=32886&status=done&style=none&taskId=ue819b357-2dba-40f2-987d-5edd80a240d&width=572)
  3. - 备忘录模式的角色及职责
  4. - Originator : 需要保存状态的对象, 即原发器: 可以生成自身状态的快照,也可以在需要时通过快照恢复自身状态的类
  5. - Memento 备忘录,负责保存好记录,即Originator内部状态
  6. - Caretaker: 守护者对象,负责保存多个备忘录对象, 使用集合管理,提高效率

2.要点总结

宏观架构

  1. 1. 备忘录( Memento )存储**原发器( Originator )**对象的内部状态,在需要时恢复原发器的历史状态。
  2. 1. Memento模式的核心是**信息隐藏**,即Originator需要向外界隐藏信息,保持其封装性。但同时又需要将状态保持到外界( Memento )。
  3. 1. 由于现代语言运行时(如C#、Java等 )都具有相当的对象序列化支持,因此往往采用效率较高、又较容易正确实现的序列化方案来实现Memento模式。

微观代码

  1. - **保存多个原发器originator对象的多个状态,**
  2. - **思路: HashMap <String, 某集合>**
  3. - **k-v: 原发器的唯一key, 对应的备忘录集合**
  4. - 若原发器类的成员变量过多,会占用大量的资源,且每一次保存都会消耗一定内存. **为了节约内存,备忘录模式可以和原型模式配合使用.**

3.案例

需求

  1. - 多人RPG游戏中, 角色有攻击力和防御力,**集体**刷Boss时:
  2. - 在大战Boss前保存自身的状态(攻击力和防御力)
  3. - 当大战Bosss时, 受到DeBuff, 攻击力和防御力下降
  4. - Boss后, 状态恢复到正常(战斗前状态)

传统方案

  1. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630156762171-a34ac287-179c-4024-b4b2-e823326f216b.png#clientId=u9b952b2d-c941-4&from=paste&height=210&id=uf30e5bb1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=420&originWidth=830&originalType=binary&ratio=1&size=31024&status=done&style=none&taskId=ub8e03fdd-8091-4d10-9637-c6720e8189d&width=415)

分析

  1. 1. 一个对象,就对应一个保存对象状态的对象, 这样当我们游戏的对象很多时,不利于管理,开销也很大.
  2. 1. 传统的方式是简单地做备份,new出另外一个对象出来,再把需要备份的数据放到这个新对象,但这就**暴露了对象内部的细节**

4.使用模式

方案

  1. - 一个简易的备忘录模式( 单一原发器, 多备忘录)

类图

  1. - ![Caretaker.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630157559706-a52dfdbd-7749-4a7e-8f4a-03df889374f4.png#clientId=u9b952b2d-c941-4&from=ui&id=u0a7adfac&margin=%5Bobject%20Object%5D&name=Caretaker.png&originHeight=919&originWidth=868&originalType=binary&ratio=1&size=46805&status=done&style=none&taskId=u7a7df0cd-b45a-494c-bf05-adf317e0e4d)

代码

  1. - 原发器 Originator
  1. // 原发器
  2. @Getter
  3. @Setter
  4. public class Originator {
  5. private String version;
  6. private String title;
  7. /**
  8. * 在该时刻保存当前状态
  9. * @return 生成备忘录: 当前的状态
  10. */
  11. public Memento createMemento(){
  12. return new Memento(version, title);
  13. }
  14. /**
  15. * 从指定 备忘录 回复状态
  16. * @param memento 传入的备忘录: 需要的状态
  17. */
  18. public void recoverStateFromMemento(Memento memento){
  19. this.version = memento.getVersion();
  20. this.title = memento.getTitle();
  21. }
  22. public void show(){
  23. System.out.println("\t\t版本: " + version + ", 标题: " + title);
  24. }
  25. }
  - 备忘录 Memento
// 对应的备忘录类
public class Memento {

    private final String version;
    private final String title;

    public Memento(String version, String title) {
        this.version = version;
        this.title = title;
    }

    public String getVersion(){
        return this.version;
    }

    public String getTitle(){
        return this.title;
    }
}
  - 守护者 Caretaker
// 守护者. 当前模式: 保存一个原发器的多个备忘    
public class Caretaker {

    // (一个原发器)保存多个状态
    private List<Memento> mementoList = new ArrayList<>();

    // 其它方案
    // 1. 一个原发器, 只保存一次状态
    //Memento memento;
    // 2. 多个原发器, 均可保存多个状态
    //HashMap<String, Memento> mementoHashMap;

    // 加入一个新状态. 可以设置一个返回值, 比如 int 来锁定当前Memento的存储下表
    public void addMemento(Memento memento){
        mementoList.add(memento);
    }

    // 获取一个状态. 简单化直接通过下标获取
    public Memento getMemento(int index){
        return mementoList.get(index);
    }

}
  - 测试

public class Client {
    public static void main(String[] args) {

        // 创建原发器对应的备忘录对应的的守护者
        Caretaker caretaker = new Caretaker();

        // 创建原发器并初始化
        Originator originator = new Originator();
        originator.setVersion("版本1.0");
        originator.setTitle("刚刚完成");
        System.out.println("--------------初始化展示: ");
        originator.show();
        // 创建当前状态备忘录, 并记录,
        caretaker.addMemento(originator.createMemento());// 0

        // 修改, 并记录
        originator.setVersion("版本1.1");
        originator.setTitle("修改了一次");
        caretaker.addMemento(originator.createMemento());// 1

        System.out.println("--------------第一次修改后展示: ");
        originator.show();

        // 修改2, 记录
        originator.setVersion("版本final");
        originator.setTitle("我觉得是最终版了");
        caretaker.addMemento(originator.createMemento());// 2

        System.out.println("--------------第二次修改后展示: ");
        originator.show();

        // 载入备忘录
        System.out.println("--------------载入备忘录--------------");

        System.out.println("--------------回滚到1.0");
        originator.recoverStateFromMemento(caretaker.getMemento(0));
        originator.show();

        System.out.println("--------------回滚到1.1");
        originator.recoverStateFromMemento(caretaker.getMemento(1));
        originator.show();

        System.out.println("--------------回滚到final");
        originator.recoverStateFromMemento(caretaker.getMemento(2));
        originator.show();

    }
}
--------------初始化展示: 
        版本: 版本1.0, 标题: 刚刚完成
--------------第一次修改后展示: 
        版本: 版本1.1, 标题: 修改了一次
--------------第二次修改后展示: 
        版本: 版本final, 标题: 我觉得是最终版了
--------------载入备忘录--------------
--------------回滚到1.0
        版本: 版本1.0, 标题: 刚刚完成
--------------回滚到1.1
        版本: 版本1.1, 标题: 修改了一次
--------------回滚到final
        版本: 版本final, 标题: 我觉得是最终版了

5.经典使用


Git版本控制

数据库的事务管理

游戏存档

文档编辑软件 中的 ctri + z

浏览器中的后退