设计模式学习(21) 23-19 备忘录模式

文章目录

  • [0. 个人感悟](#0. 个人感悟)
  • [1. 概念](#1. 概念)
  • [2. 适配场景](#2. 适配场景)
    • [2.1 适合的场景](#2.1 适合的场景)
    • [2.2 常见场景举例](#2.2 常见场景举例)
  • [3. 实现方法](#3. 实现方法)
    • [3.1 实现思路](#3.1 实现思路)
    • [3.2 UML类图](#3.2 UML类图)
    • [3.3 代码示例](#3.3 代码示例)
  • [4. 优缺点](#4. 优缺点)
    • [4.1 优点](#4.1 优点)
    • [4.2 缺点](#4.2 缺点)
  • [5. 源码分析](#5. 源码分析)
    • [5.1 Java Swing中的UndoManager](#5.1 Java Swing中的UndoManager)

0. 个人感悟

  • 备忘录模式的场景也比较专。适合进行备份、恢复
  • 模式优点很明显,状态保存于业务逻辑分离,实现解耦
  • 一个注意的点是窄接口设计,Memento(备忘录)内部保存了Originator(原发器)的状态,但这个状态不应该被除了Originator之外的其他对象(特别是Caretaker)直接访问或修改,以保护状态的封装性。具体可以看看示例代码

1. 概念

英文定义(《设计模式:可复用面向对象软件的基础》)

Without violating encapsulation, capture and externalize an object,t internal state so that the object can be restored this state later.

中文翻译

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后可以将对象恢复到原先保存的状态。

理解

  • 备忘录模式提供了一种状态恢复机制,允许对象回到某个历史状态
  • 它通过独立的对象(备忘录)来存储状态,而不是由原对象自己保存
  • 实现了状态保存与业务逻辑的分离,原对象专注于业务,管理者专注于状态管理

2. 适配场景

2.1 适合的场景

  1. 需要撤销/重做功能:如文本编辑器、绘图软件的操作历史
  2. 需要保存快照/检查点:如游戏存档、系统配置备份
  3. 状态转换复杂:如工作流引擎的状态管理
  4. 需要事务回滚:如数据库操作的事务管理
  5. 原型状态保存:如复杂对象构造过程中的中间状态保存
  6. 需要版本控制:如文档编辑的历史版本管理

2.2 常见场景举例

  1. IDE开发环境:Eclipse/IntelliJ IDEA的代码编辑撤销功能
  2. 绘图软件:Photoshop的历史记录面板
  3. 游戏开发:游戏进度存档/读档系统
  4. 表单应用:多步表单的回退功能
  5. 配置管理:系统配置的备份与恢复
  6. 数据库系统:事务的rollback机制

3. 实现方法

3.1 实现思路

  1. 识别状态数据:确定需要保存的内部状态字段
  2. 创建Memento类:设计存储状态的数据结构
  3. 在Originator中添加状态管理
    • 创建保存状态的方法(saveToMemento()
    • 创建恢复状态的方法(restoreFromMemento()
  4. 设计Caretaker类
    • 决定存储策略(栈、列表、树等)
    • 实现状态管理逻辑(撤销、重做、清空等)
  5. 控制访问权限
    • 对Originator开放宽接口(可读写状态)
    • 对Caretaker开放窄接口(只读元数据)

3.2 UML类图

角色说明:

  • Originator(原发器):需要保存状态的对象,负责创建和恢复备忘录
  • Memento(备忘录):存储Originator内部状态,通常设计为不可变对象
  • Caretaker(管理者):负责保存和管理备忘录,但不能操作备忘录内容

3.3 代码示例

背景

以文件编辑器为例,简化业务,只有保存和撤销功能
设计

原发器

java 复制代码
public class TextEditor {  
    private String content;  
  
    public String getContent() {  
        return content;  
    }  
  
    public void setContent(String content) {  
        this.content = content;  
    }  
  
    public TextEditor(String content) {  
        this.content = content;  
    }  
  
    /**  
     * @description 创建备忘  
     * @author bigHao  
     * @date 2026/1/27  
     * @return designpattern.memento.pac.TextMemento 备忘录  
     **/  
    public TextMemento createMemento() {  
        return new TextMemento(content);  
    }  
  
    /**  
     * @description 恢复备忘  
     * @author bigHao  
     * @date 2026/1/27  
     * @param memento 备忘录  
     **/  
    public void restoreMemento(TextMemento memento) {  
        this.content = memento.getContent();  
    }  
}

备忘录

  • 注意权限控制。备忘录只有原发器可以创建和访问内容。实际操作中将二者放到一个包下,相关方法使用包权限
java 复制代码
public class TextMemento {  
    private String content;  
  
    // 包访问权限,仅对Originator开放状态访问  
    TextMemento(String content) {  
        this.content = content;  
    }  
  
    String getContent() {  
        return content;  
    }  
}

管理者

java 复制代码
public class Caretaker {  
    private static final int MAX_SIZE = 100;  
  
    // 业务场景刚好使用stack 先进后出  
    private Stack<TextMemento> history = new Stack<>();  
  
    public void push(TextMemento memento) {  
        // 限制下长度  
        limitHistory();  
  
        this.history.push(memento);  
    }  
  
    public TextMemento pop() {  
        return this.history.pop();  
    }  
      
    private void limitHistory() {  
        if (history.size() > MAX_SIZE) {  
            history.removeFirst();  
        }  
    }  
  
}

测试

java 复制代码
public class Client {  
    static void main() {  
        // 创建  
        String content = "备忘录模式笔记";  
        TextEditor textEditor = new TextEditor(content);  
        printInfo(textEditor);  
  
        Caretaker caretaker = new Caretaker();  
  
        // 安全考虑,TextMemento是包访问权限,和TextEditor同包,其它地方无法直接创建  
        // new TextMemento("");  会报错  
  
        // 保存1  
        caretaker.push(textEditor.createMemento());  
  
        content += "包括三个角色,分别是原发器";  
        textEditor.setContent(content);  
        caretaker.push(textEditor.createMemento());  
        printInfo(textEditor);  
  
        // 保存2  
        content += "、备忘录";  
        textEditor.setContent(content);  
        caretaker.push(textEditor.createMemento());  
        printInfo(textEditor);  
  
        // 保存3  
        content += "和管理者";  
        textEditor.setContent(content);  
        caretaker.push(textEditor.createMemento());  
        printInfo(textEditor);  
  
        // 编辑  
        content += " 功能分别是";  
        textEditor.setContent(content);  
        printInfo(textEditor);  
  
        // 撤销1  
        System.out.println("===撤销===");  
        textEditor.restoreMemento(caretaker.pop());  
        printInfo(textEditor);  
  
        // 撤销2  
        System.out.println("===撤销===");  
        textEditor.restoreMemento(caretaker.pop());  
        printInfo(textEditor);  
  
    }  
  
    private static void printInfo(TextEditor textEditor) {  
        System.out.println("当前内容: " + textEditor.getContent());  
    }  
}

输出

复制代码
当前内容: 备忘录模式笔记
当前内容: 备忘录模式笔记包括三个角色,分别是原发器
当前内容: 备忘录模式笔记包括三个角色,分别是原发器、备忘录
当前内容: 备忘录模式笔记包括三个角色,分别是原发器、备忘录和管理者
当前内容: 备忘录模式笔记包括三个角色,分别是原发器、备忘录和管理者 功能分别是
===撤销===
当前内容: 备忘录模式笔记包括三个角色,分别是原发器、备忘录和管理者
===撤销===
当前内容: 备忘录模式笔记包括三个角色,分别是原发器、备忘录

4. 优缺点

4.1 优点

高内聚低耦合

  • 职责分离:状态保存与业务逻辑解耦,Originator只关注核心功能
  • 封装性:通过窄接口模式保护状态不被Caretaker误操作

复用性

  • 状态管理可复用:Caretaker可被多个Originator复用
  • 存储策略灵活:支持栈、列表、数据库等多种存储方式

可读性

  • 意图明确:代码明确表达了"保存状态"和"恢复状态"的意图
  • 结构清晰:三个角色分工明确,易于理解

维护性

  • 易于扩展:可轻松添加新的存储策略或状态类型
  • 修改隔离:状态存储逻辑变化不影响Originator

稳定性

  • 状态一致:确保状态恢复的正确性
  • 异常安全:状态操作失败时可恢复到之前状态

4.2 缺点

资源消耗

  • 内存占用:大量状态保存可能导致内存消耗过大
  • 性能开销:频繁的状态保存/恢复可能影响性能

设计复杂度

  • 额外类:增加了Memento和Caretaker类
  • 窄接口实现复杂:需要小心设计访问权限

状态同步

  • 深拷贝问题:复杂对象的深拷贝可能实现复杂
  • 版本兼容:状态数据结构变化时,旧备忘录可能无法恢复

5. 源码分析

5.1 Java Swing中的UndoManager

代码位置javax.swing.undo.UndoManager

角色分析

java 复制代码
// Originator:各种Document类(如PlainDocument)
public class PlainDocument extends AbstractDocument {
    // 创建UndoableEdit(备忘录)
    protected UndoableEdit createUndoableEdit(ElementChange ec) {
        return new DocumentUndoableEdit(ec);
    }
}

// Memento:UndoableEdit接口及其实现
public interface UndoableEdit {
    void undo() throws CannotUndoException;
    void redo() throws CannotRedoException;
    boolean canUndo();
    boolean canRedo();
}

// Caretaker:UndoManager类
public class UndoManager extends CompoundEdit implements UndoableEditListener {
    private Vector<UndoableEdit> edits;  // 存储备忘录
    private int indexOfNextAdd;          // 下一个添加位置
    
    public void undo() throws CannotUndoException {
        // 执行撤销操作
        UndoableEdit edit = edits.elementAt(--indexOfNextAdd);
        edit.undo();
    }
    
    public void redo() throws CannotRedoException {
        // 执行重做操作
        UndoableEdit edit = edits.elementAt(indexOfNextAdd);
        edit.redo();
        indexOfNextAdd++;
    }
}

参考:

相关推荐
檀越剑指大厂17 分钟前
【Elasticsearch系列廿】Logstash 学习
大数据·学习·elasticsearch
woodykissme2 小时前
渐开线圆柱齿轮几何计算全解析(一):从理论到实践的完整指南
学习·齿轮·齿轮加工
Asher阿舍技术站3 小时前
【AI基础学习系列】四、Prompt基础知识
人工智能·学习·prompt
CappuccinoRose4 小时前
CSS 语法学习文档(十三)
前端·css·学习·postcss·模块化·预处理器
im_AMBER4 小时前
Leetcode 121 翻转二叉树 | 二叉树中的最大路径和
数据结构·学习·算法·leetcode
『往事』&白驹过隙;5 小时前
浅谈PC开发中的设计模式搬迁到ARM开发
linux·c语言·arm开发·设计模式·iot
じ☆冷颜〃5 小时前
随机微分层论:统一代数、拓扑与分析框架下的SPDE论述
笔记·python·学习·线性代数·拓扑学
闻哥5 小时前
23种设计模式深度解析:从原理到实战落地
java·jvm·spring boot·设计模式·面试
前路不黑暗@7 小时前
Java项目:Java脚手架项目的地图服务(十)
java·数据库·spring boot·笔记·学习·spring cloud·maven
资深web全栈开发8 小时前
设计模式之享元模式 (Flyweight Pattern)
设计模式·享元模式