撤销与恢复的奥秘:设计模式之备忘录模式详解

news2024/9/22 17:58:38

备忘录模式

🎯 备忘录模式(Memento Pattern)简介

备忘录模式 是一种行为型设计模式,用于保存对象的某一时刻状态,以便稍后可以恢复到该状态,而不破坏对象的封装性。备忘录模式将对象的状态封装在一个独立的备忘录对象中,外界无法直接访问备忘录的内部细节,只能通过特定接口恢复对象的状态。

核心思想

通过在不破坏封装性的前提下,记录和恢复对象的内部状态,备忘录模式可以让对象恢复到以前的某个状态,常用于撤销操作等场景。

备忘录模式的 UML 类图

在这里插入图片描述

角色说明

  1. Originator(发起者)
    • 发起者是拥有某个内部状态的对象,它能够创建备忘录来记录当前状态,并通过恢复备忘录对象来恢复之前的状态。
  2. Memento(备忘录)
    • 备忘录存储了发起者的内部状态。在外部对象中,备忘录是不可变的,只有发起者可以访问并使用其内容。
  3. Caretaker(负责人)
    • 负责人负责保存和管理备忘录对象,但不能修改或访问备忘录的内容。它只知道如何保存和恢复备忘录。

生动案例:文本编辑器中的撤销功能

场景说明

假设我们有一个简单的文本编辑器,它允许用户输入文本并提供撤销功能。每次用户输入新的文本时,编辑器会保存当前状态(文本内容)到备忘录中。当用户点击“撤销”时,编辑器将恢复到上一个保存的状态。

代码实现

Step 1: 定义发起者类

Originator 类代表文本编辑器,它能够创建和恢复备忘录对象。

// 发起者:文本编辑器
public class TextEditor {
    private String text;

    // 设置文本
    public void setText(String text) {
        this.text = text;
        System.out.println("TextEditor: Setting text to '" + text + "'");
    }

    // 获取当前文本状态
    public String getText() {
        return text;
    }

    // 创建备忘录,保存当前文本状态
    public Memento save() {
        return new Memento(text);
    }

    // 从备忘录恢复文本状态
    public void restore(Memento memento) {
        this.text = memento.getText();
        System.out.println("TextEditor: Restored text to '" + text + "'");
    }

    // 备忘录类,用于存储文本状态
    public static class Memento {
        private final String text;

        public Memento(String text) {
            this.text = text;
        }

        private String getText() {
            return text;
        }
    }
}

Step 2: 定义负责人类

Caretaker 类负责保存和管理文本编辑器的多个备忘录,以便用户可以执行多次撤销操作。

import java.util.Stack;

// 负责人:管理备忘录的保存和恢复
public class Caretaker {
    private Stack<TextEditor.Memento> mementoStack = new Stack<>();

    // 保存备忘录
    public void save(TextEditor editor) {
        System.out.println("Caretaker: Saving state.");
        mementoStack.push(editor.save());
    }

    // 恢复备忘录
    public void undo(TextEditor editor) {
        if (!mementoStack.isEmpty()) {
            System.out.println("Caretaker: Undoing last operation.");
            editor.restore(mementoStack.pop());
        } else {
            System.out.println("Caretaker: No states to undo.");
        }
    }
}

Step 3: 测试备忘录模式

public class MementoPatternDemo {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();
        Caretaker caretaker = new Caretaker();

        // 设置文本状态并保存
        editor.setText("Version 1");
        caretaker.save(editor);

        editor.setText("Version 2");
        caretaker.save(editor);

        editor.setText("Version 3");

        // 执行撤销操作
        caretaker.undo(editor);  // 撤销到 Version 2
        caretaker.undo(editor);  // 撤销到 Version 1
        caretaker.undo(editor);  // 无法继续撤销
    }
}

输出结果

TextEditor: Setting text to 'Version 1'
Caretaker: Saving state.
TextEditor: Setting text to 'Version 2'
Caretaker: Saving state.
TextEditor: Setting text to 'Version 3'
Caretaker: Undoing last operation.
TextEditor: Restored text to 'Version 2'
Caretaker: Undoing last operation.
TextEditor: Restored text to 'Version 1'
Caretaker: No states to undo.

