备忘录模式(手把手讲解)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目:《Spring AI 项目实战》 正在持续爆肝中,基于 Spring AI + Spring Boot 3.x + JDK 21..., 点击查看 ;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观

前言:为什么需要备忘录模式?

在软件开发中,我们经常需要保存对象的某个状态,并在后续需要时恢复它。例如,游戏中的“存档”功能、文档编辑器的“撤销/重做”操作,或者数据库事务的回滚机制。然而,直接保存对象状态可能带来复杂性,比如如何避免暴露内部细节,如何管理多个版本的状态,以及如何高效地恢复数据。

备忘录模式(Memento Pattern)正是为了解决这类问题而诞生的设计模式。它通过一种“封装”的方式,将对象的内部状态保存到一个独立的对象中,从而实现状态的灵活保存与恢复。本文将从基础概念、工作原理、实际案例到代码实现,逐步解析这一模式的精髓。


备忘录模式的核心概念

1. 模式定义与角色划分

备忘录模式属于行为型模式,其核心是通过一个“备忘录”对象来捕获并保存另一个对象(发起人)的内部状态,同时允许在需要时恢复该状态。

模式涉及三个关键角色:

  1. 发起人(Originator)

    • 负责创建备忘录对象,并将自身状态保存到备忘录中。
    • 同时,发起人可以利用备忘录对象恢复之前的状态。
  2. 备忘录(Memento)

    • 存储发起人的内部状态。
    • 对外仅暴露必要的接口,以保护发起人的敏感数据不被直接修改。
  3. 管理者(Caretaker)

    • 负责保管备忘录对象,并根据需要传递备忘录给发起人。
    • 管理者通常不关心备忘录的具体内容,仅负责存储和管理。

2. 模式的核心思想

备忘录模式的核心是封装状态转移。通过将状态保存在独立的备忘录对象中,发起人可以专注于自身业务逻辑,而管理者则专注于状态的存储与传递。这种设计避免了直接暴露对象的内部细节,同时确保了状态管理的灵活性和安全性。


备忘录模式的工作原理详解

1. 状态保存与恢复的流程

备忘录模式的工作流程可以分为以下步骤:

  1. 创建备忘录:发起人调用方法生成一个备忘录对象,将当前状态数据封装到该对象中。
  2. 传递备忘录:发起人将备忘录对象传递给管理者,由管理者负责存储或管理。
  3. 恢复状态:当需要恢复历史状态时,管理者将备忘录对象返还给发起人,发起人通过该对象恢复自身状态。

2. 一个形象的比喻:游戏存档系统

假设我们开发一款游戏,玩家需要随时保存进度并能在后续加载。此时,备忘录模式可以这样应用:

  • 发起人(Originator):游戏引擎,负责管理玩家当前的关卡、血量、金币等状态。
  • 备忘录(Memento):保存玩家当前所有状态的“存档文件”。
  • 管理者(Caretaker):游戏的存档管理器,负责存储多个存档文件,并在玩家选择时提供对应的存档。

通过这种方式,游戏引擎无需关心存档如何存储,存档管理器也无需了解存档的具体内容,两者通过备忘录对象协作,实现了状态的灵活管理。


实现备忘录模式的步骤与代码示例

1. 步骤解析

  1. 定义发起人(Originator):创建一个类,包含需要保存的内部状态,并提供方法生成备忘录对象。
  2. 定义备忘录(Memento):创建一个类,用于封装发起人的状态数据。该类应限制对外暴露的接口,仅允许发起人访问敏感数据。
  3. 定义管理者(Caretaker):创建一个类,负责存储和管理备忘录对象。

2. Java语言的代码示例

以下是一个简单的文档编辑器案例,演示如何使用备忘录模式实现“撤销”功能:

发起人(Originator)

public class Document {  
    private String content; // 文档内容  
    private int version; // 版本号  

    // 生成备忘录  
    public Memento saveToMemento() {  
        return new Memento(content, version);  
    }  

    // 恢复备忘录  
    public void restoreFromMemento(Memento memento) {  
        this.content = memento.getContent();  
        this.version = memento.getVersion();  
    }  

    // 其他业务方法...  
}  

备忘录(Memento)

public class Memento {  
    private final String content;  
    private final int version;  

