备忘录模式


目录

  1. 备忘录模式简介

  2. 备忘录模式的意图

  3. 备忘录模式的结构

  4. 备忘录模式的实现

  5. 备忘录模式的适用场景

  6. 备忘录模式的优缺点

  7. 备忘录模式的常见误区与解决方案

  8. 备忘录模式的实际应用实例

  9. 备忘录模式与其他模式的比较

  10. 总结

  11. 参考资料


1. 备忘录模式简介

备忘录模式(Memento Pattern)是一种行为型设计模式,它允许在不暴露对象实现细节的情况下捕获和保存对象的内部状态,从而在未来需要时恢复对象到先前的状态。备忘录模式通过引入备忘录对象,实现了对象状态的保存与恢复,常用于实现 撤销(Undo) 功能。

关键点:

  • 状态保存与恢复:在不破坏封装性的前提下,保存对象的内部状态,并在需要时恢复。

  • 封装性:备忘录对象只暴露必要的信息,确保对象内部状态的隐私性。

  • 独立性:备忘录模式将状态的保存与恢复操作与对象的核心职责分离。


2. 备忘录模式的意图

备忘录模式的主要目的是:

  • 实现对象状态的保存与恢复:允许对象在某个时间点保存其状态,并在需要时恢复到该状态。

  • 增强系统的灵活性:通过分离状态保存与核心业务逻辑,使得系统更加灵活和可维护。

  • 支持撤销与重做功能:在应用程序中实现撤销(Undo)与重做(Redo)功能,如文本编辑器、图形设计软件等。

  • 维护对象状态的历史记录:记录对象状态的变化历史,以便进行审计或回溯。


3. 备忘录模式的结构

3.1. 结构组成

备忘录模式主要由以下三个角色组成:

  1. Originator(发起人):知道如何创建一个备忘录以保存自身的状态,以及如何通过备忘录恢复自身的状态。

  2. Memento(备忘录):存储Originator的内部状态,不允许其他对象访问备忘录的内容,确保封装性。

  3. Caretaker(负责人):负责备忘录的保存与恢复,但不能修改备忘录的内容。

角色关系:

  • Originator 创建并使用 Memento 来保存和恢复自身的状态。

  • Caretaker 持有 Memento 对象,但不暴露其内部状态。

  • Memento 仅对 Originator 可见,其他对象无法访问其内部状态。

3.2. UML类图

以下是备忘录模式的简化UML类图:

+-------------------------+
|   Caretaker             |
+-------------------------+
| - memento: Memento      |
+-------------------------+
| + saveState() : void    |
| + restoreState() : void |
|                         |
+-------------------------+

+---------------------------------------+
|   Originator                          |
+---------------------------------------+
| - state: String                       |
+---------------------------------------+
| + setState(state) : void              |
| + createMemento() : Memento           |
| + setMemento(memento: Memento) : void |
+---------------------------------------+

+-----------------------+
|     Memento           |
+-----------------------+
| - state: String       |
+-----------------------+
| + getState() : String |
+-----------------------+

说明:

  • Caretaker 通过调用 OriginatorcreateMemento() 方法来保存状态,并通过 setMemento() 方法恢复状态。

  • Originator 维护自身的状态,并负责创建和恢复备忘录。

  • Memento 封装了 Originator 的状态,仅 Originator 能访问其内部状态。


4. 备忘录模式的实现

以下示例将展示如何在Java和Python中实现备忘录模式。以一个简单的文本编辑器为例,实现支持撤销功能的备忘录模式。

4.1. Java 实现示例

示例说明

我们将实现一个简单的文本编辑器,支持文本的输入和撤销功能。通过备忘录模式,保存文本编辑器的状态,并在需要时恢复到先前的状态。

代码实现

// Memento类
public class Memento {
    private final String state;

    public Memento(String state){
        this.state = state;
    }

    public String getState(){
        return state;
    }
}

// Originator类
public class TextEditor {
    private String content;

