一、

背景

备忘录模式(Memento Pattern)是一种行为设计模式,它允许在不暴露对象内部状态的情况下保存和恢复对象之前的状态(即快照)。这种模式特别适用于实现某些功能,如撤销/重做(undo/redo)操作,或是为了保存对象的某个稳定状态以便将来可能恢复到该状态。

这个模式在实际项目中我个人用的不多,这里只是对这种设计类型进行一个学习记录。

组成结构

  • 备忘录(Memento):这个对象负责存储发起人对象的内部状态,并且可以防止Originator以外的对象访问备忘录。
  • 发起人(Originator):这是需要保存某个时刻状态的对象。它创建一个包含其当前内部状态的备忘录对象,并使用备忘录对象来恢复其内部状态。
  • 管理者(Caretaker):它负责保存备忘录对象(备忘录可能不止一个,不同时刻会创建多个备忘录),但是不能对备忘录的内容进行操作或检查。当用户需要回到某个状态时,Caretaker会从存储中取得相应的这个备忘录对象并将其返回给Originator对象。

C++示例

#include <iostream>  
#include <memory>  

// 备忘录类  
class Memento {
public:
	explicit Memento(int state) : m_state(state) {}
	int GetState() const { return m_state; }

private:
	int m_state;
};

// 发起人类  
class Originator {
public:
	Originator(int initialState) : m_state(initialState) {}

	// 创建备忘录  
	std::unique_ptr<Memento> createMemento() {
		return std::make_unique<Memento>(m_state);
	}

	// 从备忘录恢复状态  
	void RestoreFromMemento(const Memento& memento) {
		m_state = memento.getState();
	}

	// 获取当前状态  
	int GetState() const { return m_state; }

	// 设置状态  
	void SetState(int state) { m_state = state; }

private:
	int m_state;
};

// 管理者类  
class Caretaker {
public:	
     // 决定何时创建备忘录,但它不会修改备忘录对象,也不需要知道备忘录的内容
    void AddMemento(std::shared_ptr<Originator> originator) { m_memento.push_back(originator->createMemento()); }
    
    //获取备忘录
	Memento* GetMemento(int index)
    {
        if(index >= m_memento.size())
            return NULL;
        else
            return m_memento[index];
    }

    //撤销:决定何时恢复状态,但它不会修改备忘录对象,也不需要知道备忘录的内容
    void Undo(std::shared_ptr<Originator> originator) {
        if (!m_memento.empty()) {
            originator->RestoreFromMemento(m_memento.back());
            m_memento.pop_back();
        }
    }
 
private:
	std::vector<std::shared_ptr<Memento>> m_memento;
    //如果使用栈stack保存Memento,出入栈应该使用push、pop,栈顶元素用top
};

int main() {
    auto originator = std::make_shared<Originator>();
    Caretaker caretaker;

    // 保存初始化的状态
    caretaker.AddMemento(originator);

    // 修改保存第一次
    originator->SetState("First Sentence.");
    caretaker.AddMemento(originator);

    // 修改保存第二次
    originator->SetState("Second Sentence.");
    caretaker.AddMemento(originator);

    // 修改第三次,不保存
    originator->SetState("Third Sentence.");

    std::cout << "Current Text: " << originator->GetState() << std::endl;

    // 第一次撤销上次修改
    caretaker.Undo(originator);
    std::cout << "After Undo 1st : " << originator->GetState() << std::endl;

    // 第二次撤销上次修改
    caretaker.Undo(originator);
    std::cout << "After another Undo 2nd : " << originator->GetState() << std::endl;

    // 第三次撤销上次修改(已经空了)
    caretaker.Undo(originator);
    std::cout << "After one another Undo 3rd : " << originator->GetState() << std::endl;

    return 0;
}

二、备忘录模式的应用场景
C++ 备忘录模式的应用场景广泛,主要适用于以下情况:

(1)数据库事务管理: 在数据库连接的事务中,可能需要保存某个时刻的数据库连接状态,以便在出错或需要回滚时能够恢复到之前的状态。

(2)游戏状态保存与加载: 在游戏中,可能需要保存玩家在某个时刻的游戏状态,以便玩家下次可以继续之前的游戏进度。备忘录模式可以方便地实现游戏状态的保存和加载。

