实战设计模式之备忘录模式

概述

与解释器模式、迭代器模式一样,备忘录模式也是一种行为设计模式。备忘录模式允许我们保存一个对象的状态,并在稍后恢复到这个状态。该模式非常适合于需要回滚、撤销或历史记录等功能的应用场景。通过使用备忘录模式,开发者可以轻松添加诸如撤销/重做、快照等高级功能,提升用户体验。

文本编辑器的撤销/重做功能是运用备忘录模式最典型的应用场景。当我们在文本编辑器中输入或修改内容时,编辑器会定期创建当前文档状态的"快照",即备忘录。如果我们需要撤销最近的操作,编辑器可以从这些快照中恢复到之前的状态。类似地,重做操作也可以通过管理一系列的备忘录来实现。这使得用户可以轻松地回退或前进到任意一个历史版本,而不需要担心数据丢失。

基本原理

备忘录模式的基本原理可以概括为:创建一个独立的对象(备忘录),用于存储另一个对象(发起人)的内部状态。发起人在需要的时候可以请求创建一个备忘录,并将自身状态保存到这个备忘录中。同时,发起人也能够从备忘录中恢复状态。为了确保备忘录的安全性,通常会有一个管理者角色负责保管这些备忘录,但不允许对其进行任何操作。

备忘录模式主要由以下三个核心组件构成。

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函数中,我们模拟了文本编辑器的行为,包括:输入文本、撤销和重做操作。

cpp 复制代码
#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;
}

总结

备忘录模式允许对象保存其内部状态而不暴露其实现细节,这意味着,发起人可以安全地存储和恢复其状态,而无需担心破坏封装性或泄露敏感信息。对于拥有复杂内部状态的对象,备忘录模式提供了一种有效的方式来捕获和恢复这些状态,这对于需要长期运行的任务或处理大量数据的应用程序特别有用。

但创建和保存大量的备忘录可能会占用较多内存资源,尤其是在处理大规模数据或长时间运行的任务时。如果频繁地创建备忘录,可能导致性能问题和内存溢出的风险。虽然备忘录模式本身的概念很简单,但在实际应用中,特别是在并发环境或多线程场景下,正确实现和管理备忘录可能会变得相当复杂。确保不同线程间备忘录的一致性和同步性,是一个比较大的挑战。

相关推荐
Niuguangshuo3 分钟前
Python设计模式:对象池
开发语言·python·设计模式
Hi-Dison26 分钟前
Ubuntu与OpenHarmony OS 5.0显示系统架构比较
linux·ubuntu·系统架构
zhaoyqcsdn2 小时前
建造者模式详解及其在自动驾驶场景的应用举例(以C++代码实现)
c++·笔记·设计模式
xxy!4 小时前
Spring 框架中用到的设计模式
java·spring·设计模式
Leaf吧4 小时前
java 设计模式 原型模式
java·设计模式·原型模式
歡進5 小时前
🔥 每个故事都是一种设计模式
前端·javascript·设计模式
头顶秃成一缕光5 小时前
TCP的三次握手和四次挥手
java·服务器·tcp/ip·软件工程
oioihoii5 小时前
软考软件设计师考试情况与大纲概述
软件工程
Auroral1569 小时前
结构型模式:适配器模式
设计模式