设计模式——备忘录设计模式(行为型)

news2025/6/5 12:18:00

摘要

备忘录设计模式是一种行为型设计模式,用于在不破坏封装性的前提下,捕获对象的内部状态并在需要时恢复。它包含三个关键角色:原发器(Originator)、备忘录(Memento)和负责人(Caretaker)。该模式的优点包括保留对象状态、支持回滚和易于实现撤销/重做功能,但缺点是状态快照可能占用大量内存且管理复杂。其结构可通过嵌套类或中间接口类图表示,实现方式涉及原发器创建和恢复备忘录、备忘录存储状态、负责人保存和获取备忘录。适合用于需要撤销/重做功能或频繁保存状态的场景,但当状态变化不频繁或内存受限时则不适合。实战示例包括数据库脚本、实体类和状态保存与撤销API。此外,该模式可与其他设计模式或技术结合使用,如状态机模式、命令模式、责任链模式、原型模式、观察者模式、策略模式、持久化机制和Spring AOP/注解。

1. 备忘录设计模式定义

备忘录设计模式(Memento Pattern) 是一种行为型设计模式,用于在不破坏封装性的前提下,捕获一个对象的内部状态,并在以后需要时将其恢复到原先的状态。

1.1. ✅ 关键角色

角色

说明

Originator

原发器,拥有内部状态,需要保存快照并恢复自身状态

Memento

备忘录,存储 Originator的内部状态,通常是一个不可变对象

Caretaker

负责人,管理备忘录的保存与恢复,但不访问其内容

1.2. 优点:

  • 保留对象状态,支持回滚
  • 不破坏封装(状态通过 Memento 管理)
  • 易于实现撤销/重做功能

1.3. 缺点:

  • 状态快照可能占用大量内存
  • Caretaker 可能需要管理多个 Memento,带来管理复杂性

2. 备忘录设计模式结构

2.1. 基于嵌套类类图

2.2. 基于中间接口类图

2.3. 备忘录时序图

3. 备忘录设计模式实现方式

备忘录设计模式(Memento Pattern)实现方式的核心在于:在不破坏对象封装性的前提下,捕获并保存对象的内部状态,以便之后可以将其恢复。

3.1. 🧩 Originator(原发器):拥有状态,负责创建和恢复备忘录

public class Originator {
    private String state; // 需要保存的内部状态

    public void setState(String state) {
        this.state = state;
        System.out.println("设置状态为:" + state);
    }

    public String getState() {
        return state;
    }

    // 创建备忘录
    public Memento saveStateToMemento() {
        return new Memento(state);
    }

    // 从备忘录恢复状态
    public void restoreStateFromMemento(Memento memento) {
        this.state = memento.getState();
        System.out.println("恢复状态为:" + state);
    }
}

3.2. 🧩 Memento(备忘录):只暴露给 Originator,用于存储状态

public class Memento {
    private final String state;

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

    // 只有 Originator 使用
    protected String getState() {
        return state;
    }
}

3.3. 🧩 Caretaker(管理者):负责保存、获取备忘录,但不操作内容

import java.util.ArrayList;
import java.util.List;

public class Caretaker {
    private List<Memento> mementoList = new ArrayList<>();

    // 添加备忘录
    public void add(Memento memento) {
        mementoList.add(memento);
    }

    // 获取某个备忘录
    public Memento get(int index) {
        return mementoList.get(index);
    }
}

3.4. 🚀 使用示例(模拟状态保存与恢复)

public class Client {
    public static void main(String[] args) {
        Originator originator = new Originator();
        Caretaker caretaker = new Caretaker();

        originator.setState("状态 #1");
        originator.setState("状态 #2");
        caretaker.add(originator.saveStateToMemento()); // 保存状态2

        originator.setState("状态 #3");
        caretaker.add(originator.saveStateToMemento()); // 保存状态3

        originator.setState("状态 #4");

        // 恢复状态
        originator.restoreStateFromMemento(caretaker.get(0)); // 恢复到状态2
        originator.restoreStateFromMemento(caretaker.get(1)); // 恢复到状态3
    }
}

3.5. ✅ 备忘录总结

组件

职责

Originator

负责创建和恢复备忘录

Memento

存储状态(不可变对象)

Caretaker

管理多个备忘录(可以是栈、队列、列表)

3.6. ✅ 延伸使用(如 Spring 项目中)

