C++ 设计模式——备忘录模式

C++ 设计模式——备忘录模式

备忘录(Memento)模式也称为快照(Snapshot)模式,是一种行为型模式,主要用于防止数据丢失。它通过对对象的状态进行备份,以便在未来需要时可以恢复这些数据。换句话说,该模式能够将某个时间点的对象内部状态保存下来,并在必要时根据保存的内容将该对象恢复到当时的状态。备忘录模式的结构比较简单,使用频率相对较低,但在特定场景下非常有用。

1. 主要组成成分

  1. 原发器(Originator): 负责创建备忘录对象以及根据备忘录恢复自身状态的类。
  2. 备忘录(Memento): 用于存储原发器的内部状态的类。
  3. 负责人/管理员(Caretaker): 负责管理备忘录的类,控制备忘录的存取,但不修改备忘录的内容。

2. 逐步构建备忘录模式

该示例代码模拟了一个游戏中的玩家角色(Fighter),它能够保存和恢复其状态(如生命值、魔法值和攻击力)。以下是每个步骤的详细说明:

步骤1: 创建备忘录

创建备忘录类,该类将保存原发器的状态。在这一步,定义一个名为 FighterMemento 的类。这个类的主要作用是存储玩家角色的状态信息。具体来说,它保存了玩家的生命值、魔法值和攻击力。通过使用私有构造函数,该类确保只能通过 Fighter 类创建备忘录,从而保护内部状态不被随意访问。

//玩家主角相关的备忘录类
class FighterMemento
{
private:
    //构造函数,用private修饰以防止在外部被随意创建
    FighterMemento(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}

private:
    //提供一些供Fighter类来访问的接口,用private修饰防止被任意类访问
    friend class Fighter; //友元类Fighter可以访问本类的私有成员函数
    int getLife() const { return m_life; }
    void setLife(int life) { m_life = life; }
    int getMagic() const { return m_magic; }
    void setMagic(int magic) { m_magic = magic; }
    int getAttack() const { return m_attack; }
    void setAttack(int attack) { m_attack = attack; }
private:
    //玩家主角类中要保存起来的数据,就放到这里来
    int m_life;    //生命值
    int m_magic;   //魔法值
    int m_attack;  //攻击力
};

步骤2: 实现原发器

定义原发器类 Fighter,它负责创建备忘录并能从备忘录中恢复状态。在此步骤中,定义了 Fighter 类,它代表游戏中的玩家角色。此类包含了角色的基本属性(生命值、魔法值和攻击力),并提供了方法来创建备忘录和从备忘录中恢复状态。通过 createMomento() 方法,当前状态将被保存到备忘录中,而 restoreMomento() 方法则用于从备忘录恢复状态。

//玩家主角类
class Fighter
{
public:
    //构造函数
    Fighter(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}

public:
    //将玩家数据写入备忘录(创建备忘录,并在其中存储了当前状态)
    FighterMemento* createMomento()
    {
        return new FighterMemento(m_life, m_magic, m_attack);
    }
    //从备忘录中恢复玩家数据
    void restoreMomento(FighterMemento* pfm)
    {
        m_life = pfm->getLife();
        m_magic = pfm->getMagic();
        m_attack = pfm->getAttack();
    }
    //为测试目的引入的接口,设置玩家的生命值为0(玩家死亡)
    void setToDead()
    {
        m_life = 0;
    }
    //用于输出一些信息
    void displayInfo()
    {
        cout << "玩家主角当前的生命值、魔法值、攻击力分别为:" << m_life << "," << m_magic << "," << m_attack << endl;
    }

private:
    //角色属性
    int m_life;    //生命值
    int m_magic;   //魔法值
    int m_attack;  //攻击力
    //......其他数据略
};
步骤3: 创建管理者(负责人)类

定义管理者类 FCareTaker,用于管理备忘录。在这一步,定义了 FCareTakerFCareTaker2 类。它们负责管理备忘录对象。FCareTaker 类能够保存单个备忘录,而 FCareTaker2 类支持多个备忘录的管理。在游戏中,这允许存储多个角色状态快照,以便在需要时进行恢复。

