程序员必知!备忘录模式的实战应用与案例分析

Java核心基础 - 程序员古德

备忘录模式允许在不破坏封装性下捕获并在外部保存对象状态,支持状态恢复,常用于撤销、历史记录等功能。例如在线文档编辑器的撤销操作,编辑器作为原发起人记录状态并提供保存与恢复方法,历史记录或撤销为管理者,保存备忘录对象,每个状态点即为备忘录,此模式为状态管理提供了灵活强大的机制。

定义

程序员必知!备忘录模式的实战应用与案例分析 - 程序员古德

备忘录模式是一种行为型设计模式,它允许在不违反封装性的前提下捕获一个对象的内部状态,并在对象之外保存这个状态,以后可以恢复对象到这个状态,它常用于实现撤销操作、历史记录、快照等功能。

举一个业务中的例子:假设你正在使用一个在线文档编辑器,在这个编辑器中,你可以编写文档、添加格式、插入图片等,同时,这个编辑器还提供了一个非常有用的功能:历史版本或撤销操作,当你编写文档时,不小心删除了一段重要的文字,这时你不需要重新输入,而是可以简单地点击“撤销”按钮,刚刚删除的文字就会重新出现,这就是备忘录模式的一个典型应用。

在这个例子中:

  1. 文档编辑器:这是原发起人(Originator),它记录了当前文档的状态,并提供了创建备忘录(保存状态)和恢复状态的方法。
  2. 历史记录或撤销:这相当于备忘录管理者(Caretaker),它负责保存和管理备忘录对象,每次你修改文档时,编辑器都会在背后创建一个备忘录对象并交给管理者保存。
  3. 状态点:这就是备忘录(Memento)对象,它保存了文档在某个特定时间点的状态,这些对象被管理者保存起来,以便在需要时恢复。

通过这种方式,备忘录模式允许在不破坏对象封装性的情况下保存和恢复对象的状态,从而提供了一种灵活且强大的状态管理机制。

代码案例

程序员必知!备忘录模式的实战应用与案例分析 - 程序员古德

反例

以下是一个未使用备忘录模式的反例代码。在未使用备忘录模式的情况下,如果需要实现撤销功能,会直接在业务对象中保存历史状态,或者在client代码中管理状态历史,这样会破坏对象的封装性,增加业务对象的复杂性,并且使得客户端代码与业务逻辑紧密耦合,如下代码:

// TextDocument.java  
public class TextDocument {  
    private String content;  
    private final Stack<String> history; // 直接在文档类中管理历史记录  
  
    public TextDocument() {  
        this.content = "";  
        this.history = new Stack<>();  
        // 初始状态也加入历史记录,方便演示  
        history.push(content);  
    }  
  
    public void setContent(String content) {  
        // 在修改内容之前,先将当前内容保存到历史记录中  
        this.history.push(this.content);  
        this.content = content;  
    }  
  
    public String getContent() {  
        return content;  
    }  
  
    // 撤销功能实现,直接从历史记录中取出上一个状态  
    public void undo() {  
        if (!history.isEmpty()) {  
            // 弹出当前状态,因为要回退到前一个状态  
            history.pop();  
            // 如果历史记录为空,说明已经回退到最初状态  
            if (history.isEmpty()) {  
                this.content = "";  
            } else {  
                // 否则,将前一个状态设置为当前内容  
                this.content = history.peek();  
            }  
        }  
    }  
}  
  
// Client.java 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        TextDocument document = new TextDocument();  
        System.out.println("初始内容: " + document.getContent());  
  
        document.setContent("第一次编辑");  
        System.out.println("编辑后内容: " + document.getContent());  
  
        document.undo();  
        System.out.println("撤销后内容: " + document.getContent());  
  
        // 尝试再次撤销,将会回到初始状态  
        document.undo();  
        System.out.println("再次撤销后内容: " + document.getContent());  
  
        // 再次尝试撤销,内容应保持不变  
        document.undo();  
        System.out.println("无法再撤销,内容保持为: " + document.getContent());  
    }  
}

运行结果,如下:

初始内容:   
编辑后内容: 第一次编辑  
撤销后内容:   
再次撤销后内容:   
无法再撤销,内容保持为:

在这个例子中,TextDocument类直接管理了自己的历史记录,这破坏了封装性,因为历史记录管理并不是文档编辑的核心职责,撤销操作的实现存在逻辑错误,当调用undo方法时,应该回退到前一个状态,但代码中的实现会导致内容被清空,而不是回退到正确的状态。

正例

正例以下是一个使用备忘录模式的正例代码,当使用备忘录模式时,创建一个备忘录类来存储原发起人的内部状态,并由原发起人自己创建和恢复备忘录,此外,还需要一个管理者类来负责保存备忘录对象,但不需要了解备忘录的具体内容,这样,原发起人可以在不破坏封装性的情况下保存和恢复其内部状态,如下代码:

// Memento.java - 备忘录类,用于存储TextDocument的内部状态  
public class Memento {  
    private final String content;  
  
    public Memento(String content) {  
        this.content = content;  
    }  
  
    public String getContent() {  
        return content;  
    }  
}  
  
// TextDocument.java - 原发起人,负责创建备忘录和管理状态  
public class TextDocument {  
    private String content;  
  
    public TextDocument() {  
        this.content = "";  
    }  
  
    public void setContent(String content) {  
        this.content = content;  
    }  
  
    public String getContent() {  
        return content;  
    }  
  
    // 创建备忘录,保存当前状态  
    public Memento saveToMemento() {  
        return new Memento(content);  
    }  
  
    // 恢复状态,从备忘录中恢复  
    public void restoreFromMemento(Memento memento) {  
        this.content = memento.getContent();  
    }  
}  
  
// Caretaker.java - 管理者类,负责保存备忘录对象,但不了解其内容  
import java.util.Stack;  
  
public class Caretaker {  
    private final Stack<Memento> savedStates = new Stack<>();  
  
    public void addMemento(Memento memento) {  
        savedStates.push(memento);  
    }  
  
    public Memento getMemento() {  
        return savedStates.pop();  
    }  
  
    // 检查是否有保存的状态可供恢复  
    public boolean hasSavedStates() {  
        return !savedStates.isEmpty();  
    }  
}  
  
// Client.java - 客户端代码,演示如何使用备忘录模式  
public class Client {  
    public static void main(String[] args) {  
        TextDocument document = new TextDocument();  
        Caretaker caretaker = new Caretaker();  
  
        System.out.println("初始内容: " + document.getContent());  
  
        document.setContent("第一次编辑");  
        caretaker.addMemento(document.saveToMemento());  
        System.out.println("编辑后内容: " + document.getContent());  
  
        document.setContent("第二次编辑");  
        System.out.println("再次编辑后内容: " + document.getContent());  
  
        // 使用备忘录恢复状态  
        if (caretaker.hasSavedStates()) {  
            document.restoreFromMemento(caretaker.getMemento());  
            System.out.println("撤销后内容: " + document.getContent());  
        }  
  
        // 尝试再次撤销,但没有更多的保存状态了  
        if (caretaker.hasSavedStates()) {  
            document.restoreFromMemento(caretaker.getMemento());  
            System.out.println("再次撤销后内容: " + document.getContent());  
        } else {  
            System.out.println("没有更多的状态可以撤销了。");  
        }  
    }  
}

运行结果:

初始内容:   
编辑后内容: 第一次编辑  
再次编辑后内容: 第二次编辑  
撤销后内容: 第一次编辑  
没有更多的状态可以撤销了。

代码解释:

  1. Memento类是一个简单的Java Bean,用于存储TextDocument的状态。
  2. TextDocument类有设置和获取内容的方法,以及创建备忘录和从备忘录中恢复状态的方法。
  3. Caretaker类负责管理备忘录对象,它使用一个栈来存储备忘录,以便可以依次撤销多个操作。

这个实现保持了TextDocument类的封装性,因为管理者类Caretaker不需要了解TextDocument的内部实现细节,同时,client代码可以轻松地保存和恢复文档的状态,而不需要直接操作文档的内部状态。

核心总结

程序员必知!备忘录模式的实战应用与案例分析 - 程序员古德

备忘录模式允许在不破坏封装性的前提下捕获对象的内部状态,并在以后将对象恢复到该状态,其优点主要在于可以提供一种状态恢复机制,实现撤销、历史记录等功能,增强系统的灵活性和可用性,同时,由于备忘录只暴露给原发起人,这保护了发起人的封装性,防止外部对象访问其内部状态。备忘录模式也存在缺点,它可能会消耗较多的内存资源,因为需要存储多个备忘录对象,此外,管理备忘录的责任需要明确,否则可能导致状态管理的混乱。

