二十三种设计模式(十九)--备忘录模式

备忘录模式(Memento)

备忘录模式主要实现的时在一个运行的对象时, 能够在不破坏封装性的前提下备份, 将备份的数据保存到对象外的另一个对象中, 使得运行对象随时可以恢复到备份时的状态.

关键要注意的一点是不破坏类的封装性, 备份对象在外部无法改变

如下是一个经典应用示例

由于要做json序列化存储, 所以前提要引入 Jackson 依赖包

xml 复制代码
<!-- 引入 Jackson 依赖 -->
<dependency>
     <groupId>com.fasterxml.jackson.core</groupId>
     <artifactId>jackson-databind</artifactId>
     <version>2.17.0</version>
 </dependency>
 <dependency>
     <groupId>com.fasterxml.jackson.core</groupId>
     <artifactId>jackson-annotations</artifactId>
     <version>2.17.0</version>
 </dependency>
 <dependency>
     <groupId>com.fasterxml.jackson.core</groupId>
     <artifactId>jackson-core</artifactId>
     <version>2.17.0</version>
 </dependency>
java 复制代码
// 备份类, 保存主类所有属性
final class StrategyMemento {
    // 由于进行了json序列化存储, 所以, 主类的所有状态都在state中
    private final String state;
    // 备份类中额外存储备份时间
    private final LocalDateTime saveTime;

    StrategyMemento(String state) {
        this.state = state;
        this.saveTime = LocalDateTime.now();
    }

    // 用于恢复备份的接口
    public String getState() { return state; }
    public LocalDateTime getSaveTime() { return saveTime; }
}

// 备份版本管理类, 将所有的备份版本使用栈结构存储, 最后的备份最先弹出
class MementoManager {
    private final Stack<StrategyMemento> history = new Stack<>();

    public void push(StrategyMemento memento) {
        history.push(memento);
    }

    public StrategyMemento pop() {
        if (history.isEmpty()) {
            System.out.println("no more memento to pop.");
            return null;
        }

        return history.pop();
    }
}


class Originator {
    // 用来存储未序列化的属性列表
    private static final ObjectMapper mapper = new ObjectMapper();

    private String param_1;
    private int param_2;
    private Map<String, Object> param_3;
    private List<String> param_4;

    // 所有属性必须都有getter方法才能完成序列化
    public String getParam_1() { return param_1; }
    public int getParam_2() { return param_2; }
    public Map<String, Object> getParam_3() { return param_3; }
    public List<String> getParam_4() { return param_4; }

    public void modify(String a, int b, Map<String, Object> c, List<String> d) {
        this.param_1 = a;
        this.param_2 = b;
        this.param_3 = c;
        this.param_4 = d;
    }

    // 执行备份, 序列化存储, 将所有属性保存到StrategyMemento类对象中
    public StrategyMemento save() {
        try {
            String stateJson = mapper.writeValueAsString(this);
            return new StrategyMemento(stateJson);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("备份失败:" + e.getMessage());
        }
    }

    // 恢复备份
    public void restore(StrategyMemento backup) {
        try {
            Originator obj = mapper.readValue(backup.getState(), Originator.class);
            this.param_1 = obj.param_1;
            this.param_2 = obj.param_2;
            this.param_3 = obj.param_3;
            this.param_4 = obj.param_4;
            System.out.println(">>> 已恢复到" + backup.getSaveTime().toString() + "版本");
        } catch (IOException e) {
            throw new RuntimeException("恢复失败: " + e.getMessage());
        }
    }
}

总体思想就是

  1. 定义一个备份类, 用来存储每一个时刻的属性备份
  2. 在主类中增加save和restore这样的方法, 用来保存和恢复备份, save时生成备份对象, restore时从备份对象中恢复
  3. 定义一个Manager类用来存储所有的备份, 可以用栈的方式来存储, 也可以用List或者Map, 根据业务需求来选择适当的容器
    通过上述步骤, 就实现了一个备忘录模式.
    备忘录模式要注意, 备份类生成的对象, 是不可变更的, 它一旦生成, 就只能通过restore方法去恢复状态, 这是重点
    另外一点是, 可以采用各种方式备份, 但是一定要注意是深拷贝的备份, 不能是浅拷贝

下面调用上述代码进行测试:

java 复制代码
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.util.*;

import java.time.LocalDateTime;

