概述
与解释器模式、迭代器模式一样,备忘录模式也是一种行为设计模式。备忘录模式允许我们保存一个对象的状态,并在稍后恢复到这个状态。该模式非常适合于需要回滚、撤销或历史记录等功能的应用场景。通过使用备忘录模式,开发者可以轻松添加诸如撤销/重做、快照等高级功能,提升用户体验。
文本编辑器的撤销/重做功能是运用备忘录模式最典型的应用场景。当我们在文本编辑器中输入或修改内容时,编辑器会定期创建当前文档状态的“快照”,即备忘录。如果我们需要撤销最近的操作,编辑器可以从这些快照中恢复到之前的状态。类似地,重做操作也可以通过管理一系列的备忘录来实现。这使得用户可以轻松地回退或前进到任意一个历史版本,而不需要担心数据丢失。
基本原理
备忘录模式的基本原理可以概括为:创建一个独立的对象(备忘录),用于存储另一个对象(发起人)的内部状态。发起人在需要的时候可以请求创建一个备忘录,并将自身状态保存到这个备忘录中。同时,发起人也能够从备忘录中恢复状态。为了确保备忘录的安全性,通常会有一个管理者角色负责保管这些备忘录,但不允许对其进行任何操作。
备忘录模式主要由以下三个核心组件构成。
1、发起人。其职责包括创建备忘录以保存其内部状态,并通过备忘录来恢复其内部状态。发起人的状态通常是私有的,以保证数据的安全性和封装性。
2、备忘录。这是一个用来存储发起人对象内部状态的对象。备忘录的设计目的是:保护发起人的内部状态不被其他对象访问或篡改。因此,备忘录通常只允许发起人和管理者对其进行读取或写入操作,而禁止其他对象对它进行任何形式的修改。
3、管理者。管理者负责管理备忘录对象,但不对备忘录的内容进行任何操作。它的主要任务是保管备忘录对象,并在发起人需要恢复状态时提供相应的备忘录。管理者与备忘录之间的交互非常有限,这有助于保持系统的简洁性和模块化。
基于上面的核心组件,备忘录模式的实现主要有以下四个步骤。
1、定义发起人类。该类应包含一些方法来获取和设置其内部状态。此外,还需实现两个关键方法:CreateMemento和RestoreFromMemento。CreateMemento方法用于创建一个新的备忘录对象,并将当前状态保存到其中。RestoreFromMemento方法接收一个备忘录对象作为参数,并从中恢复发起人的状态。
2、定义备忘录类。该类仅需简单地存储发起人的状态即可。由于备忘录的主要目的是保护发起人的状态,因此它不应该公开任何修改状态的方法,仅提供必要的访问方法供发起人使用。
3、定义管理者类。该类负责保存和管理多个备忘录对象。管理者不需要知道备忘录的具体内容,只需要能够保存它们,并在需要时提供给发起人。管理者应当提供添加备忘录、获取特定备忘录的方法。
4、使用备忘录模式。当发起人需要保存状态时,调用CreateMemento方法并将返回的备忘录交给管理者保存。如果需要恢复状态,则从管理者那里获取对应的备忘录,并调用RestoreFromMemento方法。
实战代码
在下面的实战代码中,我们使用备忘录模式模拟了文本编辑器的撤销/重做功能。
首先,我们定义了备忘录类CTextMemento。它用于存储文本编辑器的状态,即当前文本内容。CTextMemento有一个私有成员变量m_strContent用来保存文本内容,并提供了一个公共方法GetContent来获取该文本内容。
接下来,我们定义了管理者类CHistoryManager。作为管理者角色,它负责管理备忘录对象。它包含两个向量:m_vctUndo和m_vctRedo,分别用于存储撤销栈和重做栈。SaveState方法用于将当前状态保存到撤销栈中。ClearRedoStack方法用于在进行新操作前清空重做栈,确保后续操作不会混淆历史记录。Undo方法用于从撤销栈弹出最近的状态并将其推入重做栈,然后返回新的当前状态。Redo方法则相反,从重做栈恢复状态,并将状态重新添加到撤销栈。
然后,我们定义了发起人类CTextEditor。作为发起人角色,它代表了文本编辑器本身。它拥有一个成员变量m_strContent来保存实际的文本内容,并且持有一个HistoryManager实例m_manager来管理其状态的历史记录。Type方法允许用户输入新的文本,并保存新文本到m_manager中。Undo和Redo方法分别调用m_manager.Undo和m_manager.Redo来执行撤销和重做操作,并更新当前文本内容。
最后,在main函数中,我们模拟了文本编辑器的行为,包括:输入文本、撤销和重做操作。
#include <iostream>
#include <vector>
#include <string>
using namespace std;
// 备忘录类,用于存储文本编辑器的状态
class CTextMemento
{
public:
CTextMemento(const string& strContent) : m_strContent(strContent) {}
string GetContent() const
{
return m_strContent;
}
private:
string m_strContent;
};
// 管理者类,用于管理备忘录对象
class CHistoryManager
{
public:
void SaveState(const CTextMemento& memento)
{
m_vctUndo.push_back(memento);
}
void ClearRedoStack()
{
m_vctRedo.clear();
}
CTextMemento Undo()
{
if (!m_vctUndo.empty())
{
CTextMemento memento = m_vctUndo.back();
m_vctUndo.pop_back();
m_vctRedo.push_back(memento);
if (!m_vctUndo.empty())
{
return m_vctUndo.back();
}
}
return CTextMemento("");
}
CTextMemento Redo()
{
if (!m_vctRedo.empty())
{
CTextMemento memento = m_vctRedo.back();
m_vctRedo.pop_back();
m_vctUndo.push_back(memento);
return memento;
}
return CTextMemento("");
}
private:
vector<CTextMemento> m_vctUndo;
vector<CTextMemento> m_vctRedo;
};
// 发起人类,即文本编辑器
class CTextEditor
{
// 允许HistoryManager访问TextEditor的内容
friend class CHistoryManager;
public:
void Type(const string& text)
{
// 清空重做栈
m_manager.ClearRedoStack();
m_strContent += text;
m_manager.SaveState(CTextMemento(m_strContent));
cout << "Typed: " << text << endl;
}
void Undo()
{
CTextMemento memento = m_manager.Undo();
m_strContent = memento.GetContent();
cout << "Undo to: " << m_strContent << endl;
}
void Redo()
{
CTextMemento memento = m_manager.Redo();
m_strContent = memento.GetContent();
cout << "Redo to: " << m_strContent << endl;
}
friend ostream& operator<<(ostream& os, const CTextEditor& editor)
{
os << "Current Content: " << editor.m_strContent;
return os;
}
private:
CHistoryManager m_manager;
string m_strContent;
};
int main()
{
CTextEditor editor;
editor.Type("Hello");
cout << editor << endl;
editor.Type(", Hope_Wisdom");
cout << editor << endl;
editor.Undo();
cout << editor << endl;
editor.Undo();
cout << editor << endl;
editor.Redo();
cout << editor << endl;
editor.Redo();
cout << editor << endl;
return 0;
}
总结
备忘录模式允许对象保存其内部状态而不暴露其实现细节,这意味着,发起人可以安全地存储和恢复其状态,而无需担心破坏封装性或泄露敏感信息。对于拥有复杂内部状态的对象,备忘录模式提供了一种有效的方式来捕获和恢复这些状态,这对于需要长期运行的任务或处理大量数据的应用程序特别有用。
但创建和保存大量的备忘录可能会占用较多内存资源,尤其是在处理大规模数据或长时间运行的任务时。如果频繁地创建备忘录,可能导致性能问题和内存溢出的风险。虽然备忘录模式本身的概念很简单,但在实际应用中,特别是在并发环境或多线程场景下,正确实现和管理备忘录可能会变得相当复杂。确保不同线程间备忘录的一致性和同步性,是一个比较大的挑战。