    public void setContent(String content){
        this.content = content;
        System.out.println("当前内容: " + this.content);
    }

    public Memento save() {
        return new Memento(content);
    }

    public void restore(Memento memento){
        this.content = memento.getState();
        System.out.println("恢复后的内容: " + this.content);
    }
}

// Caretaker类
import java.util.Stack;

public class Caretaker {
    private Stack<Memento> history = new Stack<>();

    public void saveState(TextEditor editor){
        history.push(editor.save());
    }

    public void undo(TextEditor editor){
        if(!history.isEmpty()){
            Memento memento = history.pop();
            editor.restore(memento);
        } else {
            System.out.println("没有可以撤销的操作。");
        }
    }
}

// 客户端代码
public class MementoPatternDemo {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();
        Caretaker caretaker = new Caretaker();

        editor.setContent("Hello");
        caretaker.saveState(editor);

        editor.setContent("Hello, World!");
        caretaker.saveState(editor);

        editor.setContent("Hello, World! This is Java.");
        
        System.out.println("\n执行撤销操作:");
        caretaker.undo(editor);

        System.out.println("\n再次执行撤销操作:");
        caretaker.undo(editor);

        System.out.println("\n再次执行撤销操作:");
        caretaker.undo(editor);
    }
}

输出

当前内容: Hello
当前内容: Hello, World!
当前内容: Hello, World! This is Java.

执行撤销操作:
恢复后的内容: Hello, World!

再次执行撤销操作:
恢复后的内容: Hello

再次执行撤销操作:
没有可以撤销的操作。

代码说明

  • Memento类:封装了文本编辑器的状态(即内容)。

  • TextEditor类(Originator):维护文本内容,负责创建和恢复备忘录。

  • Caretaker类:维护备忘录的历史记录(使用栈实现),负责保存和撤销状态。

  • MementoPatternDemo类:客户端,演示了文本编辑器的内容修改和撤销功能。

4.2. Python 实现示例

示例说明

同样,实现一个简单的文本编辑器,支持文本的输入和撤销功能。通过备忘录模式,保存文本编辑器的状态,并在需要时恢复到先前的状态。

代码实现

from abc import ABC, abstractmethod

# Memento类
class Memento:
    def __init__(self, state: str):
        self._state = state

    def get_state(self) -> str:
        return self._state

# Originator类
class TextEditor:
    def __init__(self):
        self._content = ""

    def set_content(self, content: str):
        self._content = content
        print(f"当前内容: {self._content}")

    def save(self) -> Memento:
        return Memento(self._content)

    def restore(self, memento: Memento):
        self._content = memento.get_state()
        print(f"恢复后的内容: {self._content}")

# Caretaker类
class Caretaker:
    def __init__(self):
        self._history = []

    def save_state(self, editor: TextEditor):
        self._history.append(editor.save())

    def undo(self, editor: TextEditor):
        if self._history:
            memento = self._history.pop()
            editor.restore(memento)
        else:
            print("没有可以撤销的操作。")

# 客户端代码
def memento_pattern_demo():
    editor = TextEditor()
    caretaker = Caretaker()

    editor.set_content("Hello")
    caretaker.save_state(editor)

    editor.set_content("Hello, World!")
    caretaker.save_state(editor)

    editor.set_content("Hello, World! This is Python.")
    
    print("\n执行撤销操作:")
    caretaker.undo(editor)

    print("\n再次执行撤销操作:")
    caretaker.undo(editor)

    print("\n再次执行撤销操作:")
    caretaker.undo(editor)

if __name__ == "__main__":
    memento_pattern_demo()

输出

当前内容: Hello
当前内容: Hello, World!
当前内容: Hello, World! This is Python.

执行撤销操作:
恢复后的内容: Hello, World!

再次执行撤销操作:
恢复后的内容: Hello

再次执行撤销操作:
没有可以撤销的操作。

