你写的撤销功能,99% 是伪 Memento——Undo 不是存个备份那么简单
你写的撤销功能99% 是伪 Memento——Undo 不是存个备份那么简单几乎每个业务系统都有撤销需求但大多数实现跟 Memento 模式没什么关系。它们只是存个 JSON 快照到数据库然后在用户点撤销时把旧数据覆盖回去。这个做法能跑但它不是 Memento。Memento 的核心不是备份是封装状态的访问边界——让 Originator 自己管自己的状态外部Caretaker只负责存取一个黑盒不能偷看里面装了什么。这个区别在工程里会直接决定你的撤销系统能走多远。一个真实的踩坑场景三年前我接手一个电商后台的订单编辑系统。需求很简单运营改完订单信息后可以撤销回上一步。当时的实现是每次编辑前把整个 OrderDTO 序列化成 JSON 字符串塞进order_undo_log表。撤销时反序列化覆盖当前数据。看起来没毛病直到运营提了个需求撤销的时候能不能只恢复部分字段比如只改回收货地址但保留我刚改的价格。做不了。因为快照是整个 DTO 的粒度太粗。你想拆成字段级快照可以但 JSON 字符串里各个字段纠缠在一起外部系统Caretaker根本不知道哪个字段对应什么语义。更隐蔽的问题在后面OrderDTO 加了新字段旧快照反序列化回来新字段是 null。运营撤销后提交null 覆盖了别人刚填的数据。这就是 Caretaker 越界访问内部状态结构的代价——它根本不该知道 OrderDTO 长什么样。Memento 的真正结构Memento 模式三个角色Originator发起人拥有需要保存的状态负责创建和恢复 Memento。Memento备忘录封装状态对除 Originator 外的所有对象隐藏内部细节。Caretaker管理者负责保存 Memento不能操作或检查其内容。关键约束Caretaker 只能拿到一个 Opaque 的令牌不能拆包看内容。用 Java 写大概是这个结构java // Memento 是内部类或包私有外部只能拿到接口 public interface OrderMemento { // 空接口外部没有任何方法可调用 }public class OrderEditor { private String address; private BigDecimal price; private String remark;// 创建备忘录只有 Originator 知道自己怎么存 public OrderMemento save() { return new Snapshot(address, price, remark); } // 恢复只有 Originator 知道自己怎么恢复 public void restore(OrderMemento memento) { Snapshot s (Snapshot) memento; this.address s.address; this.price s.price; this.remark s.remark; } // Memento 实现是私有的外部完全不可见 private static class Snapshot implements OrderMemento { final String address; final BigDecimal price; final String remark; Snapshot(String a, BigDecimal p, String r) { this.address a; this.price p; this.remark r; } }}// Caretaker 只管存和取绝不拆开看 public class UndoManager { private final Deque stack new ArrayDeque();public void push(OrderMemento m) { stack.push(m); } public OrderMemento pop() { return stack.pop(); } public boolean isEmpty() { return stack.isEmpty(); }} 这个结构里UndoManager根本不知道OrderMemento里面有什么。它就是一个黑盒管理员。如果哪天OrderEditor内部状态重构了——比如把address拆成province/city/detail——UndoManager一行代码不用改。这就是封装的力量。JSON 快照方案牺牲了封装换来了简单但在长期演进中付出了十倍代价。跟数据库快照、Event Sourcing 的区别很多人把 Memento 和数据库快照混为一谈其实它们解决的是不同层面的问题。数据库快照是持久层的备份机制关注的是数据丢了怎么恢复。它的受众是 DBA 和运维粒度通常是整张表或整个库不涉及业务对象的封装边界。Memento是领域层的撤销机制关注的是用户在当前会话里的操作怎么回退。它的受众是业务对象本身粒度是单个对象的状态封装核心约束是 Caretaker 不能越界。Event Sourcing是另一种思路不存状态只存事件。撤销不是恢复旧状态而是追加一个逆向事件。这个方案更强大可以 replay、可以审计但复杂度也高一个数量级。Memento 是拍照片Event Sourcing 是记日记。选哪个取决于你的撤销需求有多复杂——如果只是简单的单步/多步撤销Memento 够用了如果需要完整的历史追溯和分支回放再考虑 Event Sourcing。三个工程化陷阱1. Memento 内存爆炸如果每次状态变更都存一个完整快照高频操作下内存很快撑爆。解决思路增量 Memento只存变更的字段不是整个对象。但这会打破黑盒原则——Caretaker 需要知道哪些字段变了。折中方案是 Originator 内部做增量计算对外仍然输出一个统一的黑盒。快照 操作日志每 N 步存一个完整快照中间用 Command 模式记录操作。撤销时先找最近快照再 replay 逆向操作。惰性复制利用不可变数据结构persistent data structure新旧状态共享未变更的部分物理上只复制变更的分支。2. 深拷贝 vs 引用泄露Memento 存的是对象引用还是深拷贝如果存引用Originator 后续修改会污染 Memento。如果存深拷贝大对象性能堪忧。没有银弹。我的习惯是值对象String、Integer、不可变 BigDecimal直接存引用集合和自定义对象必须深拷贝。Java 里可以用clone()、CopyOnWriteArrayList、或者 Jackson 序列化后再反序列化笨但稳。3. 多 Originator 的交叉恢复一个 Caretaker 管多个 Originator比如一个表单里有订单编辑器和客户编辑器撤销栈是统一的。用户点了撤销应该恢复哪个 Originator 的状态这种场景需要把 Command 模式拉进来每次用户操作包装成一个 CommandCommand 执行时各自创建 Memento。撤销栈里存的是 Command 对象pop 出来就知道该调用哪个 Originator 的 restore。java public interface EditCommand { void execute(); void undo(); }public class ChangePriceCommand implements EditCommand { private final OrderEditor editor; private OrderMemento backup; private final BigDecimal newPrice;public void execute() { backup editor.save(); // 执行前存快照 editor.setPrice(newPrice); } public void undo() { editor.restore(backup); }} Memento 管怎么存状态Command 管什么时候存、存谁的。两者配合才能搭一个工业级的撤销系统。什么时候该用 Memento不是有撤销需求就必须上 Memento。判断标准状态的内部结构可能变化 →用封装隔离变化Caretaker 不应知道状态细节安全/权限原因→用黑盒机制天然适合需要多级撤销且状态对象很大 →考虑增量 Memento 或 Command 组合只是简单的单字段编辑且状态结构极稳定 → 直接存旧值可能更轻量不必硬套模式设计模式不是炫技是在约束条件下做 trade-off。Memento 的约束是封装状态访问如果你的场景不需要这个约束强上模式反而是过度设计。我在做一个用卡皮巴拉讲设计模式的微信小程序「爪爪代码冒险记」23 个设计模式用漫画 答题的方式讲目前正在开发中。如果你觉得这类内容有意思搜一下「爪爪代码冒险记」或者等我后面的文章。

