备忘录模式 (Memento Pattern)

备忘录模式 (Memento Pattern)

概述

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

意图

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

适用场景

  • 必须保存一个对象在某一个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态
  • 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性

结构

复制代码
┌─────────────┐          ┌─────────────┐
│  Caretaker  │──────────>│  Memento    │
├─────────────┤          ├─────────────┤
│ - memento   │          │ - state    │
└─────────────┘          │ + getState() │
                         │ + setState() │
                         └─────────────┘
                                 ▲
                                 │
┌─────────────┐          ┌─────────────┐
│   Originator│<─────────│   Client    │
├─────────────┤          ├─────────────┤
│ - state     │          │             │
│ + createMemento()│      └─────────────┘
│ + restoreFromMemento()│
└─────────────┘

参与者

  • Memento:备忘录存储原发器对象的内部状态,原发器根据需要决定备忘录存储原发器的哪些内部状态
  • Originator:原发器创建一个备忘录,用以记录当前时刻它的内部状态,并使用备忘录恢复内部状态
  • Caretaker:负责人负责保存好备忘录,但是不能对备忘录的内容进行操作或检查

示例代码

下面是一个完整的备忘录模式示例,以游戏角色状态保存为例:

java 复制代码
// Memento - 备忘录类
public class GameRoleMemento {
    private int vitality; // 生命力
    private int attack;   // 攻击力
    private int defense;  // 防御力
    
    public GameRoleMemento(int vitality, int attack, int defense) {
        this.vitality = vitality;
        this.attack = attack;
        this.defense = defense;
    }
    
    public int getVitality() {
        return vitality;
    }
    
    public int getAttack() {
        return attack;
    }
    
    public int getDefense() {
        return defense;
    }
}

// Originator - 原发器类
public class GameRole {
    private int vitality; // 生命力
    private int attack;   // 攻击力
    private int defense;  // 防御力
    
    // 初始化游戏角色
    public void init() {
        this.vitality = 100;
        this.attack = 100;
        this.defense = 100;
    }
    
    // 战斗
    public void fight() {
        this.vitality = 0;
        this.attack = 0;
        this.defense = 0;
    }
    
    // 保存角色状态
    public GameRoleMemento saveState() {
        return new GameRoleMemento(vitality, attack, defense);
    }
    
    // 恢复角色状态
    public void restoreState(GameRoleMemento memento) {
        this.vitality = memento.getVitality();
        this.attack = memento.getAttack();
        this.defense = memento.getDefense();
    }
    
    // 显示状态
    public void displayState() {
        System.out.println("角色当前状态:");
        System.out.println("生命力: " + vitality);
        System.out.println("攻击力: " + attack);
        System.out.println("防御力: " + defense);
    }
}

// Caretaker - 负责人类
public class RoleStateCaretaker {
    private GameRoleMemento memento;
    
    public void saveMemento(GameRoleMemento memento) {
        this.memento = memento;
    }
    
    public GameRoleMemento getMemento() {
        return memento;
    }
}

// Client - 客户端
public class Client {
    public static void main(String[] args) {
        // 创建游戏角色
        GameRole role = new GameRole();
        
        // 初始化角色
        role.init();
        System.out.println("------ 初始状态 ------");
        role.displayState();
        
        // 保存状态
        RoleStateCaretaker caretaker = new RoleStateCaretaker();
        caretaker.saveMemento(role.saveState());
        
        // 战斗
        role.fight();
        System.out.println("\n------ 战斗后状态 ------");
        role.displayState();
        
        // 恢复状态
        role.restoreState(caretaker.getMemento());
        System.out.println("\n------ 恢复后状态 ------");
        role.displayState();
    }
}

另一个示例 - 文本编辑器

java 复制代码
import java.util.ArrayList;
import java.util.List;

// Memento - 备忘录类
public class TextEditorMemento {
    private String content;
    private int cursorPosition;
    
    public TextEditorMemento(String content, int cursorPosition) {
        this.content = content;
        this.cursorPosition = cursorPosition;
    }
    