代码说明

  • Memento类:封装了文本编辑器的状态(即内容)。

  • TextEditor类(Originator):维护文本内容,负责创建和恢复备忘录。

  • Caretaker类:维护备忘录的历史记录(使用列表实现),负责保存和撤销状态。

  • memento_pattern_demo函数:客户端,演示了文本编辑器的内容修改和撤销功能。


5. 备忘录模式的适用场景

备忘录模式适用于以下场景:

  1. 需要保存和恢复对象状态:如文本编辑器的撤销/重做功能、游戏的保存点等。

  2. 需要维护对象状态的历史记录:如版本控制系统、事务管理系统等。

  3. 需要封装状态信息:在不破坏对象封装性的前提下,保存对象的内部状态。

  4. 需要支持多个级别的撤销功能:如支持多级撤销的应用程序。

  5. 实现回滚机制:在操作失败时,回滚到先前的稳定状态。

示例应用场景:

  • 文本编辑器:实现撤销和重做功能。

  • 图形设计软件:保存图形的状态,支持历史记录和回滚。

  • 游戏开发:保存游戏进度,支持加载保存点。

  • 数据库事务管理:在事务失败时,回滚到事务开始前的状态。

  • 智能家居系统:保存设备配置状态,支持恢复到先前配置。


6. 备忘录模式的优缺点

6.1. 优点

  1. 封装性强:备忘录模式有效地封装了对象的内部状态,确保了对象内部的私有性。

  2. 实现简单:通过备忘录对象的创建和恢复,实现了对象状态的保存与恢复,结构简单清晰。

  3. 支持撤销和重做:方便地实现撤销(Undo)和重做(Redo)功能,提升用户体验。

  4. 维护历史记录:能够保存对象状态的历史记录,便于审计和回溯。

  5. 增强系统灵活性:通过备忘录模式,系统可以灵活地管理对象状态的保存与恢复。

6.2. 缺点

  1. 可能消耗大量内存:频繁保存对象状态可能导致内存占用增加,尤其是对象状态较大时。

  2. 可能违反单一职责原则:Originator类不仅需要实现核心业务逻辑,还需要实现状态的保存与恢复。

  3. 备忘录可能复杂:对于复杂对象,备忘录可能需要保存大量状态信息,导致备忘录对象复杂化。

  4. 难以管理多个备忘录:当系统需要管理多个备忘录时,可能导致Caretaker类变得复杂。

  5. 潜在的隐私泄露风险:如果备忘录对象未能正确封装,可能会泄露对象的内部状态。


7. 备忘录模式的常见误区与解决方案

7.1. 误区1:过度使用备忘录模式

问题描述: 开发者可能倾向于将所有需要保存状态的场景都使用备忘录模式,导致系统中充斥着大量的备忘录类,增加了系统的复杂性和维护成本。

解决方案:

  • 评估必要性:仅在确实需要保存和恢复对象状态的场景下使用备忘录模式。

  • 限制备忘录的使用范围:避免备忘录模式扩展到不必要的对象,保持系统的简洁性。

  • 结合其他模式:在适当的情况下,结合使用其他设计模式,如命令模式,实现更灵活的功能。

7.2. 误区2:忽视备忘录的内存管理

问题描述: 频繁创建备忘录对象可能导致内存占用增加,尤其是在需要保存大量状态或备忘录对象较大时。

解决方案:

  • 优化备忘录的存储:仅保存必要的状态信息,避免冗余数据。

  • 限制备忘录的数量:设置备忘录的最大数量,定期清理不再需要的备忘录。

  • 使用轻量级备忘录:通过引用共享不可变对象或使用快照技术,减少内存占用。

7.3. 误区3:备忘录对象泄露内部状态

问题描述: 备忘录对象未能正确封装内部状态,导致外部对象可以访问或修改备忘录的内容,破坏对象的封装性。

