备忘录模式详解(附带C++实例)
备忘录模式提供了一种恢复对象到其先前状态的能力,而不需暴露该对象的内部细节。
备忘录模式特别适合处理那些直接逆向操作成本高昂或不可能的场景。在用户界面丰富的应用程序中,如文本编辑器或图形编辑软件,用户可能期望随时回退到任意先前的状态,备忘录模式在这类用例中尤为重要。
备忘录模式如下图所示:

图 1 备忘录模式
它通过 3 个关键组件实现其功能:
与命令模式的撤销操作相比,备忘录模式的主要优势在于其通用性和灵活性。它不仅可以回滚到上一个状态,还可以访问对象状态的任何先前保存的版本,提供了更为全面的状态管理解决方案。
备忘录模式在 C++ 中通常通过将对象状态封装在一个独立的类中实现,这个类被称为“备忘录”。状态恢复则是通过将这个备忘录对象的状态回复到原始对象(发起人)中完成的。备忘录模式的实现关键在于保持好封装边界,确保只有发起人可以访问备忘录内部的状态,而其他对象,如负责人(Caretaker),则不能直接访问这些状态,只负责存储备忘录对象。
2) 深拷贝:状态的保存和恢复往往需要进行对象的深拷贝,以确保原始对象和备忘录对象之间的状态完全独立。在 C++ 中,可以通过拷贝构造函数和赋值运算符重载来实现深拷贝。
3) 友元类:通过将备忘录类定义为发起人类的友元,备忘录类可以访问发起人的私有和受保护成员。这样可以有效地封装状态信息,同时确保除了发起人之外没有其他对象可以修改这些状态。
4) 智能指针:使用智能指针如 std::unique_ptr 或 std::shared_ptr 来管理备忘录对象,可以简化内存管理并防止内存泄漏。
可以看到,在备忘录模式中,保存状态通常是一个主动的行为,而不是自动或默认发生的。发起人需要显式地调用一个方法来生成其状态的快照,并将这个快照(备忘录对象)交给负责人进行管理。这样的设计允许更精细地控制何时保存状态,以及保存哪些特定的状态,从而更好地符合应用程序的需求和性能考虑。
设计时应该尽量保持备忘录的独立性和封装性,避免导致程序中不必要的耦合或性能问题。
以下是几种优化备忘录模式实现的策略。
通过这些策略,我们可以在实现备忘录模式的同时,优化其性能和资源使用,使其更加适合于资源敏感或要求高性能的应用环境。这些策略还帮助我们在不牺牲设计优点的前提下,有效地管理和减轻备忘录模式可能引入的性能负担。
备忘录模式特别适合处理那些直接逆向操作成本高昂或不可能的场景。在用户界面丰富的应用程序中,如文本编辑器或图形编辑软件,用户可能期望随时回退到任意先前的状态,备忘录模式在这类用例中尤为重要。
备忘录模式如下图所示:

图 1 备忘录模式
它通过 3 个关键组件实现其功能:
- 发起人(Originator):这是我们希望保存和恢复状态的对象;
- 备忘录(Memento):用于存储发起人对象的内部状态。备忘录保护发起人状态的完整性,确保只有发起人本身可以访问此状态;
- 负责人(Caretaker):其职责是保存或恢复备忘录,但不修改或访问备忘录的内容。它只能将备忘录传递给发起人,让发起人自行处理状态的恢复。
与命令模式的撤销操作相比,备忘录模式的主要优势在于其通用性和灵活性。它不仅可以回滚到上一个状态,还可以访问对象状态的任何先前保存的版本,提供了更为全面的状态管理解决方案。
备忘录模式在 C++ 中通常通过将对象状态封装在一个独立的类中实现,这个类被称为“备忘录”。状态恢复则是通过将这个备忘录对象的状态回复到原始对象(发起人)中完成的。备忘录模式的实现关键在于保持好封装边界,确保只有发起人可以访问备忘录内部的状态,而其他对象,如负责人(Caretaker),则不能直接访问这些状态,只负责存储备忘录对象。
实现备忘录模式的关键技术
1) 封装类:C++ 中的类可以用来封装复杂的数据结构和行为,为发起人的状态提供一个清晰的存储结构。备忘录对象通常是私有嵌套类或友元类,这样可以保证只有发起人可以访问备忘录的内部状态。2) 深拷贝:状态的保存和恢复往往需要进行对象的深拷贝,以确保原始对象和备忘录对象之间的状态完全独立。在 C++ 中,可以通过拷贝构造函数和赋值运算符重载来实现深拷贝。
3) 友元类:通过将备忘录类定义为发起人类的友元,备忘录类可以访问发起人的私有和受保护成员。这样可以有效地封装状态信息,同时确保除了发起人之外没有其他对象可以修改这些状态。
4) 智能指针:使用智能指针如 std::unique_ptr 或 std::shared_ptr 来管理备忘录对象,可以简化内存管理并防止内存泄漏。
备忘录模式实例展示
下面是一个使用备忘录模式的简单示例,用于保存和恢复一个简单对象的状态。#include <iostream> #include <memory> // Memento类存储Originator对象的内部状态 class Memento { friend class Originator; // 允许Originator访问私有成员 int state; // 存储Originator的状态 // 构造函数为私有,确保只有Originator能创建Memento Memento(int state) : state(state) {} public: ~Memento() {} // 析构函数 }; // 生成Originator类并在以后使用Memento对象来恢复其先前的状态 class Originator { int state; // Originator当前状态 public: // 构造函数,可初始化状态 Originator(int state = 0) : state(state) {} // 设置Originator的状态 void set(int state) { this->state = state; std::cout << "State set to " << this->state << std::endl; } // 保存当前状态到Memento std::unique_ptr<Memento> saveToMemento() { return std::make_unique_ptr<Memento>(state); } // 从Memento恢复状态 void restoreFromMemento(const Memento& memento) { state = memento.state; std::cout << "State restored to " << state << std::endl; } private: // 私有静态方法,用于创建Memento对象 static std::unique_ptr<Memento> createMemento(int state) { return std::unique_ptr<Memento>(new Memento(state)); } }; // Caretaker负责保存和恢复Originator的Memento class Caretaker { std::unique_ptr<Memento> memento; // 存储Memento的指针 public: // 保存Originator的状态 void saveState(Originator& originator) { memento = originator.saveToMemento(); } // 恢复Originator的状态 void restoreState(Originator& originator) { if (memento) { originator.restoreFromMemento(*memento); } } }; // 主函数 int main() { Originator originator(10); // 创建状态为10的Originator对象 Caretaker caretaker; // 创建Caretaker对象 caretaker.saveState(originator); // 保存当前状态 originator.set(20); // 改变状态为20 caretaker.restoreState(originator); // 恢复之前的状态 return 0; }在这个示例中,Originator 类代表发起人,它可以保存和恢复其状态到 Memento 对象。Caretaker 类管理这些备忘录对象,但它自己并不修改或访问这些备忘录对象的具体内容。这种方式确保了状态封装的安全性,并使得状态管理变得更加清晰和可控。
可以看到,在备忘录模式中,保存状态通常是一个主动的行为,而不是自动或默认发生的。发起人需要显式地调用一个方法来生成其状态的快照,并将这个快照(备忘录对象)交给负责人进行管理。这样的设计允许更精细地控制何时保存状态,以及保存哪些特定的状态,从而更好地符合应用程序的需求和性能考虑。
备忘录模式实践指南
在设计备忘录模式时,确定需要保存的状态内容是一个关键步骤,这通常取决于以下几个因素:1) 业务需求
首先,需要明确应用场景和业务需求。问自己,用户可能想要撤销哪些操作?哪些状态的改变是关键的,可能需要回滚?这些问题将帮助确定状态中哪些部分是必须保存的。2) 对象的核心属性
考虑那些定义了对象当前行为和外观的核心属性。例如,在文本编辑器中,可能需要保存文本内容、字体大小、颜色等属性;在游戏中,可能需要保存角色的位置、健康状态和物品清单。3) 依赖性和关联性
检查对象状态中的依赖性。某些状态可能依赖于其他对象的状态,或与其他对象的状态密切相关。确定这些关系并确保在备忘录中适当地处理这些依赖性,这样状态恢复时可以维持对象间的一致性和完整性。4) 状态的复杂性和开销
评估保存和恢复状态的资源开销。更复杂或更大的状态可能会导致性能问题。在一些性能敏感的应用中,可能需要通过只保存变化的部分或者其他优化策略来减少开销。5) 频率和时机
考虑状态保存的频率和时机。不是每次状态变化都需要保存备忘录。可能只有在特定的操作后,如用户执行了一个明确的操作(例如保存、提交或达到了一个重要的操作步骤),才进行保存。6) 用户控制
在某些情况下,可以让用户选择想要保存的状态。这增加了灵活性,允许用户根据自己的需要自定义撤销和恢复的行为。备忘录模式实施建议
在实际开发中,确定何时以及如何保存状态通常需要与团队成员进行讨论,包括开发人员、设计师和项目管理者,以确保备忘录模式的实现满足项目需求并且与用户期望一致。设计时应该尽量保持备忘录的独立性和封装性,避免导致程序中不必要的耦合或性能问题。
备忘录模式性能优化策略
备忘录模式在恢复对象状态时确保了高度的灵活性和隔离性,但这种设计也可能引入数据复制的性能问题,特别是在处理大型或复杂对象时。以下是几种优化备忘录模式实现的策略。
1) 增量备忘录
- 概念:增量备忘录不保存整个对象状态,仅保存状态改变的部分。这种方法适用于对象状态变化较小的情况,可以显著减少所需的存储空间。
- 实施方式:在创建备忘录时,记录自上次保存以来的状态变更,而非完整状态。恢复时,逐步应用这些变更直到达到所需的历史状态。
2) 共享状态数据
- 概念:使用共享对象来保存未变更的状态部分,仅对变更的部分创建备份。这可以减少冗余数据的存储,并降低内存占用。
- 实施方式:采用引用计数或智能指针来管理共享对象,确保数据在没有必要时不被复制。
3) 状态差异记录
- 概念:类似于软件版本控制,备忘录可以只记录与前一状态的差异。这种方式可以在保存和恢复状态时提供更高的效率。
- 实施方式:每个备忘录对象存储一个指向前一状态的链接和当前状态与前一状态的差异。恢复状态时,从最初状态开始逐步应用这些差异。
4) 按需备忘录
- 概念:仅在用户或系统明确需要时才创建备忘录,而不是在每次状态变化时自动创建。
- 实施方式:提供一个显式的“保存”操作,让用户决定何时保存状态,这可以减少不必要的性能开销。
5) 资源管理和清理
- 概念:有效管理备忘录对象的生命周期,及时清理不再需要的备忘录,以避免内存泄漏和性能下降。
- 实施方式:使用智能指针和自动内存管理策略,确保备忘录对象在适当的时候被销毁。
通过这些策略,我们可以在实现备忘录模式的同时,优化其性能和资源使用,使其更加适合于资源敏感或要求高性能的应用环境。这些策略还帮助我们在不牺牲设计优点的前提下,有效地管理和减轻备忘录模式可能引入的性能负担。