相关新闻

不可见只物:AI幻觉的拓扑学

不可见只物:AI幻觉的拓扑学

——为什么模型会在最自信的时候,编造它自己都信以为真的东西开篇宇宙中有一种定律。它没有被写入任何物理教科书,不作用于引力或电磁,不守恒任何量子数。它只是在那里——安静地、不可被完全解释地,存在于每一个足够复杂的系统里…

2026/8/17 3:20:10 阅读更多 →
从FNF定制框架到可迭代游戏项目:理解、跑通与工程化改造实战

从FNF定制框架到可迭代游戏项目:理解、跑通与工程化改造实战

上周在整理本地项目时,翻到一个名为“未命名-迷你世界fnf引擎”的文件夹。点开一看,里面是几个零散的脚本文件和资源包,注释里写着“引擎展示”。这让我想起一个挺有意思的现象:很多开发者,尤其是刚接触游戏制作的朋友…

2026/8/18 9:22:00 阅读更多 →
Wi-Fi MAC地址修改指南:绕过路由器黑名单与隐私保护实战

Wi-Fi MAC地址修改指南:绕过路由器黑名单与隐私保护实战

1. 从一次“断网”风波说起:为什么需要修改Wi-Fi MAC地址?事情是这样的,我家里有一台老路由器,管理后台的“设备拉黑”功能用得比较频繁,主要是为了限制家里小孩的某些设备在特定时段上网。有一次,我自己的…

2026/8/18 20:08:37 阅读更多 →

最新新闻

免费实时翻译Unity游戏文本:XUnity自动翻译器上手全攻略

免费实时翻译Unity游戏文本:XUnity自动翻译器上手全攻略

免费实时翻译Unity游戏文本:XUnity自动翻译器上手全攻略 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator 如果你曾把一款心仪的Unity游戏下载到本地,却在开场三分钟就被满屏外语劝退…

2026/8/19 8:50:47 阅读更多 →
Python惰性计算教程_延迟执行优化性能

Python惰性计算教程_延迟执行优化性能

中惰性计算并非原生强制特性, 不过它能够借助生成器、以及dask等手段主动达成延迟执行, 以此来减少内存占用并且避免提前进行计算。在其中, 惰性计算也就是Lazy, 它并非是语言原生所强制具备的特性, 不过呢, 借助生成器, 还有迭代器, 以及延迟属性, 这里的延迟属性是通过加上缓…