解决方案:

  • 严格封装:确保备忘录对象的内部状态对外不可见,仅通过Originator类进行访问。

  • 使用访问控制:在编程语言中使用访问控制机制(如Java中的private修饰符)保护备忘录的内部状态。

  • 设计备忘录接口:通过设计专门的备忘录接口,限制对备忘录内容的访问。


8. 备忘录模式的实际应用实例

8.1. 文本编辑器的撤销功能

示例说明

实现一个简单的文本编辑器,支持文本的输入和撤销功能。通过备忘录模式,保存文本编辑器的状态,并在需要时恢复到先前的状态。

Java实现

// Memento类
public class Memento {
    private final String state;

    public Memento(String state){
        this.state = state;
    }

    public String getState(){
        return state;
    }
}

// Originator类
public class TextEditor {
    private String content;

    public void setContent(String content){
        this.content = content;
        System.out.println("当前内容: " + this.content);
    }

    public Memento save() {
        return new Memento(content);
    }

    public void restore(Memento memento){
        this.content = memento.getState();
        System.out.println("恢复后的内容: " + this.content);
    }
}

// Caretaker类
import java.util.Stack;

public class Caretaker {
    private Stack<Memento> history = new Stack<>();

    public void saveState(TextEditor editor){
        history.push(editor.save());
    }

    public void undo(TextEditor editor){
        if(!history.isEmpty()){
            Memento memento = history.pop();
            editor.restore(memento);
        } else {
            System.out.println("没有可以撤销的操作。");
        }
    }
}

// 客户端代码
public class MementoPatternDemo {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();
        Caretaker caretaker = new Caretaker();

        editor.setContent("Hello");
        caretaker.saveState(editor);

        editor.setContent("Hello, World!");
        caretaker.saveState(editor);

        editor.setContent("Hello, World! This is Java.");
        
        System.out.println("\n执行撤销操作:");
        caretaker.undo(editor);

        System.out.println("\n再次执行撤销操作:");
        caretaker.undo(editor);

        System.out.println("\n再次执行撤销操作:");
        caretaker.undo(editor);
    }
}

输出

当前内容: Hello
当前内容: Hello, World!
当前内容: Hello, World! This is Java.

执行撤销操作:
恢复后的内容: Hello, World!

再次执行撤销操作:
恢复后的内容: Hello

再次执行撤销操作:
没有可以撤销的操作。

代码说明

  • Memento类:封装了文本编辑器的状态(即内容)。

  • TextEditor类(Originator):维护文本内容,负责创建和恢复备忘录。

  • Caretaker类:维护备忘录的历史记录(使用栈实现),负责保存和撤销状态。

  • MementoPatternDemo类:客户端,创建了文本编辑器和负责人对象,演示了内容修改和撤销功能。

8.2. 游戏的保存点功能

示例说明

在游戏中,玩家可以在特定的位置保存游戏进度,通过备忘录模式,实现游戏状态的保存和恢复。

Python实现

from abc import ABC, abstractmethod

# Memento类
class Memento:
    def __init__(self, state: dict):
        self._state = state.copy()

    def get_state(self) -> dict:
        return self._state

# Originator类
class Game:
    def __init__(self):
        self._state = {}

    def set_state(self, key: str, value):
        self._state[key] = value
        print(f"设置游戏状态: {key} = {value}")

    def save(self) -> Memento:
        return Memento(self._state)

    def restore(self, memento: Memento):
        self._state = memento.get_state()
        print(f"恢复游戏状态: {self._state}")

# Caretaker类
class Caretaker:
    def __init__(self):
        self._history = []

    def save_state(self, game: Game):
        self._history.append(game.save())

    def undo(self, game: Game):
        if self._history:
            memento = self._history.pop()
            game.restore(memento)
        else:
            print("没有可以撤销的操作。")

# 客户端代码
def memento_pattern_demo():
    game = Game()
    caretaker = Caretaker()

    game.set_state("level", 1)
    caretaker.save_state(game)

    game.set_state("level", 2)
    caretaker.save_state(game)

    game.set_state("score", 1500)
    
    print("\n执行撤销操作:")
    caretaker.undo(game)

    print("\n再次执行撤销操作:")
    caretaker.undo(game)

    print("\n再次执行撤销操作:")
    caretaker.undo(game)