(3)浏览器后退功能: 浏览器在浏览网页时,用户可能希望回到之前访问过的页面。备忘录模式可以保存用户浏览的历史记录,实现后退功能。

(4)虚拟机快照与恢复: 虚拟机软件如 VMware 通过快照功能来保存系统的当前状态,以便日后可以恢复到该状态。使用备忘录模式,可以方便地实现这一功能。

(5)版本控制: 在软件开发中,版本控制工具(如 git)通过保存代码的历史状态来允许开发者回滚到之前的版本。备忘录模式可以用于实现这种版本控制机制。

(6)撤销操作: 例如,在文本编辑器、图形编辑工具或游戏等应用中,用户可能希望撤销之前的操作。通过备忘录模式,可以保存用户每一步操作的状态,从而方便地实现撤销功能。

原文链接:https://blog.csdn.net/h8062651/article/details/136678875

三、代码示例

3.1 备忘录模式应用于数据库事务管理
在数据库事务管理中,备忘录模式可以用于保存事务执行过程中的中间状态,以便在需要时回滚到这些状态:

#include <iostream>  
#include <memory>  
#include <string>  
#include <vector>  

// 备忘录类,保存数据库事务的某个状态  
class Memento {
public:
    explicit Memento(const std::string& state) : m_state(state) {}
    std::string getState() const { return m_state; }

private:
    std::string m_state; // 假设状态是一个字符串,实际应用中可能是复杂的数据结构  
};

// 使用智能指针管理备忘录  
using MementoPtr = std::unique_ptr<Memento>;

// 发起人类,代表数据库事务  
class Originator {
public:
    // 创建备忘录,保存当前状态  
    MementoPtr createMemento() {
        return MementoPtr(new Memento(m_currentState));
    }

    // 从备忘录恢复状态  
    void restoreFromMemento(const Memento& memento) {
        m_currentState = memento.getState();
        std::cout << "Restored to state: " << m_currentState << std::endl;
    }

    // 执行数据库操作,改变状态  
    void setState(const std::string& state) {
        m_currentState = state;
        std::cout << "Current state: " << m_currentState << std::endl;
    }

    // 获取当前状态(仅用于演示)  
    std::string getState() const {
        return m_currentState;
    }

private:
    std::string m_currentState; // 数据库事务的当前状态  
};

// 管理者类,管理备忘录对象  
class Caretaker {
public:
    // 存储备忘录  
    void setMemento(MementoPtr memento) {
        m_memento = std::move(memento);
    }

    // 获取备忘录  
    MementoPtr getMemento() {
        return std::move(m_memento);
    }

    // 检查是否有备忘录  
    bool hasMemento() const {
        return static_cast<bool>(m_memento);
    }

private:
    MementoPtr m_memento; // 使用智能指针管理备忘录对象的生命周期  
};

int main() 
{
    Originator transaction; // 代表一个数据库事务  
    Caretaker caretaker;    // 负责管理备忘录  

    // 执行一些数据库操作,并保存状态到备忘录  
    transaction.setState("State 1");
    caretaker.setMemento(transaction.createMemento());

    transaction.setState("State 2");
    caretaker.setMemento(transaction.createMemento());

    // 模拟事务出错,回滚到之前的状态  
    transaction.setState("Error State");
    std::cout << "Rolling back due to error..." << std::endl;

    if (caretaker.hasMemento()) {
        transaction.restoreFromMemento(*caretaker.getMemento());
    }

    return 0;
}

上面代码的输出为:

Current state: State 1
Current state: State 2
Current state: Error State
Rolling back due to error...
Restored to state: State 2

在这个样例中,Memento 类保存了数据库事务的某个状态,Originator 类代表数据库事务,它可以创建备忘录来保存当前状态,也可以从备忘录中恢复状态。Caretaker 类负责管理这些备忘录对象的生命周期,使用 std::unique_ptr 来确保备忘录对象在不再需要时能够被自动释放。

在 main 函数中,模拟了一个数据库事务的执行过程,通过调用 setState 方法来改变事务的状态,并使用 Caretaker 来保存这些状态到备忘录中。当事务出错时,就可以从最后一个保存的备忘录中恢复状态,实现回滚操作。


3.2 场景描述:玩游戏通关后保存进度,后面还可以加载重玩。