    private Memento(String content, int version) {  
        this.content = content;  
        this.version = version;  
    }  

    // 仅暴露必要信息给发起人  
    String getContent() { return content; }  
    int getVersion() { return version; }  

    // 禁止外部直接创建备忘录  
    public static class MementoBuilder {  
        public Memento createMemento(String content, int version) {  
            return new Memento(content, version);  
        }  
    }  
}  

管理者(Caretaker)

public class HistoryManager {  
    private List<Memento> mementos = new ArrayList<>();  

    public void save(Memento memento) {  
        mementos.add(memento);  
    }  

    public Memento undo() {  
        if (!mementos.isEmpty()) {  
            return mementos.remove(mementos.size() - 1);  
        }  
        return null;  
    }  
}  

使用示例

public class Main {  
    public static void main(String[] args) {  
        Document doc = new Document();  
        HistoryManager history = new HistoryManager();  

        // 初始状态  
        doc.setContent("Hello World!");  
        history.save(doc.saveToMemento());  

        // 修改内容  
        doc.editContent("Hello, this is a new version.");  

        // 撤销到上一版本  
        Memento lastVersion = history.undo();  
        doc.restoreFromMemento(lastVersion);  

        System.out.println("Current content: " + doc.getContent());  
    }  
}  

备忘录模式的典型应用场景

1. 游戏中的存档系统

玩家可以在任何时间点保存游戏进度(如角色位置、装备、任务进度),并在后续加载这些存档。

2. 文档编辑器的撤销/重做功能

用户编辑文档时,系统自动保存每个操作前后的状态,支持回退到任意历史版本。

3. 数据库事务的回滚机制

在事务执行过程中,系统可能需要记录中间状态,以便在发生错误时回滚到安全点。

4. 配置管理

保存软件的配置状态,方便用户在误操作后快速恢复到之前的配置。


备忘录模式的优势与局限性

优势

  1. 解耦状态管理:发起人无需关心状态如何保存,管理者无需了解状态细节,两者通过备忘录协作。
  2. 安全性:备忘录对象对外隐藏了发起人的敏感数据,仅允许发起人访问。
  3. 灵活性:可以保存多个备忘录,支持多级撤销或恢复。

局限性

  1. 内存占用:保存大量备忘录可能导致内存消耗过高,需权衡存储数量与性能。
  2. 序列化问题:若状态数据复杂(如包含对象引用),需确保备忘录对象能正确序列化与反序列化。
  3. 适用场景有限:仅适用于需要频繁保存和恢复状态的场景,对于简单状态管理可能过度设计。

与其他模式的对比:备忘录 vs 命令模式

备忘录模式与命令模式(Command Pattern)都涉及状态的保存与操作,但核心目标不同:
| 对比项 | 备忘录模式 | 命令模式 |
|------------------|-----------------------------------|---------------------------------|
| 核心目标 | 保存对象状态,支持恢复 | 封装操作为对象,支持撤销/重做 |
| 状态存储 | 通过备忘录对象存储原始数据 | 通过命令对象存储操作指令 |
| 触发恢复 | 主动调用恢复方法 | 通过执行撤销或重做操作 |

例如,在文档编辑器中:

  • 备忘录模式直接保存文档内容和版本号,支持直接回退到某个状态。
  • 命令模式则记录每次编辑操作(如“插入文本”“删除段落”),通过反向执行命令实现撤销。

两种模式可结合使用:例如,备忘录保存状态快照,而命令记录具体操作,从而实现更细粒度的撤销功能。


总结与实践建议

备忘录模式通过分离状态保存与业务逻辑,为复杂系统的状态管理提供了优雅的解决方案。对于开发者而言,掌握这一模式能显著提升代码的可维护性和扩展性。

关键实践建议:

  1. 明确需求:仅在需要频繁保存或恢复状态时使用,避免过度设计。
  2. 保护敏感数据:确保备忘录对象仅暴露必要接口,防止数据泄露。
  3. 管理内存:根据场景限制备忘录的数量,或采用策略(如仅保存最近N个版本)。

通过本文的讲解与案例,希望读者能够理解备忘录模式的原理,并在实际开发中灵活应用这一模式,解决状态管理的痛点。


(全文约 1800 字)

最新发布