if __name__ == "__main__":
    memento_pattern_demo()

输出

设置游戏状态: level = 1
设置游戏状态: level = 2
设置游戏状态: score = 1500

执行撤销操作:
恢复游戏状态: {'level': 2}

再次执行撤销操作:
恢复游戏状态: {'level': 1}

再次执行撤销操作:
没有可以撤销的操作。

代码说明

  • Memento类:封装了游戏的状态(如等级、分数)。

  • Game类(Originator):维护游戏状态,负责创建和恢复备忘录。

  • Caretaker类:维护备忘录的历史记录(使用列表实现),负责保存和撤销状态。

  • memento_pattern_demo函数:客户端,创建了游戏和负责人对象,演示了游戏状态的修改和撤销功能。

8.3. 数据库事务的回滚功能

示例说明

在数据库应用中,事务管理需要在操作失败时回滚到事务开始前的状态。通过备忘录模式,保存事务开始前的数据库状态,并在需要时恢复到该状态。

Java实现

import java.util.HashMap;
import java.util.Map;

// Memento类
public class TransactionMemento {
    private final Map<String, Integer> state;

    public TransactionMemento(Map<String, Integer> state){
        this.state = new HashMap<>(state);
    }

    public Map<String, Integer> getState(){
        return state;
    }
}

// Originator类
public class Database {
    private Map<String, Integer> data = new HashMap<>();

    public void setData(String key, int value){
        data.put(key, value);
        System.out.println("设置数据: " + key + " = " + value);
    }

    public int getData(String key){
        return data.getOrDefault(key, 0);
    }

    public TransactionMemento save(){
        return new TransactionMemento(data);
    }

    public void restore(TransactionMemento memento){
        this.data = memento.getState();
        System.out.println("数据库状态已恢复: " + this.data);
    }
}

// Caretaker类
import java.util.Stack;

public class TransactionManager {
    private Stack<TransactionMemento> history = new Stack<>();

    public void beginTransaction(Database db){
        history.push(db.save());
        System.out.println("事务开始,状态已保存。");
    }

    public void rollback(Database db){
        if(!history.isEmpty()){
            TransactionMemento memento = history.pop();
            db.restore(memento);
            System.out.println("事务回滚成功。");
        } else {
            System.out.println("没有可回滚的事务。");
        }
    }
}

// 客户端代码
public class MementoPatternDemo {
    public static void main(String[] args) {
        Database db = new Database();
        TransactionManager tm = new TransactionManager();

        // 开始事务1
        tm.beginTransaction(db);
        db.setData("A", 100);
        db.setData("B", 200);

        // 开始事务2
        tm.beginTransaction(db);
        db.setData("A", 150);
        db.setData("C", 300);

        // 回滚事务2
        System.out.println("\n执行事务回滚:");
        tm.rollback(db);

        // 回滚事务1
        System.out.println("\n再次执行事务回滚:");
        tm.rollback(db);

        // 尝试回滚不存在的事务
        System.out.println("\n尝试回滚不存在的事务:");
        tm.rollback(db);
    }
}

输出

事务开始,状态已保存。
设置数据: A = 100
设置数据: B = 200
事务开始,状态已保存。
设置数据: A = 150
设置数据: C = 300

执行事务回滚:
数据库状态已恢复: {A=100, B=200}
事务回滚成功。

再次执行事务回滚:
数据库状态已恢复: {}
事务回滚成功。

尝试回滚不存在的事务:
没有可回滚的事务。

代码说明

  • TransactionMemento类:封装了数据库的状态(数据表中的键值对)。

  • Database类(Originator):维护数据库数据,负责创建和恢复备忘录。

  • TransactionManager类(Caretaker):维护备忘录的历史记录(使用栈实现),负责保存和回滚事务。

  • MementoPatternDemo类:客户端,创建了数据库和事务管理器对象,演示了事务的开始、数据修改和回滚功能。