//管理者(负责人)类
class FCareTaker
{
public:
    //构造函数
    FCareTaker(FighterMemento* ptmpfm) :m_pfm(ptmpfm) {} //形参是指向备忘录对象的指针

    //获取指向备忘录对象的指针
    FighterMemento* getMemento()
    {
        return m_pfm;
    }
    //保存指向备忘录对象的指针
    void setMemento(FighterMemento* ptmpfm)
    {
        m_pfm = ptmpfm;
    }
private:
    FighterMemento* m_pfm; //指向备忘录对象的指针
};

//-----------------
//支持多个快照的负责人(管理者)类
class FCareTaker2
{
public:
    //析构函数用于释放资源
    ~FCareTaker2()
    {
        for (auto iter = m_pfmContainer.begin(); iter != m_pfmContainer.end(); ++iter)
        {
            delete (*iter);
        } //end for
    }
    //保存指向备忘录对象的指针
    void setMemento(FighterMemento* ptmpfm)
    {
        m_pfmContainer.push_back(ptmpfm);
    }
    //获取指向备忘录对象的指针
    FighterMemento* getMemento(int index)
    {
        auto iter = m_pfmContainer.begin();
        for (int i = 0; i <= index; ++i)
        {
            if (i == index)
                return (*iter);
            else
                ++iter;
        } //end for
        return nullptr;
    }
private:
    //存储备忘录对象指针的容器
    vector<FighterMemento*> m_pfmContainer;  //#include <vector>
};
步骤4: 客户端使用

在客户端代码中使用备忘录模式。在客户端代码中,实例化 FighterFCareTaker2 对象,并模拟角色的状态变化。通过调用 createMomento() 方法创建备忘录,然后更改角色状态并再次创建备忘录。最后,通过 restoreMomento() 方法从备忘录恢复角色状态,并输出角色的当前状态。

int main()
{

    Fighter* p_fighter = new Fighter(800, 200, 300);
    //(1)显示玩家主角在与BOSS战斗之前的信息
    p_fighter->displayInfo();

    //(2)为玩家主角类对象创建一个备忘录对象(其中保存了当前主角类对象中的必要信息)
    //FighterMemento* p_fighterMemo = p_fighter->createMomento();
    FCareTaker* pfcaretaker = new FCareTaker(p_fighter->createMomento());

    //(3)玩家与BOSS开始战斗
    cout << "玩家主角与BOSS开始进行激烈的战斗------" << endl;
    p_fighter->setToDead();   //玩家主角在与BOSS战斗中,生命值最终变成0而死亡(被BOSS击败)
    p_fighter->displayInfo(); //显示玩家主角在与BOSS战斗之后的信息

    //(4)因为在与BOSS战斗之前已经通过NPC保存了游戏进度,这里模拟载入游戏进度,恢复玩家主角类对象的数据,让其可以与BOSS再次战斗
    cout << "玩家主角通过备忘录恢复自己的信息------" << endl;
    //p_fighter->restoreMomento(p_fighterMemo);
    p_fighter->restoreMomento(pfcaretaker->getMemento());
    p_fighter->displayInfo(); //显示玩家主角通过备忘录恢复到战斗之前的信息

    //(5)释放资源
    //delete p_fighterMemo;
    delete pfcaretaker->getMemento();
    delete pfcaretaker; //新增
    delete p_fighter;

    Fighter* p_fighter2 = new Fighter(800, 200, 300);
    FCareTaker2* pfcaretaker2 = new FCareTaker2();

    pfcaretaker2->setMemento(p_fighter2->createMomento()); // 第一次快照,生命值为800
    p_fighter2->setToDead(); // 改变玩家主角的生命值
    pfcaretaker2->setMemento(p_fighter2->createMomento()); // 第二次快照,生命值为0

    p_fighter2->displayInfo(); // 当前生命值为0
    cout << "------------------" << endl;

    // 恢复第一次快照,生命值恢复为800
    p_fighter2->restoreMomento(pfcaretaker2->getMemento(0));
    p_fighter2->displayInfo(); // 玩家主角生命值应恢复为800

    // 释放资源
    delete p_fighter2;
    delete pfcaretaker2;

    return 0;
}