    public String getContent() {
        return content;
    }
    
    public int getCursorPosition() {
        return cursorPosition;
    }
}

// Originator - 原发器类
public class TextEditor {
    private String content = "";
    private int cursorPosition = 0;
    
    public void type(String text) {
        content += text;
        cursorPosition += text.length();
    }
    
    public void delete(int length) {
        int startPos = Math.max(0, cursorPosition - length);
        content = content.substring(0, startPos) + content.substring(cursorPosition);
        cursorPosition = startPos;
    }
    
    public void moveCursor(int position) {
        cursorPosition = Math.max(0, Math.min(content.length(), position));
    }
    
    public TextEditorMemento save() {
        return new TextEditorMemento(content, cursorPosition);
    }
    
    public void restore(TextEditorMemento memento) {
        this.content = memento.getContent();
        this.cursorPosition = memento.getCursorPosition();
    }
    
    public void display() {
        System.out.println("内容: \"" + content + "\"");
        System.out.println("光标位置: " + cursorPosition);
    }
}

// Caretaker - 负责人类
public class TextEditorHistory {
    private List<TextEditorMemento> history = new ArrayList<>();
    private int currentIndex = -1;
    
    public void save(TextEditorMemento memento) {
        // 如果当前不是最新状态,删除后面的历史记录
        if (currentIndex < history.size() - 1) {
            history = history.subList(0, currentIndex + 1);
        }
        
        history.add(memento);
        currentIndex++;
    }
    
    public TextEditorMemento undo() {
        if (currentIndex > 0) {
            currentIndex--;
            return history.get(currentIndex);
        }
        return null;
    }
    
    public TextEditorMemento redo() {
        if (currentIndex < history.size() - 1) {
            currentIndex++;
            return history.get(currentIndex);
        }
        return null;
    }
    
    public boolean canUndo() {
        return currentIndex > 0;
    }
    
    public boolean canRedo() {
        return currentIndex < history.size() - 1;
    }
}

// Client - 客户端
public class Client {
    public static void main(String[] args) {
        // 创建文本编辑器
        TextEditor editor = new TextEditor();
        TextEditorHistory history = new TextEditorHistory();
        
        // 初始状态
        System.out.println("------ 初始状态 ------");
        editor.display();
        history.save(editor.save());
        
        // 输入文本
        System.out.println("\n------ 输入文本 ------");
        editor.type("Hello ");
        editor.type("World!");
        editor.display();
        history.save(editor.save());
        
        // 删除文本
        System.out.println("\n------ 删除文本 ------");
        editor.delete(6);
        editor.display();
        history.save(editor.save());
        
        // 撤销
        System.out.println("\n------ 撤销 ------");
        if (history.canUndo()) {
            editor.restore(history.undo());
            editor.display();
        }
        
        // 再次撤销
        System.out.println("\n------ 再次撤销 ------");
        if (history.canUndo()) {
            editor.restore(history.undo());
            editor.display();
        }
        
        // 重做
        System.out.println("\n------ 重做 ------");
        if (history.canRedo()) {
            editor.restore(history.redo());
            editor.display();
        }
        
        // 再次重做
        System.out.println("\n------ 再次重做 ------");
        if (history.canRedo()) {
            editor.restore(history.redo());
            editor.display();
        }
    }
}

使用序列化实现备忘录模式

java 复制代码
import java.io.*;

// Originator - 原发器类
public class SerializableOriginator implements Serializable {
    private String state;
    
    public SerializableOriginator(String state) {
        this.state = state;
    }
    
    public String getState() {
        return state;
    }
    
    public void setState(String state) {
        this.state = state;
    }
    
    // 保存状态到字节数组
    public byte[] saveStateToByteArray() {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);
            oos.close();
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
    