public class MementoPattern {
    public static void main(String[] args) {
        // 第一次数据修改
        Originator originObj = new Originator();
        Map<String, Object> map = new HashMap<>();
        map.put("obj-1", 100);
        map.put("obj-2", 200);
        List<String> list = new ArrayList<>();
        list.add("string 1");
        list.add("string 2");
        originObj.modify("origin-1", 3, map, list);
        // 执行备份
        MementoManager mementoManager = new MementoManager();
        mementoManager.push(originObj.save());

        // 第二次数据修改
        map.put("obj-3", 300);
        list.add("string 3");
        originObj.modify("origin-2", 4, map, list);
        mementoManager.push(originObj.save());

        // 第三次修改数据
        map.put("obj-2", 220);
        map.remove("obj-3");
        list.remove(1);
        list.add("string 4");
        originObj.modify("origin-3", 5, map, list);
        mementoManager.push(originObj.save());

        // 导出变更历史
        StrategyMemento backup_3 = mementoManager.pop();
        StrategyMemento backup_2 = mementoManager.pop();
        StrategyMemento backup_1 = mementoManager.pop();

        System.out.println("最新版本:");
        System.out.print("param_1:" + originObj.getParam_1() + " | ");
        System.out.print("param_2:" + originObj.getParam_2() + " | ");
        System.out.print("param_3:" + originObj.getParam_3() + " | ");
        System.out.println("param_4:" + originObj.getParam_4());
        System.out.println("===================第一次修改 ===================");
        originObj.restore(backup_1);
        System.out.print("param_1:" + originObj.getParam_1() + " | ");
        System.out.print("param_2:" + originObj.getParam_2() + " | ");
        System.out.print("param_3:" + originObj.getParam_3() + " | ");
        System.out.println("param_4:" + originObj.getParam_4());

        System.out.println("===================第二次修改 ===================");
        originObj.restore(backup_2);
        System.out.print("param_1:" + originObj.getParam_1() + " | ");
        System.out.print("param_2:" + originObj.getParam_2() + " | ");
        System.out.print("param_3:" + originObj.getParam_3() + " | ");
        System.out.println("param_4:" + originObj.getParam_4());

        System.out.println("===================第三次修改 ===================");
        originObj.restore(backup_3);
        System.out.print("param_1:" + originObj.getParam_1() + " | ");
        System.out.print("param_2:" + originObj.getParam_2() + " | ");
        System.out.print("param_3:" + originObj.getParam_3() + " | ");
        System.out.println("param_4:" + originObj.getParam_4());
    }
}

运行结果:

复制代码
param_1:origin-3 | param_2:5 | param_3:{obj-1=100, obj-2=220} | param_4:[string 1, string 3, string 4]
===================第一次修改 ===================
>>> 已恢复到2026-01-03T16:14:11.223741700版本
param_1:origin-1 | param_2:3 | param_3:{obj-1=100, obj-2=200} | param_4:[string 1, string 2]
===================第二次修改 ===================
>>> 已恢复到2026-01-03T16:14:11.224278800版本
param_1:origin-2 | param_2:4 | param_3:{obj-1=100, obj-2=200, obj-3=300} | param_4:[string 1, string 2, string 3]
===================第三次修改 ===================
>>> 已恢复到2026-01-03T16:14:11.224891400版本
param_1:origin-3 | param_2:5 | param_3:{obj-1=100, obj-2=220} | param_4:[string 1, string 3, string 4]

通过输出可以看到, 每一次的恢复都是完美的

相关推荐
小兔崽子去哪了1 天前
Spring AOP 专题
java·spring boot·spring
菩提祖师_1 天前
微信小程序茶园茶农文化交流系统
java·javascript·网络
强子感冒了1 天前
Java 泛型的学习笔记
java·笔记·学习
萧曵 丶1 天前
ThreadLocal 原理及内存泄漏详解
java·多线程·threadlocal
两个蝴蝶飞1 天前
Java量化系列(十二):收盘自动存K线图!日K/分钟K一键抓取,复盘再也不用翻软件
java·数据库·oracle
计算机徐师兄1 天前
Java基于微信小程序的社区养老保障系统
java·微信小程序·社区养老保障系统小程序·java社区养老保障系统小程序·社区养老保障系统微信小程序·社区养老保障微信小程序
GawynKing1 天前
SqlRest数据服务项目基于Idea开发环境搭建
java·ide·intellij-idea
Wang15301 天前
Java构造函数
java
天天开心a1 天前
【Vue错误修复】Vue模块居中问题
java·前端·javascript·vue.js·前端框架·vue