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

概述

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

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

基本原理

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

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

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;
}

总结

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

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

相关推荐
兵慌码乱13 小时前
面向桌面端的资产管理系统分层架构设计与核心模块实现
python·系统架构·sqlite·pyqt5·数据库设计·桌面应用开发·mvc架构
咖啡八杯14 小时前
GoF设计模式——策略模式
java·后端·spring·设计模式
doiito21 小时前
【Agent Harness】Gliding Horse 本体论系统设计:给 AI Agent 装上“语义大脑”
ai·rust·架构设计·系统设计·ai agent
doiito2 天前
【Agent Harness】为什么我把 JSON‑LD “编译成 DAG” 后,整个 Agent 平台立刻聪明了
ai·rust·架构设计·系统设计·ai agent
槑有老呆2 天前
别再手搓 Prompt 了,那个叫"手动挡循环"
设计模式
用户6919026813393 天前
Vibe Coding 开发项目的基本范式
人工智能·设计模式·代码规范
怕浪猫4 天前
领域特定语言(Domain-Specific Language, DSL)
设计模式·程序员·架构
Larcher6 天前
AI Loop:让AI像人一样自主完成任务的核心机制
javascript·人工智能·设计模式