备忘录模式

沃日,竟然被小怪打死了,先回个档 ~

意图

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态.

动机

在大多数的软件,例如 wordexcelphotoshop 等等都是支持 Ctrl+Z 撤销的操作,他们不仅能撤销一步,甚至有一个执行过操作的列表. 而能撤销,意味着需要保存一个对象操作前的状态,有点类似游戏的存档功能,但是又只希望这是一个档,不会暴露过多额外信息,被外界随意篡改,而备忘录模式恰好提供了记录对象内部状态,同时可以避免暴露过多的实现细节,不破坏对象的封装性的一种思路.

UML 图

uml

  • Memento (备忘录)
    • 备忘录存储原发器对象的内部状态,原发器 根据需要 决定备忘录存储原发器的哪些内部状态。
    • 防止原发器以外的其他对象访问备忘录。管理者 (caretaker) 只能看到备忘录的窄接口–它只能将备忘录传递给其他对象; 原发器能够看到一个宽接口,允许它访问返回到先前状态所需的数据。理想的情况是只允许生成备忘录的那个原发器访问被备忘录的内部状态。
  • Originator (原发器)
    • 原发器创建一个备忘录,用以记录当前时刻它的内部状态
    • 使用备忘录恢复内部状态
  • Caretaker (负责人)
    • 负责保存好备忘录
    • 不能对备忘录的内容进行操作或检查

这里有两点我觉得值得再好好提一下

类间的协作

  • 这里的 Originator 就是我们正常使用的对象
  • 需要备份 的时候,Originator 根据需要,用它内部状态创建一个备份对象 Memento,而这个中间件对象可以通过 Caretaker 来管理,比如以一个列表的形式展示所有存档的记录形式等等
  • 还原备份 的时候,从 Caretaker 取出需要的还原的备份状态 Memento,将这个对象设置给 OriginatorOriginatorMemento 取出保存的状态即可恢复备份的状态。

这里的 Caretaker 可能也不是必须的,它只是用来管理 Memento 的对象,算是备忘录模式的 “编外人员”

封装细节

Memento 需要对 Caretaker 等其他对象隐藏对象属性和实现细节,避免状态被篡改等问题,所以也就出现了宽窄接口的需求,而这一部分可以通过将重要的接口函数定义从 private 以及友元的形式来实现。而上文提到的理想情况只允许生成备忘录的那个原发器访问被备忘录的内部状态,之后我会尝试用例子说明。

应用场景

  • 必须保存一个对象在某一个时刻的 (部分) 状态,这样以后需要时它才能恢复到先前的状态。
  • 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。

举个例子

举个比较简单例子,而且这个例子不涉及 Caretaker,所以只有 MementoOriginator

首先看一下 Memento,我这里除了保存 Originator 需要它保存的内部状态,还引入了 uuid 这个字段,主要目的就是只能允许生成备忘录的原发器才能访问备忘录的内部状态信息
本来需要同时提供 getState()setState() 2个接口,但是因为初始化的时候就完成了赋值,所有就省略了 setState() 函数了

而且这部分最关键的部分就是 friend class OriginatorMemento 的接口都声明为 private,而将 Originator 声明为 Memento 的友元类。从而达到封装的功效

#include <iostream>
using namespace std;

class Memento {
public:
    ~Memento(){}

private:
    friend class Originator;
    Memento(string state, const string uuid) : m_state(state), m_uuid(uuid){}

    bool getState(string &state, string uuid) {
        if (m_uuid == uuid) {
            state = m_state;
            return true;
        }
        return false;
    }

private:
    string m_state;
    string m_uuid;
};

接下来就是 Originator,因为需要有个 UUID 的信息传入 Memento 中,为了简单,就先用当前的时间戳代替一下

#include <time.h>
#include "memento.h"

class Originator {
public:
    Originator() : m_state("NULL") {
        time(&m_uuid);
    }

    void setState(string state) {
        m_state = state;
    }

    void showState() {
        cout << "Current state : "<< m_state << endl;
    }

    Memento *createMemento() {
        cout << "backup state : " << m_state << endl;
        return new Memento(m_state, std::to_string(m_uuid));
    }

    void setMemento(Memento* memento) {
        if (memento) {
            bool success = memento->getState(m_state, std::to_string(m_uuid) );
            if (success)
                cout << "Undo Success" << endl;
            else
                cout << "Undo Failure" << endl;
        }
    }

private:
    string m_state;
    time_t m_uuid;
};

最后看一下 main() 函数调用部分:

#include "originator.h"
#include <windows.h>

#define SAFE_DELETE(p) if(p) {delete p; p=nullptr;}

int main()
{
    Originator *originator1 = new Originator;
    originator1->showState();

    originator1->setState("first");
    originator1->showState();

    Memento *backup1 = originator1->createMemento();

    originator1->setState("second");
    originator1->showState();

    originator1->setMemento(backup1);
    originator1->showState();

    cout << "--------------------" << endl;
    Sleep(1000);

    Originator *originator2 = new Originator;
    originator2->showState();
    originator2->setMemento(backup1);
    originator2->showState();


    SAFE_DELETE(originator1);
    SAFE_DELETE(originator2);
    SAFE_DELETE(backup1);

    return 0;
}

运行结果

Current state : NULL
Current state : first
backup state : first
Current state : second
Undo Success
Current state : first
--------------------
Current state : NULL
Undo Failure
Current state : NULL

总结

将需要备忘的内部信息封装进 Memento 对象中,并通过将接口定义成 private 以及友元函数的形式进一步加强封装,而对 Memento 的管理则可以提供新的类进行管理~