3. 备忘录模式 UML 图

备忘录模式 UML 图

UML 图解析

备忘录模式的 UML 图中包含3种角色:

  • Originator (原发器):
    • 原发器是一个普通的业务类,它负责创建备忘录以保存自身的当前内部状态。后续,原发器可以使用备忘录来恢复其内部状态。原发器可以根据需要决定备忘录将存储哪些内部状态。这里指 Fighter 类实现。
  • Memento (备忘录):
    • 备忘录是一个对象,用于存储原发器在某个时刻的内部状态。备忘录的设计通常会参考原发器的设计。为了保护备忘录中的信息不被外部访问,除了创建备忘录的原发器外,其他对象不应直接使用或修改备忘录。因此,备忘录的接口一般使用 private 修饰,并将原发器类设置为友元类。这样可以避免暴露原发器管理的信息,使得备忘录成为一个被动的存储结构。这里指FighterMemento 类。
  • Caretaker (负责人/管理者):
    • 负责人负责保存备忘录,并可以将备忘录传递给其他对象,但不需要了解备忘录的具体细节,也不能对备忘录中的内容进行操作或检查。负责人的主要职责是管理备忘录的生命周期,确保其有效性和正确性。这里指 FCareTaker 类实现。

4. 备忘录模式的优点

  • 封装性: 备忘录模式将对象的状态封装在备忘录中,使得外部无法访问对象的内部状态。
  • 简化恢复操作: 通过备忘录,可以方便地恢复对象到之前的状态,而不需要了解对象的具体实现细节。
  • 历史记录管理: 可以轻松实现对象状态的历史记录功能。

5. 备忘录模式的缺点

  • 内存消耗: 如果对象状态庞大或频繁创建备忘录,可能导致内存消耗增大。
  • 状态管理复杂性: 在需要管理多个状态时,备忘录的数量可能会迅速增加,管理起来会变得复杂。

6. 备忘录模式适用场景

  • 文本编辑器: 保存文档的历史状态,以便用户能够撤销和重做操作。
  • 游戏存档: 在游戏中保存玩家的状态,以便在需要时恢复。
  • 事务处理: 在数据库操作中保存事务的状态,以支持回滚操作。

总结

备忘录模式是一种有效的设计模式,能够帮助开发者管理对象状态的保存与恢复。在需要保留对象历史状态的应用场景中,备忘录模式提供了一种高效、简单的解决方案。

完整代码

#include <iostream>
#include <vector>

using namespace std;

//玩家主角相关的备忘录类
class FighterMemento
{
private:
    //构造函数,用private修饰以防止在外部被随意创建
    FighterMemento(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}

private:
    //提供一些供Fighter类来访问的接口,用private修饰防止被任意类访问
    friend class Fighter; //友元类Fighter可以访问本类的私有成员函数
    int getLife() const { return m_life; }
    void setLife(int life) { m_life = life; }
    int getMagic() const { return m_magic; }
    void setMagic(int magic) { m_magic = magic; }
    int getAttack() const { return m_attack; }
    void setAttack(int attack) { m_attack = attack; }
private:
    //玩家主角类中要保存起来的数据,就放到这里来
    int m_life;    //生命值
    int m_magic;   //魔法值
    int m_attack;  //攻击力
};


//玩家主角类
class Fighter
{
public:
    //构造函数
    Fighter(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}


public:
    //将玩家数据写入备忘录(创建备忘录,并在其中存储了当前状态)
    FighterMemento* createMomento()
    {
        return new FighterMemento(m_life, m_magic, m_attack);
    }
    //从备忘录中恢复玩家数据
    void restoreMomento(FighterMemento* pfm)
    {
        m_life = pfm->getLife();
        m_magic = pfm->getMagic();
        m_attack = pfm->getAttack();
    }
    //为测试目的引入的接口,设置玩家的生命值为0(玩家死亡)
    void setToDead()
    {
        m_life = 0;
    }
    //用于输出一些信息
    void displayInfo()
    {
        cout << "玩家主角当前的生命值、魔法值、攻击力分别为:" << m_life << "," << m_magic << "," << m_attack << endl;
    }


private:
    //角色属性
    int m_life;    //生命值
    int m_magic;   //魔法值
    int m_attack;  //攻击力
    //......其他数据略
};