在使用备忘录模式时,要慎重考虑是否需要提供状态恢复功能,因为不是所有系统都需要这种功能,要注意管理备忘录的生命周期,避免内存泄漏。

关注我,每天学习互联网编程技术 - 程序员古德

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/304033.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Nodejs+express后端学习笔记(1)

1 Node.js安装 1、下载安装包&#xff1a;进入官网&#xff08;https://nodejs.org/en&#xff09;&#xff0c;下载左侧的稳定版。 2、选择安装位置&#xff0c;不用勾选自动安装必要工具。 其他都默认Next。 配置环境&#xff0c;具体参考本文章&#xff1a; https://blo…

Linux系统——nmap安装与使用

一、安装nmap 1、安装nmap 【操作命令】 yum install nmap 2、查看nmap版本 【操作命令】 nmap -version 【操作实例】 3、卸载nmap 【操作命令】 yum remove nmap 二、简单使用方法 1、扫描指定ip 【操作命令】 nmap 192.168.1.1 2、扫描指定端口 【操作命令】 …

数据库管理-第130期 JSON二元性(20240109)

数据库管理130期 2024-01-09 第130期 JSON二元性&#xff08;20240109&#xff09;1 简介2 关系型表和JSON存储的优劣3 Oracle JSON关系型二元性视图总结 第130期 JSON二元性&#xff08;20240109&#xff09; 上周&#xff0c;又双叒飞了一趟上海&#xff0c;也是2024年第一飞…

Java内存模型(JMM)是基于多线程的吗

Java内存模型&#xff08;JMM&#xff09;是基于多线程的吗 这个问题按我的思路转换了下&#xff0c;其实就是在问&#xff1a;为什么需要Java内存模型 总结起来可以由几个角度来看待「可见性」、「有序性」和「原子性」 面试官&#xff1a;今天想跟你聊聊Java内存模型&#…

即时设计:设计稿与PPT完美结合,让您的创意作品更具影响力

PPT助手 更多内容 在设计领域&#xff0c;将设计稿与PPT结合起来&#xff0c;可以让您的作品更具吸引力和影响力。为了满足这一需求&#xff0c;我们向您推荐一款强大的设计工具&#xff0c;它可以将设计稿导出为PPT文件&#xff0c;支持线上预览和编辑&#xff0c;让您的创意…

ADS仿真 之 容差/良率分析

之所以要进行容差分析&#xff0c; 是因为任何电子元器件均存在一定的误差&#xff0c; 如电感、电容的精度等。 例如一个标称为2.0nH0.1nH的电感&#xff0c;代表的意思产品有99.74%的概率落在2.0nH0.1nH范围内&#xff0c; 即满足6σ &#xff0c;σ是标准偏差或者说方差&…

OpenHarmony沙箱文件

一.前言 1.前景提要 DevEcoStudio版本&#xff1a;DevEco Studio 3.1 Release SDK版本&#xff1a;3.2.2.5 API版本&#xff1a;9 2.概念 在openharmony文件管理模块中&#xff0c;按文件所有者分类分为应用文件和用户文件和系统文件。 1&#xff09;沙箱文件。也叫做应…

C++类和动态内存分配

目录 1. C类的基本概念与使用 2. 动态内存分配与指针 3. 类与动态内存分配的结合应用 4. 注意事项与最佳实践 5.一个简单的示例代码 在C编程中&#xff0c;类是一种重要的概念&#xff0c;它允许我们将数据和操作封装在一起&#xff0c;以实现更加模块化和可维护的代码。而…

运用AI翻译漫画(二)

构建代码 构建这个PC桌面应用&#xff0c;我们需要几个步骤&#xff1a; 在得到第一次的显示结果后&#xff0c;经过测试&#xff0c;有很大可能会根据结果再对界面进行调整&#xff0c;实际上也是一个局部的软件工程中的迭代开发。 界面设计 启动Visual Studio 2017, 创建…

数据结构与算法 - 线性表

文章目录 第1关&#xff1a;实现一个顺序存储的线性表第2关&#xff1a;实现一个链接存储的线性表 第1关&#xff1a;实现一个顺序存储的线性表 编程要求 本关任务是实现 step1/Seqlist.cpp 中的SL_InsAt、SL_DelAt和SL_DelValue三个操作函数&#xff0c;以实现线性表中数据的…