    // 从字节数组恢复状态
    public void restoreStateFromByteArray(byte[] data) {
        try {
            ByteArrayInputStream bais = new ByteArrayInputStream(data);
            ObjectInputStream ois = new ObjectInputStream(bais);
            SerializableOriginator originator = (SerializableOriginator) ois.readObject();
            ois.close();
            this.state = originator.getState();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

// Caretaker - 负责人类
public class SerializableCaretaker {
    private byte[] memento;
    
    public void save(byte[] memento) {
        this.memento = memento;
    }
    
    public byte[] getMemento() {
        return memento;
    }
}

// Client - 客户端
public class Client {
    public static void main(String[] args) {
        // 创建原发器
        SerializableOriginator originator = new SerializableOriginator("初始状态");
        System.out.println("初始状态: " + originator.getState());
        
        // 创建负责人
        SerializableCaretaker caretaker = new SerializableCaretaker();
        
        // 保存状态
        caretaker.save(originator.saveStateToByteArray());
        
        // 修改状态
        originator.setState("修改后的状态");
        System.out.println("修改后的状态: " + originator.getState());
        
        // 恢复状态
        originator.restoreStateFromByteArray(caretaker.getMemento());
        System.out.println("恢复后的状态: " + originator.getState());
    }
}

优缺点

优点

  1. 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史状态
  2. 实现了信息的封装,使得用户不需要关心状态的保存细节
  3. 提供了一种状态恢复的实现机制,使得系统可以更加灵活地处理状态变化

缺点

  1. 资源消耗过大,如果需要保存的状态过多,或者状态变化频繁,将会占用大量的内存资源
  2. 如果需要保存的状态非常复杂,可能会导致备忘录对象的创建和恢复成本较高

相关模式

  • 命令模式:命令模式可以使用备忘录模式来实现可撤销的操作
  • 原型模式:原型模式可以用来创建备忘录对象
  • 状态模式:状态模式关注的是对象在不同状态下的行为,而备忘录模式关注的是对象状态的保存和恢复

实际应用

  • 文本编辑器中的撤销/重做功能
  • 数据库事务中的回滚操作
  • 游戏中的存档和读档功能
  • 版本控制系统中的版本管理
  • IDE中的代码回滚功能

白箱备忘录与黑箱备忘录

白箱备忘录

白箱备忘录将备忘录类设计为原发器类的内部类,这样备忘录就可以访问原发器的所有状态。但这种方式会破坏封装性。

黑箱备忘录

黑箱备忘录将备忘录类设计为独立的类,只提供必要的接口来访问状态。这种方式保持了封装性,但可能需要更多的代码来实现状态的保存和恢复。

注意事项

  1. 备忘录模式中的备忘录对象应该是轻量级的,不应该包含过多的状态信息
  2. 备忘录模式中的负责人不应该直接操作备忘录对象,而应该通过原发器来操作
  3. 备忘录模式中的原发器应该负责创建和恢复备忘录对象
  4. 备忘录模式中的备忘录对象应该是不可变的,以防止状态被意外修改
相关推荐
Engineer邓祥浩14 小时前
设计模式学习(21) 23-19 备忘录模式
学习·设计模式·备忘录模式
小码过河.9 天前
设计模式——备忘录模式
设计模式·备忘录模式
会员果汁17 天前
14.设计模式-备忘录模式
设计模式·备忘录模式
JavaBoy_XJ24 天前
行为型-备忘录模式
备忘录模式
__万波__25 天前
二十三种设计模式(十九)--备忘录模式
java·设计模式·备忘录模式
老朱佩琪!1 个月前
Unity备忘录模式
java·unity·备忘录模式
Yeniden1 个月前
Deepeek用大白话讲解 --> 备忘录模式(企业级场景1,撤销重做2,状态保存3,游戏存档4)
游戏·备忘录模式
QQ 19226382 个月前
三自由度汽车操纵侧翻模型仿真 有说明文档 学习资料:附带整理好的Word理论分析说明文档
备忘录模式
明洞日记2 个月前
【设计模式手册017】备忘录模式 - 对象状态保存与恢复
c++·设计模式·备忘录模式