//---------------------
//管理者(负责人)类
class FCareTaker
{
public:
    //构造函数
    FCareTaker(FighterMemento* ptmpfm) :m_pfm(ptmpfm) {} //形参是指向备忘录对象的指针

    //获取指向备忘录对象的指针
    FighterMemento* getMemento()
    {
        return m_pfm;
    }
    //保存指向备忘录对象的指针
    void setMemento(FighterMemento* ptmpfm)
    {
        m_pfm = ptmpfm;
    }
private:
    FighterMemento* m_pfm; //指向备忘录对象的指针
};

//-----------------
//支持多个快照的负责人(管理者)类
class FCareTaker2
{
public:
    //析构函数用于释放资源
    ~FCareTaker2()
    {
        for (auto iter = m_pfmContainer.begin(); iter != m_pfmContainer.end(); ++iter)
        {
            delete (*iter);
        } //end for
    }
    //保存指向备忘录对象的指针
    void setMemento(FighterMemento* ptmpfm)
    {
        m_pfmContainer.push_back(ptmpfm);
    }
    //获取指向备忘录对象的指针
    FighterMemento* getMemento(int index)
    {
        auto iter = m_pfmContainer.begin();
        for (int i = 0; i <= index; ++i)
        {
            if (i == index)
                return (*iter);
            else
                ++iter;
        } //end for
        return nullptr;
    }
private:
    //存储备忘录对象指针的容器
    vector<FighterMemento*> m_pfmContainer;  //#include <vector>
};

int main()
{

    Fighter* p_fighter = new Fighter(800, 200, 300);
    //(1)显示玩家主角在与BOSS战斗之前的信息
    p_fighter->displayInfo();

    //(2)为玩家主角类对象创建一个备忘录对象(其中保存了当前主角类对象中的必要信息)
    //FighterMemento* p_fighterMemo = p_fighter->createMomento();
    FCareTaker* pfcaretaker = new FCareTaker(p_fighter->createMomento());

    //(3)玩家与BOSS开始战斗
    cout << "玩家主角与BOSS开始进行激烈的战斗------" << endl;
    p_fighter->setToDead();   //玩家主角在与BOSS战斗中,生命值最终变成0而死亡(被BOSS击败)
    p_fighter->displayInfo(); //显示玩家主角在与BOSS战斗之后的信息

    //(4)因为在与BOSS战斗之前已经通过NPC保存了游戏进度,这里模拟载入游戏进度,恢复玩家主角类对象的数据,让其可以与BOSS再次战斗
    cout << "玩家主角通过备忘录恢复自己的信息------" << endl;
    //p_fighter->restoreMomento(p_fighterMemo);
    p_fighter->restoreMomento(pfcaretaker->getMemento());
    p_fighter->displayInfo(); //显示玩家主角通过备忘录恢复到战斗之前的信息

    //(5)释放资源
    //delete p_fighterMemo;
    delete pfcaretaker->getMemento();
    delete pfcaretaker; //新增
    delete p_fighter;

    Fighter* p_fighter2 = new Fighter(800, 200, 300);
    FCareTaker2* pfcaretaker2 = new FCareTaker2();

    pfcaretaker2->setMemento(p_fighter2->createMomento()); // 第一次快照,生命值为800
    p_fighter2->setToDead(); // 改变玩家主角的生命值
    pfcaretaker2->setMemento(p_fighter2->createMomento()); // 第二次快照,生命值为0

    p_fighter2->displayInfo(); // 当前生命值为0
    cout << "------------------" << endl;

    // 恢复第一次快照,生命值恢复为800
    p_fighter2->restoreMomento(pfcaretaker2->getMemento(0));
    p_fighter2->displayInfo(); // 玩家主角生命值应恢复为800

    // 释放资源
    delete p_fighter2;
    delete pfcaretaker2;

    return 0;
}
  • 31
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值