前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

一、备忘录模式(Memento Pattern)

1. 模式概述

备忘录模式 是一种 行为型设计模式,它在不破坏封装性的前提下,捕获并外部化一个对象的内部状态,以便以后可将该对象恢复到原先保存的状态。

核心思想“时光倒流” —— 保存快照,随时回滚。

备忘录模式 = 安全快照 + 时光回溯

  • 它不是“备份数据库”,而是“代码级状态保险”。

类比理解:

  • 游戏存档:闯关前保存进度,失败后读档重来;
  • VMware 快照:虚拟机状态冻结,可随时回退;
  • Word 撤销(Undo):每一步操作生成快照,支持多级回退。

2. 使用场景

备忘录模式适用于以下情况:

  • 需要 实现撤销(Undo)/重做(Redo) 功能;
  • 对象状态 复杂且需完整保存,但又不能暴露内部细节;
  • 需要 在不破坏封装性 的前提下保存/恢复状态;
  • 工作流引擎中支持 回退到历史节点

典型应用

  • 文本编辑器的撤销栈;
  • 游戏存档系统;
  • 事务回滚机制(轻量级);
  • 表单草稿自动保存。

3. 示例程序:游戏角色状态管理(优化版)

1)备忘录 GameState

/**
 * 备忘录(Memento)
 * 仅 Originator 可读写,对外提供有限访问(可选)
 */
public final class GameState {
    private final String level;
    private final float x;
    private final float y;
    private final int health;

    public GameState(String level, float x, float y, int health) {
        this.level = level;
        this.x = x;
        this.y = y;
        this.health = health;
    }

    // 仅用于 Originator 恢复(包级私有或通过内部类实现更佳)
    String getLevel() { return level; }
    float getX() { return x; }
    float getY() { return y; }
    int getHealth() { return health; }

    @Override
    public String toString() {
        return String.format("Level=%s, Pos=(%.1f, %.1f), HP=%d", level, x, y, health);
    }
}

2)发起人 GameCharacter

/**
 * 发起人(Originator)
 * 负责创建和恢复备忘录
 */
public class GameCharacter {
    private String level = "1-1";
    private float x = 0.0f;
    private float y = 0.0f;
    private int health = 100;

    // 创建备忘录(保存当前状态)
    public GameState saveState() {
        return new GameState(level, x, y, health);
    }

    // 从备忘录恢复状态
    public void restoreState(GameState state) {
        this.level = state.getLevel();
        this.x = state.getX();
        this.y = state.getY();
        this.health = state.getHealth();
    }

    // 模拟游戏行为
    public void move(float dx, float dy) {
        this.x += dx;
        this.y += dy;
    }

    public void takeDamage(int damage) {
        this.health = Math.max(0, this.health - damage);
    }

    @Override
    public String toString() {
        return String.format("当前状态: Level=%s, Pos=(%.1f, %.1f), HP=%d", level, x, y, health);
    }
}

3)管理者 HistoryManager

import java.util.*;

/**
 * 管理者(Caretaker)
 * 仅负责存储和获取备忘录,不操作其内容
 */
public class HistoryManager {
    private final Deque<GameState> history = new ArrayDeque<>();
    private static final int MAX_HISTORY = 10; // 限制历史数量

    public void save(GameState state) {
        if (history.size() >= MAX_HISTORY) {
            history.removeLast(); // 移除最旧记录
        }
        history.addFirst(state);
    }

    public GameState undo() {
        if (history.isEmpty()) {
            throw new IllegalStateException("无历史记录可恢复");
        }
        GameState current = history.removeFirst();
        return history.isEmpty() ? null : history.peekFirst();
    }

    public List<GameState> getHistory() {
        return new ArrayList<>(history);
    }
}

4)客户端测试 Main

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

        // 初始状态
        history.save(player.saveState());
        System.out.println(player);

        // 行动1:移动
        player.move(10, 5);
        history.save(player.saveState());
        System.out.println(player);

        // 行动2:受伤
        player.takeDamage(30);
        history.save(player.saveState());
        System.out.println(player);

        // 撤销一次(回退到移动后、受伤前)
        GameState prevState = history.undo();
        if (prevState != null) {
            player.restoreState(prevState);
        }
        System.out.println("\n【撤销后】" + player);

        // 查看历史
        System.out.println("\n历史记录:");
        history.getHistory().forEach(System.out::println);
    }
}

输出示例:

当前状态: Level=1-1, Pos=(0.0, 0.0), HP=100
当前状态: Level=1-1, Pos=(10.0, 5.0), HP=100
当前状态: Level=1-1, Pos=(10.0, 5.0), HP=70

【撤销后】当前状态: Level=1-1, Pos=(10.0, 5.0), HP=100

历史记录:
Level=1-1, Pos=(10.0, 5.0), HP=100
Level=1-1, Pos=(0.0, 0.0), HP=100

5)模式角色

  • 封装性:Caretaker 只能传递 Memento,不能访问其内部数据;
  • 快照隔离:每个 Memento 是独立状态副本,互不影响。
角色 职责 示例
Originator(发起人) 创建备忘录、从备忘录恢复自身状态 GameCharacter
Memento(备忘录) 存储 Originator 的内部状态(通常为不可变) GameStateSnapshot
Caretaker(管理者) 负责保存和管理 Memento,但不能修改或查看其内容 HistoryManager

4. FQA

1)备忘录模式 vs 原型模式

对比项 备忘录模式 原型模式
目的 保存/恢复对象部分或全部状态(用于回滚) 复制对象(避免重复创建开销)
是否生成新实例 恢复时修改原对象状态 返回全新克隆对象
封装性 高(Caretaker 无法访问内部状态) 依赖 clone() 实现
典型用途 Undo/Redo、快照 对象池、配置模板复制

2)模式优点

  • 提供状态回滚能力,支持撤销/重做;
  • 保持封装性:外部无法直接访问对象内部状态;
  • 状态隔离:每个快照独立,互不影响。

3)注意事项

  • 内存消耗大:保存大量快照可能占用过多内存;
  • 性能开销:频繁保存/恢复影响效率;
  • 不适用于大对象:若状态数据庞大,需考虑序列化或增量快照。