2026/8/19 8:50:47 阅读更多 →
AI写的论文痕迹太重怎么办?5款工具帮你优化AIGC检测结果!

AI写的论文痕迹太重怎么办?5款工具帮你优化AIGC检测结果!

用DeepSeek或者ChatGPT帮忙写完论文,交稿前突然想起来:学校要查AIGC检测。赶紧找了个检测工具一测——AI率72%。怎么把论文里的AI痕迹去掉?有没有什么去AI痕迹工具能一次搞定?这篇文章帮你整理了2026年3月实测过的5款消除AI痕迹软…

2026/8/19 8:50:47 阅读更多 →
字幕编辑器从零到一实战教程:视频语音转字幕、时间轴同步与自动翻译全流程

字幕编辑器从零到一实战教程:视频语音转字幕、时间轴同步与自动翻译全流程

字幕编辑器从零到一实战教程:视频语音转字幕、时间轴同步与自动翻译全流程 【免费下载链接】subtitleedit the subtitle editor :) 项目地址: https://gitcode.com/gh_mirrors/su/subtitleedit 电影看到一半,字幕比演员的嘴慢了整整两秒&#xff…

2026/8/19 8:50:47 阅读更多 →
三步搞定Win11与Office 2024永久激活:开源KMS激活脚本KMS_VL_ALL_AIO实操指南

三步搞定Win11与Office 2024永久激活:开源KMS激活脚本KMS_VL_ALL_AIO实操指南

三步搞定Win11与Office 2024永久激活:开源KMS激活脚本KMS_VL_ALL_AIO实操指南 【免费下载链接】KMS_VL_ALL_AIO Smart Activation Script 项目地址: https://gitcode.com/gh_mirrors/km/KMS_VL_ALL_AIO 毕业第三年,小周从同事手里接手了一台"…

2026/8/19 8:50:47 阅读更多 →
Pomona:基于Agentic工作流的自动化代码质量改进系统

Pomona:基于Agentic工作流的自动化代码质量改进系统

1. 项目概述:当代码质量成为“日常习惯”在大型金融科技公司,比如彭博社(Bloomberg),代码库的规模动辄数百万行,由数千名工程师共同维护。一个普遍存在的挑战是:如何在不中断核心功能开发、不增…

2026/8/19 8:49:47 阅读更多 →

日新闻

【单片机课程设计/毕业设计】基于 STM32 与 WiFi 模块的室内通风智能管控系统设计 基于 STM32 的人体存在感知自适应风扇控制系统设计(018503)

【单片机课程设计/毕业设计】基于 STM32 与 WiFi 模块的室内通风智能管控系统设计 基于 STM32 的人体存在感知自适应风扇控制系统设计(018503)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/8/19 0:00:30 阅读更多 →
AI如何驱动数学猜想生成:从大语言模型到自动化数学发现

AI如何驱动数学猜想生成:从大语言模型到自动化数学发现

1. 项目概述:当AI开始“猜”数学定理 最近在AI研究圈里,一个名为“Moonshine”的项目引起了不小的讨论。这名字本身就挺有意思,直译是“月光”,但在数学史上,它特指一个神秘而美丽的联系——魔群月光猜想,连…

2026/8/19 0:00:30 阅读更多 →
WarcraftHelper 魔兽争霸3优化实战指南

WarcraftHelper 魔兽争霸3优化实战指南

WarcraftHelper 魔兽争霸3优化实战指南 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 一台刚配的新电脑,跑《魔兽争霸3》却卡成 PPT——这…

2026/8/19 0:02:31 阅读更多 →

周新闻

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

如果你是一名开发者,最近可能已经感受到了AI大模型正在从“玩具”变成“生产力工具”的强烈信号。从代码补全到智能Agent,从本地部署到云端API,我们正处在一个技术栈快速重构的节点。然而,面对层出不穷的模型、框架和工具&#xf…

2026/8/18 9:15:35 阅读更多 →
工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

第四篇:反射——高频能量撞墙之后会发生什么? —— 你以为信号已经过去了,其实它正在回来打你 老Q的现场笔记 第五季,我们正式进入工业神经系统层。这里不再是单个设备的战斗,而是整个工厂“经脉”层面的秩序之战。从这一篇开始,你将第一次看清:看似简单的信号传播,背…

2026/8/18 9:06:28 阅读更多 →
【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。🍎 往期回顾关注个人主页:Matlab科研工作室👇 关注我领取海量matlab电子书和…

2026/8/18 9:04:56 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/19 5:04:55 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/19 7:42:22 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/17 18:55:55 阅读更多 →