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

news2024/11/18 13:52:45

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.coloradmin.cn/o/1368029.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue3打印插件Print.js的使用

文章目录 一、Print.js二、安装2.1、 js文件2.2、npm2.3、CDN 三、使用3.1、网页&#xff08;HTML&#xff09;打印3.2、PDF 打印3.3、图像打印3.4、JSON 打印 四、最后 一、Print.js 在使用 Print.js 插件之前&#xff0c;我们可以通过下面的链接先了解和认识一下这个 JavaScr…

Linux下Redis6下载、安装和配置教程-2024年1月5日

Linux下Redis6下载、安装和配置教程-2024年1月5日 一、下载二、安装三、启动四、设置开机自启五、Redis的客户端1.Redis命令行客户端2.windows上的图形化桌面客户端 一、下载 1.Redis的官方下载&#xff1a;https://redis.io/download/ 2.网盘下载&#xff1a; 链接&#xff…

基于FPGA的万兆以太网学习(1)

万兆(10G) 以太网测速视频:FPGA 实现UDP万兆以太网的速度测试 1 代码结构 2 硬件需求 SFP+屏蔽笼可以插入千兆或万兆光模块。SFP+信号定义与 SFP 一致。 3 Xilinx IP 10 Gigabit Ethernet Subsystem IP说明 文章链接: Xilinx IP 10 Gigabit Ethernet Subsystem IP 4 E…

【ZooKeeper高手实战】ZooKeeper常用命令及客户端工具Curator核心功能

&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308; 欢迎关注公众号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术 的推送 发送 资料 可领取 深入理…

答疑解惑:核技术利用辐射安全与防护考核

前言 最近通过了《核技术利用辐射安全与防护考核》&#xff0c;顺利拿到了合格证。这是从事与辐射相关行业所需要的一个基本证书&#xff0c;考试并不难&#xff0c;在此写篇博客记录一下主要的知识点。 需要这个证书的行业常见的有医疗方面的&#xff0c;如放疗&#xff0c;…

寒假护眼台灯哪款更好?五款高品质护眼台灯推荐

近年来学生近视的现象越来越严重了&#xff0c;而且近视的年龄也越来越小了&#xff0c;不少还没开始上小学的孩子&#xff0c;就已经戴上了厚厚的近视眼镜。而那些高年级的学生更是近视的重灾区&#xff0c;不仅每天需要高强度的学习和长时间用眼&#xff0c;甚至晚上都还需要…

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

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

【pdf密码】pdf打印密码强制解除

正常的PDF文件是可以打印的&#xff0c;如果PDF文件打开之后发现文件不能打印&#xff0c;我们需要先查看一下自己的打印机是否能够正常运行&#xff0c;如果打印机是正常的&#xff0c;我们再查看一下&#xff0c;文件中的打印功能按钮是否是灰色的状态。 如果PDF中的大多数功…

LED电平显示驱动电路图

LB1409九位LED电平显示驱动电路 如图所示为LBl409九位LED电平显示驱动电路。图&#xff08;a&#xff09;是用LB1409做电平显示驱动电路&#xff0c;图&#xff08;b&#xff09;是应用基准电压电平显示驱动电路。LB1409是日本东京互洋电机株式会社生产的产品&#xff0c;与其…

最新ThinkPHP版本实现证书查询系统,实现批量数据导入,自动生成电子证书

前提&#xff1a;朋友弄了一个培训机构&#xff0c;培训考试合格后&#xff0c;给发证书&#xff0c;需要一个证书查询系统。委托我给弄一个&#xff0c;花了几个晚上给写的证书查询系统。 实现功能&#xff1a; 前端按照姓名手机号码进行证书查询证书信息展示证书展示&#x…

0108作业

#include "widget.h"Widget::Widget(QWidget *parent): QWidget(parent) {this->setWindowTitle("腾讯会议");this->resize(470,800);//设置界面大小this->setFixedSize(470,800);//锁定界面大小this->setStyleSheet("background-color:w…

老板向我请教Transformer的原理,我没讲清

最近&#xff0c;一直跟别人讲大语言模型带来的AIGC是巨变&#xff0c;涉及了多个领域&#xff0c;并且谈了我们工作和生活中可以利用的地方&#xff0c;以及预测2024年大语言模型将在哪些领域爆发。这时&#xff0c;老板过来了&#xff0c;就聊&#xff0c;问&#xff0c;谈到…

解决:Unity : Error while downloading Asset Bundle: Couldn‘t move cache data 问题

目录 问题&#xff1a; 尝试 问题得到解决 我的解释 问题&#xff1a; 最近游戏要上线&#xff0c;发现一个现象&#xff0c;部分机型在启动的时候闪退或者黑屏&#xff0c;概率是5%左右&#xff0c;通过Bugly只有个别机型才有这个现象&#xff0c;其实真实情况比这严重的多…

【操作系统】复习汇总(各章节知识图谱)

第1章&#xff1a; 第2章&#xff1a; 第3章&#xff1a; 第4章&#xff1a; 第5章&#xff1a; 第6章&#xff1a; 第7章&#xff1a; 第8章&#xff1a; 第9章&#xff1a;

如何用GPT/GPT4完成AI绘图和论文写作?

详情点击链接&#xff1a;如何用GPT/GPT4完成AI绘图和论文写作&#xff1f; 一OpenAI 1.最新大模型GPT-4 Turbo 2.最新发布的高级数据分析&#xff0c;AI画图&#xff0c;图像识别&#xff0c;文档API 3.GPT Store 4.从0到1创建自己的GPT应用 5. 模型Gemini以及大模型Clau…

Vue使用printJS导出网页为pdf、printJS导出pdf

先放几个参考链接 感谢&#xff01; Vue使用PrintJS实现页面打印功能_vue print.js 设置打印pdf的大小-CSDN博客 前台导出pdf经验汇总 &#xff08;html2canvas.js和浏览器自带的打印功能-print.js&#xff09;以及后台一些导出pdf的方法_iqc后台管理系统怎么做到导出pdf-CSD…

3D模型UV展开原理

今年早些时候&#xff0c;我为 MAKE 杂志写了一篇教程&#xff0c;介绍如何制作视频游戏角色的毛绒动物。 该技术采用给定的角色 3D 模型及其纹理&#xff0c;并以编程方式生成缝纫图案。 虽然我已经编写了一般摘要并将源代码上传到 GitHub&#xff0c;但我在这里编写了对使这一…

3D软件坐标系速查【左手/右手】

本文介绍不同3D软件的世界坐标系之间的差异及其工作原理。 NSDT工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 基本上&#xff0c;游戏引擎和3…

西电期末考点总结

一.“打擂台” 介绍 打擂台用于找到一个数组中的最值问题&#xff0c;先设置一个虚拟擂主&#xff0c;并保证他是“最弱的”&#xff0c;然后遍历数组&#xff0c;找到“更强的”数据&#xff0c;就交换擂主&#xff0c;“打”到最后的“擂主”就是最值数据 相关题目 1004.…

【服务器数据恢复】Raid5热备盘同步失败导致lvm结构损坏的数据恢复案例

服务器数据恢复环境&#xff1a; 两组由4块磁盘组建的raid5磁盘阵列&#xff0c;两组raid5阵列划分为lun并组成了lvm结构&#xff0c;ext3文件系统。 服务器故障&#xff1a; 一组raid5阵列中的一块硬盘离线&#xff0c;热备盘自动上线并开始同步数据。在热备盘完成同步之前&am…