备忘录模式的优缺点

优点

  1. 保持封装性
    备忘录模式不会暴露对象的内部状态,保证了对象的封装性。
  2. 方便状态恢复
    可以轻松地将对象恢复到先前的状态,实现如撤销、回滚等功能。
  3. 简化复杂对象状态的管理
    通过备忘录模式,可以方便地保存和恢复对象的多个状态,适合需要频繁更改状态的场景。

缺点

  1. 占用资源
    如果保存的状态较多或状态信息较大,备忘录模式会占用大量内存,导致性能问题。
  2. 实现较复杂
    实现备忘录模式需要额外的类和逻辑来保存和管理状态,增加了代码复杂性。

备忘录模式的应用场景

  1. 撤销操作
    在编辑器、文档处理器等软件中,当用户进行编辑时,备忘录模式可以保存每一步操作的状态,从而支持撤销功能。
  2. 事务管理
    在数据库事务中,备忘录模式可以用来记录事务的中间状态,以便在发生错误时回滚到之前的状态。
  3. 游戏进度保存
    在游戏开发中,备忘录模式可以用于保存游戏的当前进度,以便玩家可以恢复到之前的游戏状态。

备忘录模式思想在优秀源码中的应用

Java 的 Serializable 接口(对象状态的持久化)

在 JDK 中,Serializable 接口允许对象的状态以字节流的形式保存和恢复。这与备忘录模式类似,因为它能够在某个时刻保存对象的完整状态,并在需要时恢复该状态

Serializable 示例

import java.io.*;

class Originator implements Serializable {
    private String state;

