22种设计模式——备忘录模式

1. 概述

备忘录模式是一种行为设计模式, 允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。

在这里插入图片描述
备忘录模式也很好理解,但实现过程是复杂了点。这次不从问题引入了,我们从使用场景引入:对于office这个办公软件,相信大家都不陌生了,他有一项功能叫做撤销,即把内容回退到上次的操作,这项功能在当今许多软件里都存在,那现在让你来实现,你会怎么做呢?

你选择采用直接的方式来实现该功能: 程序在执行任何操作前会记录所有的对象状态, 并将其保存下来。 当用户此后需要撤销某个操作时, 程序将从历史记录中获取最近的快照, 然后使用它来恢复所有对象的状态。
在这里插入图片描述
让我们来思考一下这些状态快照。 首先, 到底该如何生成一个快照呢? 很可能你会需要遍历对象的所有成员变量并将其数值复制保存。 但只有当对象对其内容没有严格访问权限限制的情况下, 你才能使用该方式。 不过很遗憾, 绝大部分对象会使用私有成员变量来存储重要数据, 这样别人就无法轻易查看其中的内容。

okk,我们可以先忽略这个问题,我们假设这个类可以公开它所有的属性。尽管这种方式能够解决当前问题, 让你可随时生成对象的状态快照, 但这种方式仍存在一些严重问题。 未来你可能会添加或删除一些成员变量。 这听上去很简单, 但需要对负责复制受影响对象状态的类进行更改。

还有更多问题。 让我们来考虑编辑器 (Editor) 状态的实际 “快照”, 它需要包含哪些数据? 至少必须包含实际的文本、 光标坐标和当前滚动条位置等。 你需要收集这些数据并将其放入特定容器中, 才能生成快照。

这个备忘录类中几乎没有任何方法, 但有许多与编辑器状态一一对应的成员变量。 为了让其他对象能保存或读取快照, 你很可能需要将快照的成员变量设为公有。 无论这些状态是否私有, 其都将暴露一切编辑器状态。 其他类会对快照类的每个小改动产生依赖, 除非这些改动仅存在于私有成员变量或方法中, 而不会影响外部类。

我们似乎走进了一条死胡同: 要么会暴露类的所有内部细节而使其过于脆弱; 要么会限制对其状态的访问权限而无法生成快照。
在这里插入图片描述
我们再想想,谁拥有这个类属性的访问权限?是他自己!!!那现在就有了一种想法,我们把保存备份数据这个动作交由它自己实现,不再让别人实现。具体怎么做呢?看下面代码。

  • 总结
    1. 备忘录模式用于对对象的备份,便于我们去回溯我们的对象数据。
    2. 备忘录把备份这个功能交由对象本身实现,而不是让别的类去实现。

2. 特点

  • 优点

    1. 你可以在不破坏对象封装情况的前提下创建对象状态快照。
    2. 你可以通过让负责人维护原发器状态历史记录来简化原发器代码。
  • 缺点

    1. 如果客户端过于频繁地创建备忘录, 程序将消耗大量内存。
    2. 负责人必须完整跟踪原发器的生命周期, 这样才能销毁弃用的备忘录。
    3. 绝大部分动态编程语言 (例如 PHP、 Python 和 JavaScript) 不能确保备忘录中的状态不被修改。
  • 使用场景

    1. 当你需要创建对象状态快照来恢复其之前的状态时, 可以使用备忘录模式。备忘录模式允许你复制对象中的全部状态 (包括私有成员变量), 并将其独立于对象进行保存。 尽管大部分人因为 “撤销” 这个用例才记得该模式, 但其实它在处理事务 (比如需要在出现错误时回滚一个操作) 的过程中也必不可少。

    2. 当直接访问对象的成员变量、 获取器或设置器将导致封装被突破时, 可以使用该模式。备忘录让对象自行负责创建其状态的快照。 任何其他对象都不能读取快照, 这有效地保障了数据的安全性。


3. 实现

  • UML类图
    在这里插入图片描述

  • 角色说明

    1. 原发器:可以生成自身状态的快照, 也可以在需要时通过快照恢复自身状态。
    2. 备份类:是原发器状态快照的值对象 。 通常做法是将备忘录设为不可变的, 并通过构造函数一次性传递数据。
    3. 负责人:负责人通过保存备忘录栈来记录原发器的历史状态。 当原发器需要回溯历史状态时, 负责人将从栈中获取最顶部的备忘录, 并将其传递给原发器的恢复 方法。
  • Java代码

    1. 原发器

      /**
       * @Author: chy
       * @Description: 原发器,表示文档类
       * @Date: Create in 21:09 2021/3/30
       */
      public class Document {
          // 表示要备份的数据
          private String content;
          // 保存方法
          public BackUp save(){
              return new BackUp(content);
          }
          // 撤回方法
          public void resume(BackUp backup){
              content = backup.getContent();
          }
          // 修改数据方法
          public void change(String content){
              this.content = content;
          }
          // 打印数据方法
          public void printf(){
              System.out.println(content);
          }
      }
      
    2. 备份类

      /**
       * @Author: chy
       * @Description: 快照类,表示备份
       * @Date: Create in 1:11 2021/3/31
       */
      public class BackUp {
          private String content;
      
          public String getContent() {
              return content;
          }
      
          public BackUp(String content) {
              this.content = content;
          }
      }
      
    3. 负责人

      /**
       * @Author: chy
       * @Description: 负责人,这里表示历史记录
       * @Date: Create in 1:12 2021/3/31
       */
      public class History {
          // 用栈保存备份
          Stack<BackUp> history = new Stack<>();
          // 添加历史记录
          public void add(BackUp backUp){
              history.push(backUp);
          }
          // 取出历史记录的备份
          public BackUp getVersion(){
              return history.pop();
          }
      }
      
    4. 客户端

      /**
       * @Author: chy
       * @Description: 客户端
       * @Date: Create in 21:08 2021/3/30
       */
      public class Client {
          public static void main(String[] args) {
              // 新建历史记录类
              History history = new History();
              // 新建文档类
              Document document = new Document();
              // 每次修改数据就要添加历史记录
              document.change("第一次");
              history.add(document.save());
              document.printf();
      
              document.change("第二次");
              history.add(document.save());
              document.printf();
      
              document.change("第三次");
              history.add(document.save());
              document.printf();
      
              // 开始撤回
              document.resume(history.getVersion());
              document.printf();
      
              document.resume(history.getVersion());
              document.printf();
      
              document.resume(history.getVersion());
              document.printf();
          }
      }
      
    5. 结果
      在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值