原文地址: 备忘录模式 更多内容请关注:智想天开
1. 备忘录模式简介
备忘录模式(Memento Pattern)是一种行为型设计模式,它允许在不暴露对象实现细节的情况下捕获和保存对象的内部状态,从而在未来需要时恢复对象到先前的状态。备忘录模式通过引入备忘录对象,实现了对象状态的保存与恢复,常用于实现 撤销(Undo) 功能。
关键点:
-
状态保存与恢复:在不破坏封装性的前提下,保存对象的内部状态,并在需要时恢复。
-
封装性:备忘录对象只暴露必要的信息,确保对象内部状态的隐私性。
-
独立性:备忘录模式将状态的保存与恢复操作与对象的核心职责分离。
2. 备忘录模式的意图
备忘录模式的主要目的是:
-
实现对象状态的保存与恢复:允许对象在某个时间点保存其状态,并在需要时恢复到该状态。
-
增强系统的灵活性:通过分离状态保存与核心业务逻辑,使得系统更加灵活和可维护。
-
支持撤销与重做功能:在应用程序中实现撤销(Undo)与重做(Redo)功能,如文本编辑器、图形设计软件等。
-
维护对象状态的历史记录:记录对象状态的变化历史,以便进行审计或回溯。
3. 备忘录模式的结构
3.1. 结构组成
备忘录模式主要由以下三个角色组成:
-
Originator(发起人):知道如何创建一个备忘录以保存自身的状态,以及如何通过备忘录恢复自身的状态。
-
Memento(备忘录):存储Originator的内部状态,不允许其他对象访问备忘录的内容,确保封装性。
-
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 通过调用 Originator 的
createMemento()
方法来保存状态,并通过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 = statedef get_state(self) -> str:return self._state# Originator类
class TextEditor:def __init__(self):self._content = ""def set_content(self, content: str):self._content = contentprint(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. 备忘录模式的适用场景
备忘录模式适用于以下场景:
-
需要保存和恢复对象状态:如文本编辑器的撤销/重做功能、游戏的保存点等。
-
需要维护对象状态的历史记录:如版本控制系统、事务管理系统等。
-
需要封装状态信息:在不破坏对象封装性的前提下,保存对象的内部状态。
-
需要支持多个级别的撤销功能:如支持多级撤销的应用程序。
-
实现回滚机制:在操作失败时,回滚到先前的稳定状态。
示例应用场景:
-
文本编辑器:实现撤销和重做功能。
-
图形设计软件:保存图形的状态,支持历史记录和回滚。
-
游戏开发:保存游戏进度,支持加载保存点。
-
数据库事务管理:在事务失败时,回滚到事务开始前的状态。
-
智能家居系统:保存设备配置状态,支持恢复到先前配置。
6. 备忘录模式的优缺点
6.1. 优点
-
封装性强:备忘录模式有效地封装了对象的内部状态,确保了对象内部的私有性。
-
实现简单:通过备忘录对象的创建和恢复,实现了对象状态的保存与恢复,结构简单清晰。
-
支持撤销和重做:方便地实现撤销(Undo)和重做(Redo)功能,提升用户体验。
-
维护历史记录:能够保存对象状态的历史记录,便于审计和回溯。
-
增强系统灵活性:通过备忘录模式,系统可以灵活地管理对象状态的保存与恢复。
6.2. 缺点
-
可能消耗大量内存:频繁保存对象状态可能导致内存占用增加,尤其是对象状态较大时。
-
可能违反单一职责原则:Originator类不仅需要实现核心业务逻辑,还需要实现状态的保存与恢复。
-
备忘录可能复杂:对于复杂对象,备忘录可能需要保存大量状态信息,导致备忘录对象复杂化。
-
难以管理多个备忘录:当系统需要管理多个备忘录时,可能导致Caretaker类变得复杂。
-
潜在的隐私泄露风险:如果备忘录对象未能正确封装,可能会泄露对象的内部状态。
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] = valueprint(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();// 开始事务1tm.beginTransaction(db);db.setData("A", 100);db.setData("B", 200);// 开始事务2tm.beginTransaction(db);db.setData("A", 150);db.setData("C", 300);// 回滚事务2System.out.println("\n执行事务回滚:");tm.rollback(db);// 回滚事务1System.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) 通过封装对象的内部状态,实现了对象状态的保存与恢复,增强了系统的灵活性和可维护性。该模式适用于需要支持撤销/重做功能、维护对象状态历史记录以及封装对象内部状态的场景。通过将状态的保存与对象的核心职责分离,备忘录模式使系统设计更加清晰和模块化。
关键学习点回顾:
-
理解备忘录模式的核心概念:通过备忘录对象保存和恢复对象的内部状态,确保封装性。
-
掌握备忘录模式的结构:包括Originator、Memento和Caretaker之间的关系。
-
识别适用的应用场景:支持撤销/重做功能、维护对象状态历史记录、实现回滚机制等。
-
认识备忘录模式的优缺点:封装性强、实现简单、支持撤销和重做;但可能导致内存消耗增加、系统复杂性提升。
-
理解常见误区及解决方案:避免过度使用、优化内存管理、确保备忘录的封装性。
-
实际应用中的备忘录模式实例:文本编辑器的撤销功能、游戏的保存点功能、数据库事务的回滚功能等。