    public Originator(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    @Override
    public String toString() {
        return "State: " + state;
    }
}

public class SerializableMementoExample {
    public static void main(String[] args) {
        Originator originator = new Originator("Initial State");

        // 保存状态到文件
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("memento.ser"))) {
            out.writeObject(originator);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 修改状态
        originator.setState("Modified State");
        System.out.println("Current State: " + originator);

        // 从文件恢复状态
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("memento.ser"))) {
            Originator restored = (Originator) in.readObject();
            System.out.println("Restored State: " + restored);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

解释
  • Serializable 通过序列化和反序列化机制将对象的状态保存到文件中(类似于备忘录模式中的 Memento),并在需要时恢复对象状态。
  • 尽管 Serializable 的应用范围广泛,并非完全符合备忘录模式的设计意图,但它确实提供了对象状态存储和恢复的能力。

Spring 中的 Transaction Management(事务管理回滚)

Spring Framework 中的事务管理系统也使用了备忘录模式的思想。它通过保存数据库的事务状态,在出现异常或错误时能够将数据恢复到原始状态。

原理

  • 在事务开始时,Spring 会保存当前数据库的状态,类似于创建一个备忘录(Memento)。
  • 如果事务中发生错误,Spring 可以将数据库回滚到之前的状态,类似于从备忘录中恢复状态。
  • 这一机制通过 Spring 的事务管理器 实现,允许将整个事务的状态保存并在需要时恢复。
@Transactional
public void transferMoney(Account fromAccount, Account toAccount, double amount) {
    fromAccount.withdraw(amount);
    toAccount.deposit(amount);

    // 在这里如果发生异常,Spring 会自动回滚事务到之前的状态
}

解释

  • @Transactional 方法抛出未检查异常时,Spring 的事务管理机制会回滚之前的操作,恢复数据库到初始状态。这个操作类似于备忘录模式中的“撤销”,在事务失败时恢复到之前的状态。

总结

备忘录模式 提供了一种记录对象状态的机制,并在不破坏对象封装的前提下恢复对象状态。通过备忘录模式,系统可以轻松实现撤销、重做、恢复等功能,非常适合需要频繁修改和恢复对象状态的场景。

通过文本编辑器撤销功能的案例,我们清晰地展示了备忘录模式如何应用于实际开发中,使系统可以灵活管理对象的历史状态,实现多步撤销操作。

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

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

相关文章

技术周总结 09.16~09.22 周日(架构 C# 数据库)

文章目录 一、09.16 周一1.1&#xff09;问题01&#xff1a; 软件质量属性中"质量属性场景"、"质量属性环境分析"、"质量属性效用树"、"质量属性需求用例分析"分别是什么&#xff1f;1.2&#xff09;问题02&#xff1a; 软件质量属性中…

机器学习(1)sklearn的介绍和六个主要模块、估计器、模型持久化

文章目录 1.sklearn介绍2.sklearn的模块3.监督学习和无监督学习1. 监督学习 (Supervised Learning)例子 2. 无监督学习 (Unsupervised Learning)例子 4.估计器估计器的主要特性和方法包括&#xff1a;估计器的类型&#xff1a;示例&#xff1a;使用 scikit-learn 中的估计器 5.…

用最通俗易懂的语言和例子讲解三维点云

前言&#xff1a; 我整体的学习顺序是看的按B站那“唯一”的三维点云的视频学习的&#xff08;翻了好久几乎没有第二个...&#xff09;对于深度学习部分&#xff0c;由于本人并没有进行学习&#xff0c;所以没有深究。大多数内容都进行了自己的理解并找了很多网络的资源方便理解…

JavaScript可视化示例

JavaScript 可视化是指使用 JavaScript 编程语言来创建和操作图形、图表、动画等视觉元素的过程。以下是一些常见的 JavaScript 可视化库和工具&#xff0c;以及它们的主要特点&#xff1a; 1. D3.js 特点: D3.js&#xff08;Data-Driven Documents&#xff09;是一个非常强大…

MySQL高阶之存储过程

什么是存储过程? 存储过程可称为过程化SQL语言&#xff0c;是在普通SQL语句的基础上增加了编程语言的特点&#xff0c;把数据操作语句(DML)和查询语句(DQL)组织在过程化代码中&#xff0c;通过逻辑判断、循环等操作实现复杂计算的程序语言。 换句话说&#xff0c;存储过程其实…

Linux常用命令 笔记

Linux常用指令 查看命令ls 列出指定路径下的文件和目录cd 切换目录绝对路径相对路径 pwd 查看当前路径的绝对路径touch 创建空文件cat 显示文件内容echo 显示内容 & 写入文件vim 文本编辑器打开文件编辑文件保存退出 mkdir 创建目录rm 删除文件&目录删除文件删除目录 定…

java重点学习-设计模式

十三 设计模式 工厂模式&#xff1a;spring中使用&#xff08;目的是&#xff1a;解耦&#xff09; 1.简单工厂 所有的产品都共有一个工厂&#xff0c;如果新增产品&#xff0c;则需要修改代码&#xff0c;违反开闭原则是一种编程习惯&#xff0c;可以借鉴这种编程思路 2.工厂方…

分布式锁优化之 防死锁 及 过期时间的原子性保证(优化之设置锁的过期时间)

文章目录 1、AlbumInfoApiController --》testLock()2、AlbumInfoServiceImpl --》testLock()3、问题&#xff1a;可能会释放其他服务器的锁。 在Redis中设置一个名为lock的键&#xff0c;值为111&#xff0c;并且只有在该键不存在时才设置&#xff08;即获取锁&#xff09;。同…

为解决bypy大文件上传报错—获取百度云文件直链并使用Aria2上传文件至服务器

问题描述 一方面组内的服务器的带宽比较小&#xff0c;另一方面使用bypy方式进行大文件(大于15G)上传时会报错&#xff08;虽然有时可以成功上传&#xff0c;但是不稳定&#xff09;&#xff1a; 解决方式 总体思路: 获得云盘需要下载文件的直链复制直链到服务器中使用自带…

Selenium with Python学习笔记整理(网课+网站持续更新)

本篇是根据学习网站和网课结合自己做的学习笔记&#xff0c;后续会一边学习一边补齐和整理笔记 官方学习网站在这获取&#xff1a; https://selenium-python.readthedocs.io/getting-started.html#simple-usage WEB UI自动化环境配置 (推荐靠谱的博客文章来进行环境配置,具…

OpenCV运动分析和目标跟踪(3)计算图像序列的加权平均值函数accumulateWeighted()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 更新一个运行平均值。 该函数计算输入图像 src 和累积器 dst 的加权和&#xff0c;使得 dst 成为帧序列的运行平均值&#xff1a; dst ( x , y…

CANopen通讯协议笔记

CANopen通讯协议笔记 CANopen 通信对象编号CANopen整体框架网络管理&#xff08;NMT&#xff09;主要任务上线报文心跳报文过程数据对象&#xff08;PDO&#xff09;SDO服务数据对象对象字典概述 CANopen 通信对象编号 CANopen报文传输采用 CAN 标准帧格式。 这里的CAN-ID也叫…

ARM(Day 2)

一、作业 &#xff08;1&#xff09;汇编代码 .text.globl _start_start:mov r0, #0x5mov r1, #0x10比较r0,r1 是否相等 相等执行stop 不相等执行下一步比较&#xff08; r0 > r1 ?&#xff09;cmp r0, r1 比较实际在做减法 (YES NO )subhi r0, r0, r1 r0 > r1 …

浅谈Spring Cloud:OpenFeign

RestTemplate 方式调用存在的问题&#xff1a; String url "http://userservice/user/" order.getUserId(); User user restTemplate.getForObject(url, User.class); 这是通过URL地址来访问的。但是&#xff1a; 代码可读性差&#xff0c;编程体验不统一参数复…

NCNN 源码(1)-模型加载-数据预处理-模型推理

参考 ncnn 第一个版本的代码。 0 整体流程 demo&#xff1a;squeezenet ncnn 自带的一个经典 demo&#xff1a;squeezenet 的代码: // 网络加载 ncnn::Net squeezenet; squeezenet.load_param("squeezenet_v1.1.param"); squeezenet.load_model("squeezenet_…

对象关系映射ORM

目录 ORM【重要】 1、 什么是ORM 2、 实体类 3、 ORM改造登录案例 ORM【重要】 1、 什么是ORM 目前使用JDBC完成了CRUD,但是现在是进行CRUD,增删改方法要设计很多参数,查询的方法需要设计集合才能返回. 在实际开发中,我们需要将零散的数据封装到对象处理. ORM (Object Rela…

在曲线图上最值和极值点位置进行适当标注

1、首先生成一组0-100的随机数&#xff0c;组内共有100个数据&#xff1b; yyrandi([0,100],[1,100]); 2、求这组数据的功率谱密度&#xff0c;并绘图&#xff1b; msize(yy,2); xdft fft(yy); % 计算功率谱密度 psd (1/m) * abs(xdft).^2; x1:m; loglog(x,psd,Linewid…

恶意windows程序

Lab07-01.exe分析&#xff08;DOS攻击&#xff09; 1.当计算机重启后&#xff0c;这个程序如何确保它继续运行(达到持久化驻留)? 创建Malservice服务实现持久化 先分析sub_401040桉函数 尝试获取名为HGL345互斥量句柄&#xff0c;如果不存在则直接结束流程&#xff1b;如果存…

【设计模式】万字详解:深入掌握五大基础行为模式

作者&#xff1a;后端小肥肠 &#x1f347; 我写过的文章中的相关代码放到了gitee&#xff0c;地址&#xff1a;xfc-fdw-cloud: 公共解决方案 &#x1f34a; 有疑问可私信或评论区联系我。 &#x1f951; 创作不易未经允许严禁转载。 姊妹篇&#xff1a; 【设计模式】&#xf…

主语部分、谓语部分、限定动词 (谓语动词) 和非限定动词 (非谓语动词)

主语部分、谓语部分、限定动词 {谓语动词} 和非限定动词 {非谓语动词} 1. 主语部分 (subject)1.1. Forms of the subject 2. 谓语部分 (predicate)2.1. Cambridge Dictionary2.2. Longman Dictionary of Contemporary English2.3. 谓语部分和谓语动词2.4. Traditional grammar …