9. 备忘录模式与其他模式的比较

9.1. 备忘录模式 vs. 观察者模式

  • 备忘录模式用于保存和恢复对象的内部状态,支持撤销和重做功能。

  • 观察者模式用于建立一对多的依赖关系,当一个对象状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。

关键区别:

  • 目的不同:备忘录模式关注对象状态的保存与恢复,观察者模式关注对象状态的同步和通知。

  • 结构不同:备忘录模式引入备忘录对象来封装状态,观察者模式通过观察者和被观察者之间的注册关系实现通知。

  • 应用场景不同:备忘录模式适用于需要撤销/重做功能的场景,观察者模式适用于需要对象间状态同步的场景。

9.2. 备忘录模式 vs. 命令模式

  • 备忘录模式用于保存和恢复对象的内部状态,支持撤销和重做功能。

  • 命令模式用于封装请求,使请求的发送者与接收者解耦,支持请求的队列化、日志记录和撤销功能。

关键区别:

  • 目的不同:备忘录模式关注对象状态的保存与恢复,命令模式关注请求的封装与执行。

  • 结构不同:备忘录模式通过备忘录对象保存状态,命令模式通过命令对象封装请求。

  • 应用场景不同:备忘录模式适用于需要保存对象状态的场景,命令模式适用于需要封装请求并支持撤销/重做的场景。

9.3. 备忘录模式 vs. 访问者模式

  • 备忘录模式用于保存和恢复对象的内部状态

  • 访问者模式用于分离数据结构与数据操作,通过访问者对象遍历数据结构并执行操作。

关键区别:

  • 目的不同:备忘录模式关注对象状态的保存与恢复,访问者模式关注对对象结构的操作和处理。

  • 结构不同:备忘录模式通过备忘录对象封装状态,访问者模式通过访问者和元素之间的双向调用实现操作。

  • 应用场景不同:备忘录模式适用于需要保存对象状态的场景,访问者模式适用于需要对对象结构执行多种操作的场景。

9.4. 备忘录模式 vs. 原型模式

  • 备忘录模式用于保存和恢复对象的内部状态,支持撤销和重做功能。

  • 原型模式用于创建对象的副本,通过复制现有对象来创建新对象,支持动态创建对象。

关键区别:

  • 目的不同:备忘录模式关注对象状态的保存与恢复,原型模式关注对象的复制与实例化。

  • 结构不同:备忘录模式通过备忘录对象保存状态,原型模式通过克隆(clone()方法)创建对象副本。

  • 应用场景不同:备忘录模式适用于需要保存对象状态的场景,原型模式适用于需要高效创建对象副本的场景。


10. 总结

备忘录模式(Memento Pattern) 通过封装对象的内部状态,实现了对象状态的保存与恢复,增强了系统的灵活性和可维护性。该模式适用于需要支持撤销/重做功能、维护对象状态历史记录以及封装对象内部状态的场景。通过将状态的保存与对象的核心职责分离,备忘录模式使系统设计更加清晰和模块化。

关键学习点回顾:

  1. 理解备忘录模式的核心概念:通过备忘录对象保存和恢复对象的内部状态,确保封装性。

  2. 掌握备忘录模式的结构:包括Originator、Memento和Caretaker之间的关系。

  3. 识别适用的应用场景:支持撤销/重做功能、维护对象状态历史记录、实现回滚机制等。

  4. 认识备忘录模式的优缺点:封装性强、实现简单、支持撤销和重做;但可能导致内存消耗增加、系统复杂性提升。

  5. 理解常见误区及解决方案:避免过度使用、优化内存管理、确保备忘录的封装性。

  6. 实际应用中的备忘录模式实例:文本编辑器的撤销功能、游戏的保存点功能、数据库事务的回滚功能等。


11. 参考资料