设计模式20-备忘录
- 动机
- 定义与结构
- 定义
- 结构
- C++代码推导
- 优缺点
- 应用场景
- 总结
- 备忘录模式和序列化
- 备忘录模式
- 1. **动机**
- 2. **实现方式**
- 3. **应用场景**
- 4. **优点**
- 5. **缺点**
- 序列化
- 1. **动机**
- 2. **实现方式**
- 3. **应用场景**
- 4. **优点**
- 5. **缺点**
- 对比总结
动机
- 在软件构建过程中,某些对象的状态在转换过程中可能由于某种需要这个程序能够回溯到对象之前处于某个点时的状态。使用一些公用接口来让其他对象得到对象的状态,便会暴露对象细节的实现
- 如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的封装性呢。
- 在软件开发中,有时需要在不破坏对象封装性的前提下捕获并存储对象的内部状态,以便稍后可以将对象恢复到以前的状态。这种需求通常出现在实现撤销/恢复操作的场景中,例如文本编辑器中的撤销操作。直接暴露对象的内部状态会违反封装原则,而备忘录模式提供了一种解决方案,使得状态恢复操作能够在不破坏封装性的前提下实现。
定义与结构
定义
备忘录模式在不破坏封装性的前提下,捕获对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将对象恢复到原先保存的状态。
结构
解释这张图:
这张图描绘了一个典型的UML(统一建模语言)结构图,展示了“备忘录模式”(Memento Pattern)的核心组件及其关系。备忘录模式是一种用于捕获和存储一个对象内部状态的方式,以便可以在未来某个时刻将对象恢复到这个状态。这个模式特别适用于需要保存和恢复对象状态的场景,比如撤销操作、事务处理等。
图中包含了三个主要的类:
-
Originator(原发器):这个类是需要被保存状态的对象。它包含一个
CreateMemento()
方法,用于创建一个备忘录对象(Memento),这个对象包含了Originator的当前状态。Originator还包含一个SetMemento(Memento m)
方法,用于设置Originator的状态为备忘录对象(Memento)中存储的状态。图中对SetMemento
方法的描述可能有些误解,因为通常这个方法不会直接从外部传入一个备忘录对象来设置状态,而是由Caretaker或者系统其他部分将之前保存的备忘录对象传递回Originator来恢复状态。 -
Memento(备忘录):这个类用于存储Originator的内部状态,以保护这些状态不受外部访问。它包含了一个构造函数(尽管图中没有直接标出),该构造函数可能接受来自Originator的当前状态作为参数。Memento还包含
GetState()
和SetState(state)
方法(尽管图中SetState
方法的参数可能标记有误,通常备忘录模式中的Memento类不需要SetState
方法,因为备忘录主要是用来存储状态的)。但在实际实现中,GetState()
方法用于返回备忘录中存储的状态,而SetState
(如果存在的话)可能并不是Memento类的一部分,而是用于在Originator中恢复状态的过程中的一部分,但这通常是通过将Memento对象传递给Originator的某个恢复状态方法来实现的。 -
Caretaker(管理者):这个类负责保存备忘录对象,但不检查备忘录对象的内容。在图中,Caretaker类被描述为有一个
ReturnNewMemento(state)
方法,这实际上可能是一个误解或错误。在标准的备忘录模式中,Caretaker类会维护一个备忘录对象的列表,但不直接创建新的Memento对象。相反,它可能包含一个AddMemento(Memento m)
方法来添加新的备忘录对象,以及一个GetMemento(index)
或类似的方法来检索之前保存的备忘录对象。图中的ReturnNewMemento(state)
可能试图表达Caretaker返回一个新状态的Memento对象给Originator,但这并不是Caretaker类的典型职责;更常见的是,Caretaker简单地存储和检索Memento对象。
图中箭头表示类之间的交互关系。从Originator到Memento的箭头表示Originator创建Memento对象的过程,而从Caretaker到Originator的箭头(尽管图中没有直接画出)表示Caretaker可能将存储的Memento对象传递回Originator以恢复状态。
需要注意的是,图中的某些细节(如ReturnNewMemento(state)
方法和SetState0
方法的标记)可能与标准的备忘录模式实现不完全一致,可能是为了简化图示或特定实现的表示。
C++代码推导
以下是一个简单的备忘录模式的C++实现示例,模拟一个文本编辑器的撤销功能。
备忘录类:
#include <iostream>
#include <string>
// 备忘录类,用于存储Originator的内部状态
class Memento {
private:
std::string state;
public:
Memento(const std::string& state) : state(state) {}
std::string getState() const {
return state;
}
};
发起人类:
class Originator {
private:
std::string state;
public:
void setState(const std::string& state) {
this->state = state;
std::cout << "State set to: " << state << std::endl;
}
std::string getState() const {
return state;
}
Memento* createMemento() {
return new Memento(state);
}
void setMemento(Memento* memento) {
state = memento->getState();
std::cout << "State restored to: " << state << std::endl;
}
};
负责人类:
class Caretaker {
private:
Memento* memento;
public:
void saveMemento(Memento* memento) {
this->memento = memento;
}
Memento* getMemento() {
return memento;
}
~Caretaker() {
delete memento;
}
};
客户端代码:
int main() {
Originator* originator = new Originator();
Caretaker* caretaker = new Caretaker();
originator->setState("State1");
caretaker->saveMemento(originator->createMemento());
originator->setState("State2");
caretaker->saveMemento(originator->createMemento());
originator->setState("State3");
// 恢复到上一个状态
originator->setMemento(caretaker->getMemento());
delete originator;
delete caretaker;
return 0;
}
运行结果:
State set to: State1
State set to: State2
State set to: State3
State restored to: State2
优缺点
优点:
- 封装性:备忘录模式可以在不破坏对象封装性的前提下保存和恢复对象的状态,符合对象的封装原则。
- 简化撤销操作:备忘录模式简化了复杂系统中的撤销操作实现,使得对象能够恢复到先前的状态。
缺点:
- 内存开销:如果对象的状态较大且备忘录创建频繁,可能会占用大量内存,增加系统开销。
- 管理复杂性:备忘录需要被妥善管理,尤其是在需要多个备忘录进行多步撤销时,备忘录的管理和恢复可能会变得复杂。
应用场景
备忘录模式在以下场景中应用较多:
- 需要保存对象状态以便恢复:例如实现多步撤销/恢复功能的场景,如文本编辑器、图形编辑器等。
- 需要避免暴露对象内部状态:当需要避免外部直接访问对象的内部状态时,可以使用备忘录模式来实现状态保存和恢复。
- 对象状态变化复杂且不可预测:在对象状态变化频繁且不可预测的情况下,备忘录模式可以有效管理这些状态变化。
总结
- 备忘录模式是一种强大的设计模式,通过封装对象的内部状态,实现了状态的保存和恢复功能。虽然它能够有效地支持撤销和恢复操作,但在实际应用中需要注意内存消耗和管理复杂性,特别是在对象状态复杂且变化频繁的场景下。
- 备忘录模式存储原发器对象的内部状态。在需要时恢复原发器状态。
- 备忘录模式的核心是信息隐藏,即原发器需要向外界隐藏信息保持其封装性,但同时又需要将再保存到外界。
- 由于现代语言运行时如JAVA ,c#,都具有相当的对象序列化支持。因此往往采用效率较高,又较容易正确实现的序列化方案来实现备忘录模式。
备忘录模式和序列化
备忘录模式和序列化在某些方面具有相似的功能,但它们的目标和应用场景不同。
备忘录模式
1. 动机
备忘录模式的主要动机是保存和恢复对象的内部状态,而不破坏对象的封装性。它常用于实现撤销/恢复操作,让对象能够恢复到之前的某个状态。
2. 实现方式
备忘录模式通过创建一个“备忘录”对象,将发起人(Originator)对象的内部状态存储在该备忘录对象中。发起人可以使用这个备忘录对象来恢复其内部状态。备忘录模式通常包括三个角色:发起人、备忘录和负责人(Caretaker),其中负责人仅负责管理备忘录的保存和恢复,不直接访问备忘录的内容。
3. 应用场景
- 撤销/恢复操作:如文本编辑器、图形编辑器中的撤销功能。
- 历史记录管理:需要保存对象的状态以便以后恢复。
- 避免对象内部状态暴露:需要保存和恢复对象状态时,但不希望暴露对象的内部实现。
4. 优点
- 封装性:备忘录模式可以在不破坏对象封装性的前提下保存和恢复状态。
- 简单性:对外界来说,状态的保存和恢复过程是透明的,简化了撤销/恢复功能的实现。
5. 缺点
- 内存消耗:如果对象的状态较大或备忘录频繁创建,可能会导致较大的内存开销。
- 管理复杂性:当涉及多个状态备忘录时,备忘录的管理可能会变得复杂。
序列化
1. 动机
序列化的主要目的是将对象的状态转换为字节流,以便可以将其存储在文件、数据库或通过网络传输,并在以后将其反序列化为原始对象。
2. 实现方式
序列化将对象的状态转换为字节流,并将其保存到存储介质中。反序列化则是将字节流重新转换为对象。序列化通常依赖于语言或平台提供的内置机制,如Java的Serializable
接口或C++的自定义序列化逻辑。
3. 应用场景
- 持久化存储:将对象状态保存到文件或数据库中,以便以后恢复。
- 网络传输:在分布式系统中,将对象状态通过网络传输到另一个系统。
- 跨平台数据交换:不同平台之间的数据交换,需要将对象状态转换为通用的字节流格式。
4. 优点
- 持久性:序列化使得对象状态可以长期保存,并在需要时恢复。
- 跨平台:序列化后的数据可以在不同平台或系统之间传输和共享。
5. 缺点
- 性能开销:序列化和反序列化可能会引入性能开销,特别是在大型对象或频繁操作的情况下。
- 安全性:序列化数据可能会暴露对象的内部状态,存在安全风险,如果反序列化的对象来自不可信的来源,可能会导致安全漏洞。
对比总结
-
用途不同:
- 备忘录模式:主要用于保存和恢复对象的状态,以支持撤销/恢复操作,强调对象的封装性。
- 序列化:主要用于持久化或跨网络传输对象的状态,强调对象的持久性和可传输性。
-
实现方式不同:
- 备忘录模式:通常是在内存中保存对象的状态,并通过备忘录对象管理状态的保存和恢复。
- 序列化:将对象的状态转换为字节流,存储在外部介质中或通过网络传输。
-
封装性:
- 备忘录模式:注重保持对象的封装性,外部系统无法访问对象的内部状态。
- 序列化:通常会暴露对象的内部状态,可能会引发安全问题。
-
应用场景不同:
- 备忘录模式:适用于需要频繁保存和恢复对象状态的场景,如撤销功能。
- 序列化:适用于对象的持久化存储、网络传输、跨平台数据交换等场景。
备忘录模式更适合在应用程序内部管理对象的状态转换,而序列化更适合在应用程序与外部系统之间交换或持久化数据。两者可以结合使用,例如在分布式系统中,备忘录模式管理对象状态,而序列化用于将状态持久化或传输到其他节点。