二十一、备忘录模式

发布于:2024-08-22 ⋅ 阅读:(21) ⋅ 点赞:(0)


1 基本介绍

备忘录模式(Memento Pattern)是一种 行为型 设计模式,它在 不破坏封装性 的前提下,将某个时间点的实例的状态 保存 下来,之后在有必要时,再将实例 恢复 至当时的状态。

2 案例

本案例实现了浏览器的 访问 和 回退 功能:

  • 访问功能:输入网址,然后显示网址。
  • 回退功能:返回上一个网页,然后显示网址。如果没有上一个网页,则显示无法回退。

2.1 Webpage 类

public class Webpage { // 网页
    private String url; // 路径

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public Memento createMemento() { // 创建备忘录
        return new Memento(url);
    }

    public void restoreMemento(Memento memento) { // 从指定备忘录恢复状态
        this.url = memento.getUrl();
    }
}

2.2 Memento 类

public class Memento { // 保存网页路径的备忘录
    // Memento 类的字段的可见性为 包内可见,这代表 Webpage 类可以随意使用 Memento 类中的字段
    String url; // 路径

    // 假设与 Memento 类同包的类只有 Webpage 类
    // Memento 类的构造器的可见性为 包内可见,这代表只有 Webpage 类可以创建 Memento 类的实例
    Memento(String url) {
        this.url = url;
    }

    // 这个方法的可见性为 public,可以被本包外的类调用
    public String getUrl() {
        return url;
    }
}

2.3 Browser 类

import java.util.LinkedList;

public class Browser { // 浏览器
    private Webpage webpage = new Webpage(); // 当前的网页,初始化的网页没有网址
    private LinkedList<Memento> mementos = new LinkedList<>(); // 存储 以前网页状态 的备忘录集合

    public void access(String url) { // 访问一个网页
        if (webpage.getUrl() != null) { // 如果当前网页有网址
            mementos.push(webpage.createMemento()); // 则将当前网页的状态保存到备忘录集合中
        }
        webpage.setUrl(url);
        System.out.println("当前的网页的网址为:" + webpage.getUrl());
    }

    public void back() { // 返回上一个网页
        if (!mementos.isEmpty()) { // 如果之前访问过网页
            webpage.restoreMemento(mementos.pop()); // 则返回到上一个网页
            System.out.println("上一个网页的网址为:" + webpage.getUrl());
        } else {
            System.out.println("没有网页可以回退");
        }
    }
}

2.4 Client 类

public class Client { // 客户端,测试了 浏览器的 访问 和 返回 功能
    public static void main(String[] args) {
        Browser browser = new Browser();
        browser.access("https://www.baidu.com");
        browser.access("https://baike.baidu.com");
        browser.access("https://www.jd.com");
        browser.back();
        browser.back();
        browser.back();
    }
}

2.5 Client 类的运行结果

当前的网页的网址为:https://www.baidu.com
当前的网页的网址为:https://baike.baidu.com
当前的网页的网址为:https://www.jd.com
上一个网页的网址为:https://baike.baidu.com
上一个网页的网址为:https://www.baidu.com
没有网页可以回退

2.6 总结

本案例中,在访问新的网页时,保存了网页的网址,用以之后的回退功能。

需要特别注意 Memento 类中 各个字段 和 各个方法(包括构造器)的可见性:

  • 所有字段:由于它们在 Webpage 类中会使用到,所以将其可见性设置为 包内可见
  • 构造器:由于不想其他包的类随便生成 Memento 类的实例,所以将其可见性设置为 包内可见
  • 方法分为两类:
    • 只想被 Webpage 类调用的方法:可见性设置为 包内可见。这种情况比较少,一般没有这种方法。
    • 可以被包外的类调用的方法:可见性设置为 public

当将 Memento 类的 字段 和 方法 设置为如上的可见性时,就达成了本模式的一个重点——不破环 Webpage 类的封装性。不过 Memento 类和 Webpage 类的 耦合度很高,如果要修改 Webpage 类的字段,则需要修改 Memento 类的字段。

3 各角色之间的关系

3.1 角色

3.1.1 Originator ( 生成者 )

该角色负责 实现 生成 Memento 角色的实例 和 根据指定 Memento 角色实例恢复自身状态 的方法。本案例中,Webpage 类扮演了该角色。

3.1.2 Memento ( 备忘录 )

该角色负责 记录 Originator 角色中需要的 信息,这些信息可以被 Originator 角色直接访问,但不暴露给其他类。此外,Memento 角色中含有两类方法:

  • 只能被 Originator 角色访问的方法(包括构造器)。
  • 可以被其他类访问的方法。

这些方法具有不同的访问修饰符。本案例中,Memento 类扮演了该角色。

3.1.3 Caretaker ( 管理者 )

该角色负责 在合适的情况下 记录 Originator 角色的状态(即保存其生成的 Memento 实例)然后在合适的情况下 恢复 Originator 角色的状态(即给 Originator 角色传递 Memento 实例)。本案例中,Browser 类扮演了该角色。

3.1.4 Client ( 客户端 )

该角色负责 使用 Caretaker 角色完成具体的业务逻辑。本案例中,Client 类扮演了该角色。

3.2 类图