你可以将备忘录模式与:

  • Spring 注解(如 @Service@Component
  • 数据持久化(备忘录入库,支持长期存档)
  • REST 接口撤销(如风控规则撤销)
  • 状态机结合(状态保存+回滚)

4. 备忘录设计模式适合场景

4.1. ✅ 适合使用备忘录模式的场景

场景

说明

撤销操作(Undo/Redo)功能

比如文本编辑器、IDE、画图工具,用户希望能够一步步撤销操作。

状态快照与回滚

比如事务性系统、工作流、审批流、游戏存档等,可以保存当前状态,失败时快速回滚。

风控策略、配置管理

在金融风控系统中,策略配置改动后,能够恢复到某个时间点前的策略状态。

状态机状态保存

和状态机结合使用,记录每个状态变化的历史,可实现状态追溯。

临时修改但可还原的场景

比如购物车中的临时优惠应用、试算试验。

AI / 数据建模模拟

在做一系列模拟实验时,需要保存中间状态,方便比较或回退。

4.2. ❌ 不适合使用备忘录模式的场景

场景

原因

状态对象非常庞大或频繁变动

会频繁创建大量备份,造成内存/存储负担。例如大型图像、视频编辑。

备份数据无法或不允许暴露给外部

即使模式保证封装性,有些敏感状态如密钥/隐私也不应被存储。

状态之间无明显断点或快照意义不大

比如高频实时流系统,数据快速变化,快照意义小。

备份状态对业务无价值

若状态回滚从不发生,记录也无实际意义,反而增加维护成本。

替代机制更适合

比如用数据库的事务机制或版本控制系统就能解决的,不必使用设计模式实现复杂备份。

总结:备忘录模式适用于“状态可保存且可能需要还原”的对象,在撤销、版本控制、配置管理等场景特别合适。

5. 备忘录设计模式实战示例

在风控系统中,用户可配置规则,每次变更都自动保存历史状态,并支持「撤销」功能。

5.1. ✳️ 数据库脚本(rule_history.sql

CREATE TABLE rule_config (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  rule_code VARCHAR(64),
  rule_content TEXT,
  version INT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE rule_memento (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  rule_id BIGINT,
  rule_content TEXT,
  version INT,
  saved_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

5.2. ✳️ 实体类

@Data
@Entity
@Table(name = "rule_config")
public class RuleConfig {
    @Id
    @GeneratedValue
    private Long id;

    private String ruleCode;

    private String ruleContent;

    private Integer version;
}
@Data
@Entity
@Table(name = "rule_memento")
public class RuleMemento {
    @Id
    @GeneratedValue
    private Long id;

    private Long ruleId;

    private String ruleContent;

    private Integer version;

    private Timestamp savedAt;
}

5.3. 🧠 备忘录模式结构

5.3.1. 备忘录(Memento)

public class RuleMementoSnapshot {
    private final String content;
    private final Integer version;

    public RuleMementoSnapshot(String content, Integer version) {
        this.content = content;
        this.version = version;
    }

    public String getContent() {
        return content;
    }

    public Integer getVersion() {
        return version;
    }
}

5.3.2. 发起人(Originator)

@Component
public class RuleOriginator {
    public RuleMementoSnapshot save(RuleConfig ruleConfig) {
        return new RuleMementoSnapshot(ruleConfig.getRuleContent(), ruleConfig.getVersion());
    }

    public void restore(RuleConfig ruleConfig, RuleMementoSnapshot snapshot) {
        ruleConfig.setRuleContent(snapshot.getContent());
        ruleConfig.setVersion(snapshot.getVersion());
    }
}

5.3.3. 负责人(Caretaker)

@Service
public class RuleHistoryService {

    @Autowired
    private RuleMementoRepository mementoRepository;

    public void saveMemento(Long ruleId, RuleMementoSnapshot snapshot) {
        RuleMemento memento = new RuleMemento();
        memento.setRuleId(ruleId);
        memento.setRuleContent(snapshot.getContent());
        memento.setVersion(snapshot.getVersion());
        mementoRepository.save(memento);
    }

    public RuleMementoSnapshot getLastMemento(Long ruleId) {
        RuleMemento latest = mementoRepository.findTopByRuleIdOrderBySavedAtDesc(ruleId);
        return new RuleMementoSnapshot(latest.getRuleContent(), latest.getVersion());
    }
}

5.4. 💡 状态保存与撤销 API(Controller)

@RestController
@RequestMapping("/api/rule")
public class RuleController {

    @Autowired 
    private RuleRepository ruleRepository;
    
    @Autowired 
    private RuleHistoryService historyService;
    
    @Autowired 
    private RuleOriginator originator;

    @PostMapping("/update")
    public ResponseEntity<String> updateRule(@RequestBody RuleConfig newRule) {
        RuleConfig old = ruleRepository.findById(newRule.getId()).orElseThrow();
        RuleMementoSnapshot snapshot = originator.save(old);
        historyService.saveMemento(old.getId(), snapshot);

        old.setRuleContent(newRule.getRuleContent());
        old.setVersion(old.getVersion() + 1);
        ruleRepository.save(old);
        return ResponseEntity.ok("规则已更新并记录历史");
    }

    @PostMapping("/undo/{ruleId}")
    public ResponseEntity<String> undo(@PathVariable Long ruleId) {
        RuleConfig rule = ruleRepository.findById(ruleId).orElseThrow();
        RuleMementoSnapshot lastSnapshot = historyService.getLastMemento(ruleId);
        originator.restore(rule, lastSnapshot);
        ruleRepository.save(rule);
        return ResponseEntity.ok("回滚成功");
    }
}

6. 备忘录设计模式思考

备忘录设计模式(Memento Pattern)在实际开发中往往不会单独使用,而是与其他设计模式组合,以解决更复杂的状态管理、回滚、撤销等需求。下面是常见的组合方式和典型应用场景,尤其适合 金融风控、流程引擎、配置管理 等场景。

6.1. ✅ 备忘录模式 + 状态机模式(State Pattern)

组合说明:

  • 状态机负责管理状态切换逻辑;
  • 备忘录负责保存每个状态快照,实现状态回滚/撤销

场景示例:

  • 风控审批流程中,支持将任务状态退回上一步。
  • 订单状态(待支付 → 已支付 → 配送中)中用户申请退款,需要回退到「待支付」。

6.2. ✅ 备忘录模式 + 命令模式(Command Pattern)

组合说明:

  • 命令封装用户操作;
  • 备忘录记录执行前的状态,实现 undo() 操作。

场景示例:

  • 金融交易撤销;
  • 系统管理员修改风控规则,每次操作都可以回滚。

6.3. ✅ 备忘录模式 + 责任链模式(Chain of Responsibility)

组合说明:

  • 每个处理节点执行任务前记录状态;
  • 执行失败时利用备忘录回滚上一步处理。

场景示例:

  • 信贷审批流程,每个环节出错需恢复上一次状态。

6.4. ✅ 备忘录模式 + 原型模式(Prototype Pattern)

组合说明:

  • 使用原型的浅/深拷贝方式快速创建备忘录对象;
  • 提高状态快照的创建效率。

场景示例:

  • 在风险规则配置页面修改参数时,使用深克隆构建快照并保存。

6.5. ✅ 备忘录模式 + 观察者模式(Observer Pattern)

组合说明:

  • 状态变化时通知观察者;
  • 备忘录用于记录状态变化历史。

场景示例:

  • 配置变更通知下游服务,但允许撤销恢复上一个配置。

6.6. ✅ 备忘录模式 + 策略模式(Strategy Pattern)

组合说明:

  • 策略负责计算/处理;
  • 备忘录记录策略执行前后的状态,便于结果回退。

场景示例:

  • 多种风控策略组合决策后,若发现误判,恢复上一次执行前的状态。

6.7. ✅ 备忘录模式 + 持久化机制(如 Repository、数据库)

组合说明:

  • 备忘录对象不是只存在于内存,而是持久化保存(如 JSON 存库)。
  • 支持跨进程或长时间状态恢复。

场景示例:

  • 某条风控规则上线后的历史版本备份,可通过后台 UI 操作恢复。

6.8. ✅ 备忘录模式 + Spring AOP/注解

组合说明:

  • 使用注解(如 @MementoBackup)自动拦截方法;
  • 在方法前后生成并记录状态快照,实现零侵入式回滚机制

场景示例:

  • Spring Boot 应用中,支持风控参数调整回滚功能,只需加注解即可。

6.9. 🧩 组合参考表

组合模式

典型作用

适用系统类型

状态机

管理状态转换+回滚

审批系统、风控流程引擎

命令

操作封装+撤销

配置平台、策略决策平台

责任链

多环节状态保护

风控引擎、流程引擎

原型

快速复制状态

配置回滚、草稿管理

观察者

状态广播+恢复

分布式配置中心

策略

策略组合+状态对比

风控策略、限额控制

持久化

状态历史存档

长期任务跟踪系统

AOP/注解

自动化拦截/回滚

企业级 Spring 应用

博文参考

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

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

相关文章

UI自动化测试的革新,新一代AI工具MidScene.js实测!

前言 AI已经越来越深入地走入我们的实际工作,在软件测试领域,和AI相关的新测试工具、方法也层出不穷。在之前我们介绍过结合 mcp server 实现 AI 驱动测试的案例,本文我们将介绍一个近期崭露头角的国产AI测试工具 Midscene.js Midscene.js简介 MidScene.js 是由字节跳动 w…

4. Qt对话框(2)

在上节中已经学习了对话框的确认和取消&#xff0c;本节内容继续接上节完成登录对话框实例并得到登录信息。 本文部分ppt、视频截图原链接&#xff1a;[萌马工作室的个人空间-萌马工作室个人主页-哔哩哔哩视频] 1 实现登录对话框 1.1 功能需要 得到登录信息&#xff0c;需要…

Android Studio 2022.2.1.20 汉化教程

查看Android Studio 版本 Android Studio Flamingo | 2022.2.1 Patch 2 下载&#xff1a;https://plugins.jetbrains.com/plugin/13710-chinese-simplified-language-pack----/versions/stable

golang -- slice 底层逻辑

目录 一、前言二、结构三、创建3.1 根据 make创建3.2 通过数组创建 四、内置append追加元素4.1 追加元素4.2 是否扩容4.2.1 不扩容4.2.2 扩容 总结 一、前言 前段时间学了go语言基础&#xff0c;过了一遍之后还是差很多&#xff0c;所以又结合几篇不同资料重新学习了一下相关…

SOC-ESP32S3部分:26-物联网MQTT连云

飞书文档https://x509p6c8to.feishu.cn/wiki/IGCawAgqFibop7kO83KcsDFBnNb ESP-MQTT 是 MQTT 协议客户端的实现&#xff0c;MQTT 是一种基于发布/订阅模式的轻量级消息传输协议。ESP-MQTT 当前支持 MQTT v5.0。 特性 支持基于 TCP 的 MQTT、基于 Mbed TLS 的 SSL、基于 WebSo…

制造业的未来图景:超自动化与劳动力转型的双重革命

市场现状&#xff1a;传统制造业的转型阵痛 当前全球制造业正站在历史性变革的十字路口。埃森哲对552位工厂经理的全球调研显示&#xff0c;60%的受访者将劳动力转型视为首要战略任务​​&#xff0c;而63%的工厂正在加速部署自动化技术[1]。超过​75%的工厂经理​​认为&…

【Unity】相机 Cameras

1 前言 主要介绍官方文档中相机模块的内容。 关于“9动态分辨率”&#xff0c;这部分很多API文档只是提了一下&#xff0c;具体细节还需要自己深入API才行。 2 摄像机介绍 Unity 场景在三维空间中表示游戏对象。由于观察者的屏幕是二维屏幕&#xff0c;Unity 需要捕捉视图并将…

如何在 Solana 上发币,并创建初始流动性让项目真正“动”起来?

在 Solana 上发行代币如今已不再是技术门槛&#xff0c;而是市场策略和执行效率的较量。如果你只是简单发了一个代币&#xff0c;却没为它建立流动性和市场机制&#xff0c;那么它就只是一个“死币”。 本文将带你一步步理解&#xff0c;如何从发币到建立流动性池&#xff0c;…

核心机制:滑动窗口

TCP 协议 1.确认应答 可靠传输的核心机制 2.超时重传 可靠传输的核心机制 3.连接管理 TCP/网络 最高的面试题 三次握手,建立连接(必须是 三次) 四次挥手,断开连接(可能是 三次) 核心机制四:滑动窗口 算法中的"滑动窗口" 出自 TCP 前面的三个…

苹果电脑深度清理,让老旧Mac重焕新生

在日常使用苹果电脑的过程中&#xff0c;随着时间推移&#xff0c;系统内会积累大量冗余数据&#xff0c;导致电脑运行速度变慢、磁盘空间紧张。想要让设备恢复流畅&#xff0c;苹果电脑深度清理必不可少。那么&#xff0c;如何进行苹果电脑深度清理呢&#xff1f;接下来为你详…

微服务面试(分布式事务、注册中心、远程调用、服务保护)

1.分布式事务 分布式事务&#xff0c;就是指不是在单个服务或单个数据库架构下&#xff0c;产生的事务&#xff0c;例如&#xff1a; 跨数据源的分布式事务跨服务的分布式事务综合情况 我们之前解决分布式事务问题是直接使用Seata框架的AT模式&#xff0c;但是解决分布式事务…

高性能MYSQL(三):性能剖析

一、性能剖析概述 &#xff08;一&#xff09;关于性能优化 1.什么是性能&#xff1f; 我们将性能定义为完成某件任务所需要的时间度量&#xff0c;换句话说&#xff0c;性能即响应时间&#xff0c;这是一个非常重要的原则。 我们通过任务和时间而不是资源来测量性能。数据…

mysql(十四)

目录 多表查询 1.准备工作 2--创建表格 3--插入数据 2.笛卡尔积查询 3.内连接查询 1--隐式内连接 格式 查询 2--显示内连接&#xff08;Inner join .. on &#xff09; 格式 查询 4.外连接查询 1--左外连接查询&#xff08;LEFT OUTER JOIN .. ON &#xff09; 格式 查询 2-- 右…

工业物联网中的事件驱动采样架构及优化

论文标题 Event-Based Sampling Architecture and Optimization for Industrial Internet of Things 工业物联网中的事件驱动采样架构及优化 作者信息 Tejas Thosani Process Control Systems, Micron Technology Inc., Manassas, USA tthosanimicron.com Andres Prado Esp…

基于 HT for Web 的轻量化 3D 数字孪生数据中心解决方案

一、技术架构&#xff1a;HT for Web 的核心能力 图扑软件自主研发的 HT for Web 是基于 HTML5 的 2D/3D 可视化引擎&#xff0c;核心技术特性包括&#xff1a; 跨平台渲染&#xff1a;采用 WebGL 技术&#xff0c;支持 PC、移动端浏览器直接访问&#xff0c;兼容主流操作系统…

JavaScript 性能优化:从入门到实战

在当今快节奏的互联网时代&#xff0c;用户对网页和应用的加载速度与响应性能要求越来越高。JavaScript 作为网页交互的核心语言&#xff0c;其性能表现直接影响用户体验。本文将用简单易懂的语言&#xff0c;带你了解 JavaScript 性能优化的实用技巧&#xff0c;帮助你的代码跑…

启动metastore时报错MetaException(message:Version information not found in metastore

把hdfs清空重新安装了一下&#xff0c;hive的mysql元数据库删除掉之后重建之后一直启动报错 metastore.RetryingHMSHandler (RetryingHMSHandler.java:<init>(83)) - HMSHandler Fatal error: MetaException(message:Version information not found in metastore.) 后来…

MyBatisPlus(1):快速入门

我们知道&#xff0c;MyBatis是一个优秀的操作数据库的持久层框架&#xff08;优秀持久层框架——MyBatis&#xff09;&#xff0c;其基于底层的JDBC进行高度封装&#xff0c;极大的简化了开发。但是对于单表操作而言&#xff0c;我们需要重复地编写简单的CRUD语句。这其实是不…

京东热点缓存探测系统JDhotkey架构剖析

热点探测使用场景 MySQL 中被频繁访问的数据 &#xff0c;如热门商品的主键 IdRedis 缓存中被密集访问的 Key&#xff0c;如热门商品的详情需要 get goods$Id恶意攻击或机器人爬虫的请求信息&#xff0c;如特定标识的 userId、机器 IP频繁被访问的接口地址&#xff0c;如获取用…

【Elasticsearch】ILM(Index Lifecycle Management)策略详解

ILM&#xff08;Index Lifecycle Management&#xff09;策略详解 1.什么是 ILM 策略&#xff1f;2.ILM 解决的核心业务问题3.ILM 生命周期阶段3.1 Hot&#xff08;热阶段&#xff09;3.2 Warm&#xff08;温阶段&#xff09;3.3 Cold&#xff08;冷阶段&#xff09;3.4 Delete…