首页 > 编程笔记 > C++笔记 阅读:1

C++备忘录模式及其实现(非常详细)

备忘录模式(Memento Pattern)的目的是在不违背封装原则的前提下,捕获对象的内部状态,并在之后能够恢复到该状态。

备忘录模式的优点是可以在不破坏对象封装的情况下恢复对象的状态。它将状态保存在备忘录对象中,从而对外部是不可见的。同时,备忘录模式使原发器对象的状态管理由负责人对象来完成,从而提高了代码的可维护性和可扩展性。

备忘录模式可能会消耗较大的内存空间,特别是在保存大量状态的情况下,其次,备忘录模式的实现可能会增加代码的复杂性和维护成本。

传统备忘录模式

传统备忘录模式一般涉及 3 个角色:原发器(Originator)、备忘录(Memento)和负责人(Caretaker),如下图所示:


图 1 传统备忘录模式UML简图

C++11元编程下的结构设计

使用 C++11 元编程方式将状态数据用模板参数代替,然后整体结构的构成仍然和传统的方式保持一致。采用这种设计方式能够在提高代码的通用性的同时保持和传统模式一致,也保持了编码习惯的一致,如下图所示。


图 2 C++11模板备忘录模式UML简图

C++备忘录模式实现和解析

备忘录模式代码分成两部分,分别是以名字空间 private__ 保护起来的部分和对外暴露出来的部分,类 memento 和 originator 被保护在 private__ 名字空间内,类 caretaker 是唯一暴露给使用者的接口类。

memento 和 originator 在使用时通过在类 caretaker 中的别名来获取具体类型,这样做的好处是在特化处理时仅需要特化一个 caretaker 便能够简化开发过程的编码工作。

备忘录类 memento 实际上是一个备忘录条目,多个备忘录条目会记录在 caretaker 中,memento 模板类的模板参数 T 是状态数据类型,代码如下:
//designM/memo.hpp
namespace private__{
    template<typename T >
    class memento{
        static_assert(std::is_copy_constructible<T >::value,"必须支持复制构造");
        static_assert(std::is_copy_assignable<T >::value,"必须支持赋值复制操作");
    public:
        memento(const T&state):m_state__(state){}

        T getState()const{
             return m_state__;
        }
        //重载箭头操作符号和解引用操作符,方便数据访问
        T * operator->(){return&m_state__;}
        T& operator * (){return m_state__;}
    private:
        T m_state__;
    };

原发器 originator 可以用来操作备忘录,将自己的状态添加到备忘录中或者从备忘录中恢复状态。成员函数 setState 和 getState 用来指定或者获取 createMemento 状态数据。createMemento() 和 restoreMemento() 成员函数用来创建或者恢复状态,代码如下:
template<typename T>
class originator {
public:
    void setState(const T& state) {
        m_state__ = state;
    }

    T& getState() {
        return m_state__;
    }

    memento<T> createMemento() const {
        return memento<T>(m_state__);
    }

    void restoreMemento(const memento<T>& memento) {
        m_state__ = memento.getState();
    }

    T* operator->() {
        return &m_state__;
    }

    T& operator*() {
        return m_state__;
    }

private:
    T m_state__;
};

模板类 caretaker 记录了所有备忘录信息。模板参数T是备忘录状态的数据类型,内部针对 T 进行退化和移除指针的操作,用来提高 T 函数的覆盖范围,从而增加类的通用性。

内部针对原发器类 private__::originator 和备忘录类 private__::memento 进行别名处理。在使用备忘录模块时可以通过 caretaker::memo_t 获取备忘录类型,使用 caretaker::orgnt_t 获取原发器类型,代码如下:
template<typename T >
class caretaker
{
public:
    using stat_t = typename std::remove_pointer<
                      typename std::decay< T >::type >::type;

    using memo_t = private__::memento< stat_t >;
    using orgnt_t = private__::originator< stat_t >;
public:
    caretaker(){}
    virtual~caretaker(){}

成员函数 add() 用来添加备忘信息;get() 方法用来读取指定数据的索引;remove() 函数用来移除指定索引的备忘信息。内部重载了下标运算符,用于获取指定的索引的应用,通过下标运算符可以修改备忘信息,代码如下:
    void add(const memo_t&memento)
    {
        m_mementos__.push_back(memento);
    }
    memo_t get(int index)const
    {
        return m_mementos__[index];
    }
    void remove(int idx)
    {
        if(idx< m_mementos__.size()){
             m_mementos__.erase(idx);
        }
    }
    void clear()
    {
        m_mementos__.erase(m_mementos__.begin(),m_mementos__.end());
    }
    memo_t&operator[](int idx)
    {
        return m_mementos__[idx];
    }
private:
    std::vector<memo_t > m_mementos__;
};

C++备忘录模式应用示例

在下面的示例中首先特化了负责人 caretaker 模板,使用 int 类型作为状态类型,并利用特化后的类型定义一个负责人对象 caretaker,引出原发器对象类型 crtk::orgnt_t,定义了一个 originator 原发器对象。

修改原发器对象的状态,输出状态数据,将状态记录到负责人对象中,然后修改状态,并输出新的状态数据。最后恢复状态,并输出恢复后的状态信息,代码如下:
int main() {
    using crtk = caretaker<int>;
    crtk caretaker;
    crtk::orgnt_t originator;

    // 设置初始状态
    int state = 5;
    originator.setState(&state);
    std::cout << "初始状态: " << *originator.getState() << std::endl;

    // 创建备忘录并保存到caretaker中
    caretaker.add(originator.createMemento());

    // 修改状态
    *originator.getState() = 10;
    std::cout << "修改后的状态: " << *originator.getState() << std::endl;

    // 使用备忘录恢复原始状态
    originator.restoreMemento(caretaker.get(0));
    std::cout << "恢复后的状态: " << *originator.getState() << std::endl;

    return 0;
}
运行结果如下:

初始状态:5
修改后的状态:10
恢复后的状态:5

相关文章