21. 备忘录模式
一、备忘录模式
在实现撤销时,首先必须保存软件系统的历史状态。当用户需要取消错误操作并且返回到某个历史状态时,可以取出事先保存的历史状态来覆盖当前状态。备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤。当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。
备忘录模式(Memento Pattern)是一种行为设计模式,它允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。备忘录模式通过提供一个简单的接口,使得存储和恢复对象状态的操作与对象本身解耦,从而可以在不违反封装原则的情况下实现这些操作。
备忘录模式的主要角色如下:
- 原发器(Originator):它是一个普通类,可以创建一个备忘录,并存储其当前内部状态,也可以使用备忘录来恢复其内部状态。一般将需要保存内部状态的类设计为原发器。
- 备忘录(Memento):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用。原发器的设计在不同的编程语言中实现机制会有所不同。
- 负责人(Caretaker):负责人又称为管理者,他负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,他只负责存储对象,而不能修改对象,也无须知道对象的实现细节。
备忘录模式的核心是备忘录类以及用于管理备忘录的负责人类的设计。
二、C++实现备忘录模式
在使用备忘录模式时,首先应该存在一个原发器类Originator。在真实业务中,原发器类是一个具体的业务类,它包含一些用于存储成员数据的属性。
// 原发器,发起人
class Originator
{
private:
std::string state; // 状态信息
public:
Memento * createStateMemento(void); // 创建备忘录
void restoreStateMemento(Memento * memento); // 根据备忘录恢复状态
std::string getState(void);
void setState(std::string state);
};
Memento * Originator::createStateMemento(void)
{
return new Memento(this);
}
void Originator::restoreStateMemento(Memento * memento)
{
state = memento->getState();
}
std::string Originator::getState(void)
{
return state;
}
void Originator::setState(std::string state)
{
this->state = state;
}
对于备忘录类 Memento 而言,它通常提供了与原发器相对应的属性(可以是全部,也可以是部分)用于存储原发器的状态。
// 备忘录
class Memento
{
private:
std::string state;
std::string getState(void);
void setState(std::string state);
public:
friend class Originator;
Memento(Originator * originator);
};
Memento::Memento(Originator * originator)
{
state = originator->getState();
}
std::string Memento::getState(void)
{
return state;
}
void Memento::setState(std::string state)
{
this->state = state;
}
在设计备忘录类时需要考虑其封装性,除了 Originator 类,不允许其他类来调用备忘录类 Memento 的构造函数与相关方法。如果不考虑封装性,允许其他类调用 setState() 等方法,将导致在备忘录中保存的历史状态发生改变,通过撤销操作所恢复的状态就不再是真实的历史状态,备忘录模式也就失去了本身的意义。
在 C++ 中可以使用 friend 关键字,让原发器类和备忘录类成为友元类,相互之间可以访问对方的一些私有属性。在 Java 语言中可以将原发器类和备忘录类放在一个包中,让它们之间满足默认的包内可见性,也可以将备忘录类作为原发器类的内部类,使得只有原发器才可以访问备忘录中的数据,其他对象都无法直接使用备忘录中的数据。
对于负责人类 Caretaker,它用于保存备忘录对象,并提供 getMemento() 方法用于向客户端返回一个备忘录对象。原发器通过使用这个备忘录对象可以回到某个历史状态。
// 管理者
class Caretaker
{
private:
std::vector<Memento *> mementoList; // 备忘录列表
public:
Memento * getMemento(int index); // 获取第几个备忘录状态
void setMemento(Memento * memento); // 添加备忘录状态
};
Memento * Caretaker::getMemento(int index)
{
return mementoList[index];
}
void Caretaker::setMemento(Memento * memento)
{
mementoList.push_back(memento);
}
main() 函数:
#include <iostream>
#include <vector>
int main(void)
{
Originator * originator = new Originator();
Caretaker * caretaker = new Caretaker();
originator->setState("State1");
Memento * memento1 = originator->createStateMemento();
caretaker->setMemento(memento1);
originator->setState("State2");
Memento * memento2 = originator->createStateMemento();
caretaker->setMemento(memento2);
originator->setState("State3");
Memento * memento3 = originator->createStateMemento();
caretaker->setMemento(memento3);
std::cout << "Current State: " << originator->getState() << std::endl;
originator->restoreStateMemento(caretaker->getMemento(0));
std::cout << "Current State: " << originator->getState() << std::endl;
delete memento1;
delete memento2;
delete memento3;
delete originator;
delete caretaker;
return 0;
}
三、备忘录模式的总结
3.1、备忘录模式的优点
- 它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤。当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。
- 备忘录实现了对信息的封装。一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作。
3.2、备忘录模式的缺点
资源消耗过大。如果需要保存的原发器类的成员变量太多,就不可避免地需要占用大量的存储空间,每保存一次对象的状态都需要消耗一定的系统资源。
3.3、备忘录模式的适用场景
- 保存一个对象在某一个时刻的全部状态或部分状态,这样以后需要时就能够恢复到先前的状态,实现撤销操作。
- 防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象。