//Memento.h
/****************************************************/
#pragma once
#include <iostream>
#include <unordered_map>
#include <vector>
#include <list>
#include <string>
 
using namespace std;
 
// 备忘录类-游戏进度
class Memento 
{
public:
    // 构造函数
    Memento(string state) : m_state(state) {}
 
    // 获取状态
    std::string getState() const{ 
        return m_state;
    }
 
private:
    std::string m_state;
};
 
// 发起类-游戏
class Game 
{
public:
    // 设置状态
    void setState(string state) { 
        m_state = state;
    }
 
    // 获取状态
    string getState() { 
        return m_state;
    }
 
    // 保存状态至备忘录
    Memento saveStateToMemento() { 
        return Memento(m_state); 
    }
 
    // 从备忘录获取状态
    void getStateFromMemento(const Memento& memento) { 
        m_state = memento.getState(); 
    }
 
private:
    std::string m_state;
};
 
// 备忘录管理类-进度管理
class CareTaker 
{
public:
    // 添加备忘录
    void addMemento(const Memento& memento) { 
        m_mementos.push_back(memento);
    }
 
    // 获取备忘录
    Memento getMemento(int index) { 
        return m_mementos[index];
    }
 
private:
    std::vector<Memento> m_mementos;
};

//main.cpp
/****************************************************/
#include <iostream>
#include <string>
#include "Memento.h"
 
using namespace std;
 
int main() 
{
    Game game;
    CareTaker careTaker;
    // 通关
    game.setState("进度:第一关通过");
    game.setState("进度:第二关通过");
    // 保存进度,进度被管理系统管理
    careTaker.addMemento(game.saveStateToMemento());
    // 继续通关
    game.setState("进度:第三关通过");
    // 保存进度,进度被管理系统管理
    careTaker.addMemento(game.saveStateToMemento());
    // 继续通关
    game.setState("进度:第四关通过");
    // 当前进度
    cout << "当前" << game.getState() << endl;
    // 获取首个进度
    game.getStateFromMemento(careTaker.getMemento(0));
    cout << "1)" << game.getState() << endl;
    // 获取第二个进度
    game.getStateFromMemento(careTaker.getMemento(1));
    cout << "2)" << game.getState() << endl;
 
    return 0;
}

程序结果如下。

       游戏通过第二关后保存进度,这是进度一;通过第三关后又一次保存进度,这是进度二;通过第四关后想要重玩进度一,就加载了进度一,此时进度变为刚通过第二关的状态;加载进度二,也是一样。

四、备忘录模式的优点与缺点
C++ 备忘录模式的优点主要包括:

(1)封装性: 备忘录模式可以很好地支持对象的封装性,因为状态的保存和恢复都是在对象内部完成的,外部代码无法直接访问或修改对象的内部状态。

(2)易于管理历史状态: 通过保存对象的多个备忘录,可以轻松地实现状态的回滚或切换到之前的某个状态,这对于需要保存用户操作历史或实现撤销/重做功能的系统来说非常有用。

(3)易于实现状态持久化: 备忘录可以被序列化并保存到磁盘上,实现状态的持久化,以便在程序重启后恢复之前的状态。

(4)降低耦合度: 通过引入备忘录作为中介,可以将对象状态的保存和恢复逻辑与对象本身解耦,降低了代码之间的耦合度,提高了系统的可维护性。

然而,C++ 备忘录模式也存在一些缺点:

(1)资源消耗: 每个备忘录对象都是对象状态的一个完整拷贝,如果对象的状态很大或者很复杂,那么保存大量的备忘录可能会导致大量的内存消耗。

(2)可能泄露实现细节: 尽管备忘录模式可以保护对象的封装性,但如果备忘录的实现不小心暴露了过多的内部状态细节,那么这些细节可能会被不当地使用,从而破坏对象的封装性。

(3)管理复杂性: 如果系统中有大量的对象需要保存状态,那么管理和维护这些备忘录可能会变得复杂。需要确保备忘录的创建、保存、加载和销毁等操作都得到正确的处理。

(4)可能引发安全问题: 在某些情况下,如果备忘录中包含了敏感信息(如密码、密钥等),那么这些信息的保存和加载可能会引发安全问题。需要采取适当的安全措施来保护这些信息。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部