[答疑]领域特定语言DSL属于伪创新吗(谷爱凌)

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 Zeyu 2024-1-4 9:20 马丁福勒的领域特定语言DSL是否有阅读的价值&#xff1f;属于伪创新吗&#xff1f; UMLChina潘加宇 这个问题就有点伪创新 &#xff0c;让人误以为DSL是Fowler发…

本地部署Canal笔记-实现MySQL与ElasticSearch7数据同步

背景 本地搭建canal实现mysql数据到es的简单的数据同步&#xff0c;仅供学习参考 建议首先熟悉一下canal同步方式&#xff1a;https://github.com/alibaba/canal/wiki 前提条件 本地搭建MySQL数据库本地搭建ElasticSearch本地搭建canal-server本地搭建canal-adapter 操作步骤…

shp与数据库(插入数据)

前言 正文 geopandas与shp文件创建表和录入数据 解释一下上面的代码 查询cd2表的geometry字段 查看一下表 前一篇博客的冲突 问题的解决 POLYGON与MUTLIPOLYGON的说明 解决问题的代码 修改表的创建 shapefile和sqlalchemy插入数据 数据库查询 最后 前言 前一篇讲解…

与AI合作 -- 写一个modern c++单例工厂

目录 前言 提问 bard给出的答案 AI答案的问题 要求bard改进 人类智能 AI VS 人类 前言 通过本文读者可以学到modern C单例模式工厂模式的混合体&#xff0c;同时也能看到&#xff1a;如今AI发展到了怎样的智能程度&#xff1f;怎样让AI帮助我们快速完成实现头脑中的想法&…

【hcie-cloud】【17】华为云Stack灾备服务介绍【灾备方案概述、备份解决方案介绍】【上】

文章目录 前言灾备方案概述灾备的定义灾备的重要性故障和灾难对业务连续性带来的挑战灾备系统的衡量指标RTO与RPO分析 灾备等级标准数据中心容灾解决方案全景图云灾备服务总结架构华为云Stack灾备服务总览 备份解决方案介绍云备份服务介绍备份服务架构介绍云备份服务组件功能介…

界面原型设计工具有哪些?看看这9个

界面原型设计是现代设计师必备的技能之一。在设计数字产品或应用程序时&#xff0c;界面原型是将概念转化为具体可交互界面的重要步骤。对于新手小白来说&#xff0c;选择一款易于上手且功能强大的界面原型设计工具至关重要。本文将介绍 9 个常用的界面原型设计工具&#xff0c…

计算机体系结构动态调度(计分板及Tomasulo)学习记录

1.动态调度核心思想&#xff1a;允许就绪指令越过前方停顿指令&#xff0c;提前进入运行&#xff08;乱序执行&#xff09; 就绪指令指不存在资源冲突、操作数已就绪的指令&#xff0c;例如&#xff0c;计分板算法使用计分板来实现&#xff0c;Tomasulo使用保留站来实现&#…

苹果电脑交互式原型设计软件Axure RP 9 mac特色介绍

Axure RP 9 for Mac是一款交互式原型设计软件&#xff0c;使用axure rp9以最佳的方式展示您的作品&#xff0c;优化现代浏览器并为现代工作流程设计。同时确保您的解决方案正确完整地构建。Axure RP 9 for Mac为您整理笔记&#xff0c;将其分配给UI元素&#xff0c;并合并屏幕注…

swing快速入门(三十九)进度对话框

&#x1f381;注释很详细&#xff0c;直接上代码 上一篇 &#x1f9e7;新增内容 &#x1f9e8;1.模拟耗时操作 &#x1f9e8;2.使用计时器更新进度对话框 &#x1f380;源码&#xff1a; package swing31_40;import javax.swing.*; import java.awt.event.ActionEvent; import …

【debug】为什么ansible中使用command出错

碎碎念 在使用ansible执行command的时候&#xff0c;遇到执行会出错的command 比如执行source打算读取环境变量的时候 错误提示为&#xff1a; 没有那个文件或目录:source 一开始以为是错误提示有问题&#xff0c;一直在testrc的路径上检查&#xff0c;但是同样一行命令使用…