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

article/2025/6/30 1:26:23

摘要

备忘录设计模式是一种行为型设计模式,用于在不破坏封装性的前提下,捕获对象的内部状态并在需要时恢复。它包含三个关键角色:原发器(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()); // 保存状态2originator.setState("状态 #3");caretaker.add(originator.saveStateToMemento()); // 保存状态3originator.setState("状态 #4");// 恢复状态originator.restoreStateFromMemento(caretaker.get(0)); // 恢复到状态2originator.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@GeneratedValueprivate Long id;private String ruleCode;private String ruleContent;private Integer version;
}
@Data
@Entity
@Table(name = "rule_memento")
public class RuleMemento {@Id@GeneratedValueprivate 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 {@Autowiredprivate 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.hkcw.cn/article/wtQncAMRmC.shtml

相关文章

Linux磁盘管理

磁盘基础 分类 运行方式与原理 详细信息 机械硬盘(HDD)-家用 电机带动磁盘高速旋转&#xff0c;读取数据&#xff1b;速度可以达到5400&#xff0c;7200 rpm&#xff08;round per minute-转/分钟&#xff09; 固态硬盘&#xff08;SSD) 集成电路与芯片&#xff0c;存储芯…

C# XAML 基础:构建现代 Windows 应用程序的 UI 语言

在现代 Windows 应用程序开发中&#xff0c;XAML (eXtensible Application Markup Language) 扮演着至关重要的角色。作为一种基于 XML 的声明性语言&#xff0c;XAML 为 WPF (Windows Presentation Foundation)、UWP (Universal Windows Platform) 和 Xamarin.Forms 应用程序提…

鸿蒙进阶——Mindspore Lite AI框架源码解读之模型加载详解(一)

文章大纲 引言一、模型加载概述二、核心数据结构三、模型加载核心流程 引言 Mindspore 是一款华为开发开源的AI推理框架&#xff0c;而Mindspore Lite则是华为为了适配在移动终端设备上运行专门定制的版本&#xff0c;使得我们可以在OpenHarmony快速实现模型加载和推理等功能&…

趋势因子均值策略思路

本策略旨在通过多种退出条件来管理交易头寸&#xff0c;以实现稳健的交易决策。策略的核心在于利用交易趋势因子&#xff08;ttf&#xff09;及其平均值&#xff08;ttfavg&#xff09;来判断市场趋势&#xff0c;并结合其他技术指标来制定买入、卖出和止损的决策。 交易逻辑思…

FDR的定位原理

一、FDR定位原理概述 频域反射法(FDR)通过分析被测设备在频域上的反射特征&#xff0c;来推断时域(距离域)上的故障位置和性质。当电磁波信号沿着传输线进行传播时&#xff0c;如果遇到阻抗不连续点&#xff0c;一部分能量会继续向前传播&#xff0c;另一部分能量则会反射回来。…

【保姆级教程】PDF批量转图文笔记

如果你有一个PDF文档&#xff0c;然后你想把它发成图文笔记emmm&#xff0c;最好再加个水印&#xff0c;你会怎么做&#xff1f; 其实也不麻烦&#xff0c;打开PDF文档&#xff0c;挨个截图&#xff0c;然后打开PS一张一张图片拖进去&#xff0c;再把水印图片拖进去&#xff0…

【机器学习|评价指标3】平均绝对误差(MAE)、平均绝对百分比误差(MAPE)、均方误差(MSE)、均方根误差(RMSE)详解,附代码。

【机器学习|评价指标3】平均绝对误差&#xff08;MAE&#xff09;、平均绝对百分比误差&#xff08;MAPE&#xff09;、均方误差&#xff08;MSE&#xff09;、均方根误差&#xff08;RMSE&#xff09;详解&#xff0c;附代码。 【机器学习|评价指标3】平均绝对误差&#xff0…

SpringBoot高校宿舍信息管理系统小程序

概述 基于SpringBoot的高校宿舍信息管理系统小程序项目&#xff0c;这是一款非常适合高校使用的信息化管理工具。该系统包含了完整的宿舍管理功能模块&#xff0c;采用主流技术栈开发&#xff0c;代码结构清晰&#xff0c;非常适合学习和二次开发。 主要内容 这个宿舍管理系…

【笔记】在 MSYS2 MINGW64 环境中安装构建工具链(CMake、GCC、Make)

&#x1f4dd; 在 MSYS2 MINGW64 环境中安装构建工具链&#xff08;CMake、GCC、Make&#xff09; ✅ 目标说明 记录在 MSYS2 的 MINGW64 工具链环境中&#xff0c;成功安装用于 C/C 构建的常用开发工具。 包括&#xff1a; GCC 编译器Make 构建系统CMake 跨平台构建工具基础开…

2_MCU开发环境搭建-配置MDK兼容Keil4和C51

MCU开发环境搭建-配置MDK兼容Keil4和C51 一、概述 本文以MDK-ARM V5.36版本基础介绍DMK-ARM工程兼容Keil4和C51的配置。 注:在阅读本文前,请先安装和配置完成MDK-ARM(Keil5)。 二、工具包下载 链接: https://pan.baidu.com/s/1Tu2tDD6zRra4xb_PuA1Wsw 提取码: 81pp 三、…

Redis部署架构详解:原理、场景与最佳实践

Redis部署架构详解&#xff1a;原理、场景与最佳实践 Redis作为一种高性能的内存数据库&#xff0c;在现代应用架构中扮演着至关重要的角色。随着业务规模的扩大和系统复杂度的提升&#xff0c;选择合适的Redis部署架构变得尤为重要。本文将详细介绍Redis的各种部署架构模式&a…

从0开始学习R语言--Day14--贝叶斯统计与结构方程模型

贝叶斯统计 在很多时候&#xff0c;我们经常会看到在统计分析中出现很多反直觉的结论&#xff0c;比如假如有一种病&#xff0c;人群中的患病率为1%&#xff0c;患者真患病时&#xff0c;检测结果为阳性的概率是99%&#xff0c;如果没有&#xff0c;则检测结果为阳性的概率是5…

免费的硬盘工具

—————【下 载 地 址】——————— 【​本章下载一】&#xff1a;https://pan.xunlei.com/s/VORkn5VgcUDScW2C5kyqIyX5A1?pwdw5db# 【​本章下载二】&#xff1a;https://pan.quark.cn/s/dc84a71de32a 【百款黑科技】&#xff1a;https://ucnygalh6wle.feishu.cn/wiki/…

【Python训练营打卡】day42 @浙大疏锦行

DAY 42 Grad-CAM与Hook函数 知识点回顾 1. 回调函数 2. lambda函数 3. hook函数的模块钩子和张量钩子 4. Grad-CAM的示例 作业&#xff1a;理解下今天的代码即可 Grad-CAM 在深度学习中&#xff0c;我们经常需要查看或修改模型中间层的输出或梯度。然而&#xff0c;标准的…

手机隐藏玩法有哪些?

1️⃣飞行模式充电更快 开启飞行模式后&#xff0c;手机会断开所有网络连接&#xff0c;减少后台数据传输&#xff0c;充电速度能提升 30% 以上 2️⃣关闭后台应用反而更耗电 频繁清理后台可能让耗电量增加 10%-20% &#xff0c;正确做法是让常用程序驻留后台 3️⃣闲置手机别…

浅写弱口令与命令爆破

#作者&#xff1a;允砸儿 #日期&#xff1a;乙巳青蛇年 五月初七 笔者从今天开始写各种的漏洞以及靶场演示&#xff0c;这一部分理论伴随着实践但还是实践比较重要。从这一部分开始我们就要找到对方电脑的漏洞进行渗透测试最终获取我们需要得到的信息。笔者就先拿最简单的弱…

B树和B+树

二叉搜索树和平衡二叉树 二叉搜索树&#xff0c;左子节点小于父节点发值&#xff0c;右子节点大于父节点的值。如果需要查找8&#xff0c;需要三次&#xff0c;而顺序查找需要6次。 同样是二叉搜索树&#xff0c;下图的情况查找效率会很低&#xff0c;从而引出平衡二叉树&#…

PDF 转 HTML5 —— HTML5 填充图形不支持 Even-Odd 奇偶规则?(第一部分)

在填充 PDF 中的图形时&#xff08;以及许多其他技术中&#xff09;&#xff0c;你可以选择使用 Even-Odd&#xff08;奇偶&#xff09; 或 Non-Zero&#xff08;非零&#xff09; 填充规则。 对于那些已经在想“你在说啥&#xff1f;”的朋友&#xff0c;别担心&#xff0c;我…

java反序列化: Transformer链技术剖析

Transformer链是CC反序列化漏洞的"执行引擎"&#xff0c;本文聚焦Transformer链的核心原理和实现机制&#xff0c;为后续完整利用链分析奠定基础。 一、Java命令执行与序列化限制 1.1 常规命令执行方式 Java中执行系统命令的标准方法是通过Runtime类&#xff1a; …

bismark OT CTOT OB CTOB 以及mapping后的bam文件中的XG,XR列的含义

首先&#xff0c;OT&#xff0c;OB&#xff0c;CTOT&#xff0c;CTOB都是描述测序reads的&#xff0c;而不是描述参考基因组的。 bisul-fate建库会将DNA双链文库中非甲基化的C转化成U。转化结束后&#xff0c;被转化的U和互补链的G并不配对。此时正链&#xff08;&#xff0c;…