alt text
说明:

  • Caretaker 中可能用 Memento memento;List<Memento> mementos; 等的方式来 聚合 Memento。
  • Memento 中有两类方法,一类是供给 Originator 调用的,另一类是提供给包外的类调用的。

4 注意事项

  • ** Memento 的生命周期管理**:
    • 及时使用与删除: Memento 创建出来就应在“最近”的代码中使用,避免长时间占用内存。若 Memento 不再需要,应立即删除其引用,以便垃圾回收器回收处理。
    • 避免内存泄漏:由于 Memento 可能包含大量数据,若不及时管理可能会导致内存泄漏
  • 封装性保护
    • 状态封装:备忘录模式应 确保妥善封装 Originator 的内部状态防止外部对象直接访问或修改这些状态
    • 接口设计:Memento 和 Originator 之间的接口设计应清晰明确,确保 Memento 仅包含必要的状态信息,避免暴露过多的内部实现细节。
  • 性能考虑
    • 避免频繁创建:备忘录模式的性能开销主要来自于备忘录对象的创建和存储。因此,应避免在频繁操作的场景(如循环中)创建备忘录对象,以减少不必要的资源消耗。
    • 优化存储:对于大型对象或复杂状态,应考虑使用 压缩 技术来优化存储,减少内存占用。
  • 安全性考虑
    • 敏感数据保护:若 Memento 中包含 敏感数据(如用户个人信息、密码等),则应采取 加密脱敏 等安全措施来保护这些数据的安全性。
    • 并发控制:在 多线程环境 下,若多个线程同时操作同一个发起人对象的状态,并尝试保存或恢复 Memento,则需要考虑 并发控制问题,以避免数据竞争或状态损坏。

5 优缺点

优点

  • 封装性保护:备忘录模式能够有效地 保护封装边界,将 Originator 的内部状态细节隐藏起来,不直接暴露给外部对象。只有 Originator 能够直接访问和修改其内部状态,而其他对象(如 Caretaker)只能通过 Memento 来 间接地 存储 和 恢复 这些状态。
  • 灵活的撤销操作:在需要实现 撤销操作 的场景中,备忘录模式能够方便地 保存每一次操作前的状态,并允许用户随时撤销到之前的状态。这种撤销操作不依赖于系统当前的状态,因此具有很高的灵活性和可靠性。
  • 状态恢复的无损性:由于备忘录模式直接保存了对象的内部状态,因此在恢复状态时可以做到 完全无损,即恢复到保存备忘录时的精确状态,不会有任何数据丢失或损坏。

缺点

  • 资源消耗:备忘录模式会 消耗较多的内存资源,因为需要为每个保存的状态创建一个备忘录对象。如果状态信息很大或者需要频繁地保存和恢复状态,那么这种资源消耗可能会变得非常显著。
  • 管理复杂性:随着系统中备忘录对象的增多,Caretaker 需要维护一个庞大的备忘录集合。这可能会增加系统的管理复杂性,使得 在需要时快速找到并恢复正确的备忘录变得困难
  • 耦合度较高:在备忘录模式中,Memento 对 Originator 的 依赖性很强,如果修改 Originator 的字段,则也需要修改 Memento 的字段。

6 适用场景

  • 文本编辑器的撤销/恢复操作:在文本编辑器中,用户可能会进行多次编辑操作,如输入、删除、修改等。为了 支持撤销(Undo)和恢复(Redo)功能,编辑器可以使用备忘录模式来保存每次编辑操作前的文本状态。当用户执行撤销操作时,编辑器可以从备忘录中恢复到上一次保存的状态;当用户执行恢复操作时,则可以再次回到撤销前的状态。
  • 游戏的存档/读档操作:在游戏中,玩家可能需要保存游戏进度以便在之后继续游戏或者在某些情况下需要恢复到之前的某个游戏状态。游戏可以使用备忘录模式来保存游戏状态,包括玩家的位置、物品、得分等信息。当玩家选择存档时,游戏将当前状态保存到备忘录中;当玩家选择读档时,则可以从备忘录中恢复之前保存的游戏状态。
  • 数据库事务的回滚:在数据库管理中,事务是一组不可分割的操作序列,它们要么全部执行成功,要么全部不执行。为了 支持事务的回滚操作(即在事务执行过程中发生错误时撤销已执行的操作),数据库系统可以使用备忘录模式来保存事务执行前的状态。如果事务执行失败或需要回滚,数据库系统可以从备忘录中恢复到事务开始前的状态。
  • 安全监管中的状态记录:在安全监管领域,系统可能需要记录其运行状态以便在出现问题时进行回溯和调查。通过使用备忘录模式,系统可以定期保存其关键状态信息到备忘录中。当系统出现问题时,监管人员可以从备忘录中检索之前的状态信息,以便分析问题的原因和过程。

7 总结

备忘录模式 是一种 行为型 设计模式,它在 不破坏 Originator 的封装性 的前提下,将它的某个时间点的状态通过 Memento 保存 下来,之后在有必要时,再使用 Memento 将它 恢复 至当时的状态。这种模式保证了 Originator 的封装性,而且在恢复状态时没有数据的丢失。不过缺点也很明显,就是会占用大量的内存或储存空间。在需要 先记录数据然后在一段时间后恢复数据 的场景中,经常会使用到备忘录模式。


网站公告

今日签到

点亮在社区的每一天
去签到