• 突破编程_C++_设计模式(备忘录模式)


    1 备忘录模式的基本概念

    C++ 备忘录模式(Memento Pattern) 是一种行为设计模式,它用于在不破坏封装的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

    备忘录模式通常涉及三个主要角色:

    (1)发起人(Originator)角色: 记录当前时刻的内部状态,并可使用备忘录恢复内部状态。发起人根据需要将内部状态信息封装在备忘录对象中,并使用备忘录对象存储其内部状态快照。

    (2)备忘录(Memento)角色: 负责存储发起人对象的内部状态,并可防止发起人以外的其他对象访问备忘录。备忘录有两个接口:一个是供发起人内部状态访问,它可以提供发起人所需要的内部状态;另一个是供管理者保存备忘录状态。

    (3)管理者(Caretaker)角色: 负责保存好备忘录,不能对备忘录的内容进行操作或检查。

    使用 C++ 实现备忘录模式时,通常会涉及到类的设计和对象的交互。发起人类需要能够创建备忘录对象并保存其内部状态,同时也需要能够从备忘录对象恢复其内部状态。备忘录类则需要负责存储和提供发起人的内部状态。管理者类则负责存储和管理备忘录对象。

    这种模式在需要保存和恢复对象状态的情况下非常有用,例如在撤销操作、保存游戏进度或保存用户设置等场景中。通过使用备忘录模式,可以在不暴露对象内部状态的情况下,实现对对象状态的保存和恢复,从而保持对象的封装性。

    2 备忘录模式的实现步骤

    备忘录模式的实现步骤如下:

    (1)定义发起人(Originator)类:

    • 发起人需要有一个用于存储其内部状态的备忘录(Memento)类型的成员变量。
    • 提供创建备忘录的方法,该方法应复制发起人的当前状态到新的备忘录对象中。
    • 提供从备忘录恢复状态的方法,该方法将使用备忘录中保存的状态来恢复发起人的内部状态。

    (2)定义备忘录(Memento)类:

    • 备忘录类用于存储发起人的内部状态。
    • 备忘录类应该包含发起人内部状态所需的所有字段,并提供相应的访问和设置方法。
    • 通常,备忘录类的构造函数会接收发起人的状态作为参数,并保存这些状态。

    (3)定义管理者(Caretaker)类:

    • 管理者类负责管理备忘录对象。
    • 它通常包含一个或多个备忘录对象的成员变量,并提供方法来存储和获取这些备忘录对象。
    • 管理者不应该直接访问备忘录的内容,只是负责存储和传递备忘录对象。

    (4)使用备忘录模式:

    • 在需要保存状态的时候,发起人会创建一个新的备忘录对象,并调用其方法来保存当前状态。
    • 发起人然后将这个备忘录对象传递给管理者,管理者负责保存这个备忘录对象。
    • 在需要恢复状态的时候,发起人会从管理者那里获取之前保存的备忘录对象,并调用其方法来恢复状态。

    如下为样例代码:

    #include   
    #include   
    
    // 备忘录类  
    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 setMemento(std::unique_ptr<Memento> memento) {
    		m_memento = std::move(memento);
    	}
    
    	// 获取备忘录  
    	std::unique_ptr<Memento> getMemento() {
    		return std::move(m_memento);
    	}
    
    private:
    	std::unique_ptr<Memento> m_memento;
    };
    
    int main() 
    {
    	// 创建发起人并设置初始状态  
    	Originator originator(10);
    	std::cout << "Current state: " << originator.getState() << std::endl;
    
    	// 创建并保存备忘录  
    	Caretaker caretaker;
    	caretaker.setMemento(originator.createMemento());
    
    	// 修改发起人状态  
    	originator.setState(20);
    	std::cout << "State after modification: " << originator.getState() << std::endl;
    
    	// 从备忘录恢复状态  
    	if (auto memento = caretaker.getMemento()) {
    		originator.restoreFromMemento(*memento);
    		std::cout << "State after restoration: " << originator.getState() << std::endl;
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    上面代码的输出为:

    Current state: 10
    State after modification: 20
    State after restoration: 10
    
    • 1
    • 2
    • 3

    上面代码中定义了一个 Memento 类来存储发起人的状态,Originator 类负责创建备忘录和从备忘录恢复状态,Caretaker 类负责管理备忘录对象。

    代码中使用了 std::unique_ptr 智能指针来管理备忘录对象的生命周期,确保在不需要时能够自动释放内存。在 Caretaker 类中,使用 std::move 来转移备忘录对象的所有权,这样在恢复状态时,Caretaker 将不再拥有该备忘录对象。

    在 main 函数中,展示了如何使用这些类来保存和恢复发起人的状态。首先,创建一个发起人并设置其初始状态。然后,创建一个备忘录并将其保存在管理者中。接下来,修改发起人的状态,并从管理者那里获取之前保存的备忘录来恢复状态。

    3 备忘录模式的应用场景

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

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

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

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

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

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

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

    3.1 备忘录模式应用于数据库事务管理

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

    #include   
    #include   
    #include   
    #include   
    
    // 备忘录类,保存数据库事务的某个状态  
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    上面代码的输出为:

    Current state: State 1
    Current state: State 2
    Current state: Error State
    Rolling back due to error...
    Restored to state: State 2
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

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

    3.2 备忘录模式应用游戏状态保存与加载

    在游戏开发中,使用备忘录模式来保存和加载游戏状态是一种非常实用的做法。通过保存游戏状态的快照,玩家可以在退出游戏后继续之前的进度,或者在需要时回滚到之前的某个状态。下面是一个简单的样例,展示了如何使用备忘录模式来实现游戏状态的保存与加载:

    首先,定义游戏状态类和备忘录类:

    #include   
    #include   
    #include   
    
    // 游戏状态类  
    class GameState {
    public:
    	GameState(int level, int score) : m_level(level), m_score(score) {}
    
    	int getLevel() const { return m_level; }
    	int getScore() const { return m_score; }
    
    	void setLevel(int level) { m_level = level; }
    	void setScore(int score) { m_score = score; }
    
    	// 创建游戏状态的备忘录  
    	std::unique_ptr<GameState> createMemento() const {
    		return std::make_unique<GameState>(m_level, m_score);
    	}
    
    	// 从备忘录恢复游戏状态  
    	void restoreFromMemento(const GameState& memento) {
    		m_level = memento.getLevel();
    		m_score = memento.getScore();
    	}
    
    private:
    	int m_level; // 游戏级别  
    	int m_score; // 游戏得分  
    };
    
    // 游戏状态的管理者类  
    class Caretaker {
    public:
    	// 保存游戏状态的备忘录  
    	void saveMemento(std::unique_ptr<GameState> memento) {
    		if (m_memento) {
    			std::cout << "Previous game state has been discarded." << std::endl;
    		}
    		m_memento = std::move(memento);
    	}
    
    	// 获取游戏状态的备忘录  
    	std::unique_ptr<GameState> getMemento() {
    		return std::move(m_memento);
    	}
    
    	// 检查是否有保存的备忘录  
    	bool hasMemento() const {
    		return static_cast<bool>(m_memento);
    	}
    
    private:
    	std::unique_ptr<GameState> m_memento; // 使用智能指针管理备忘录  
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    然后,在游戏逻辑中使用这些类:

    class Game {
    public:
    	Game() : m_state(1, 0), m_caretaker() {}
    
    	// 更新游戏状态  
    	void updateState(int levelChange, int scoreChange) {
    		m_state.setLevel(m_state.getLevel() + levelChange);
    		m_state.setScore(m_state.getScore() + scoreChange);
    		std::cout << "Current game state: Level " << m_state.getLevel()
    			<< ", Score " << m_state.getScore() << std::endl;
    	}
    
    	// 保存游戏状态  
    	void saveGameState() {
    		m_caretaker.saveMemento(m_state.createMemento());
    		std::cout << "Game state saved." << std::endl;
    	}
    
    	// 加载游戏状态  
    	void loadGameState() {
    		if (m_caretaker.hasMemento()) {
    			auto memento = m_caretaker.getMemento();
    			m_state.restoreFromMemento(*memento);
    			std::cout << "Game state loaded: Level " << m_state.getLevel()
    				<< ", Score " << m_state.getScore() << std::endl;
    		}
    		else {
    			std::cout << "No game state to load." << std::endl;
    		}
    	}
    
    private:
    	GameState m_state; // 当前游戏状态  
    	Caretaker m_caretaker; // 游戏状态的管理者  
    };
    
    int main() 
    {
    	Game game;
    
    	// 模拟游戏进行,更新状态并保存  
    	game.updateState(1, 100); // 假设玩家通过了第一关,得分100  
    	game.saveGameState(); // 保存当前状态  
    
    	// 继续游戏...  
    	game.updateState(1, 200); // 假设玩家通过了第二关,得分再增加200  
    
    	// 加载之前保存的游戏状态  
    	game.loadGameState(); // 加载之前保存的状态  
    
    	// 继续游戏...  
    	// ...  
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    上面代码的输出为:

    Current game state: Level 2, Score 100
    Game state saved.
    Current game state: Level 3, Score 300
    Game state loaded: Level 2, Score 100
    
    • 1
    • 2
    • 3
    • 4

    在这个样例中,GameState 类表示游戏的状态,包含游戏的级别和得分。它提供了创建备忘录和从备忘录恢复状态的方法。Caretaker 类负责管理这些备忘录,并使用 std::unique_ptr 智能指针来确保备忘录对象的正确释放。

    Game 类封装了游戏逻辑,包括更新游戏状态、保存和加载游戏状态。当玩家完成一个阶段或关卡时,可以调用 saveGameState() 方法来保存当前的游戏状态。如果玩家需要回滚到之前的状态,或者重新开始游戏时,可以调用 loadGameState() 方法来加载之前保存的状态。

    在 main() 函数中,模拟了游戏过程,包括更新状态、保存状态和加载状态。当然,在真实的游戏开发中,这些操作通常会与用户交互(如点击按钮、触发事件等)以及游戏逻辑(如关卡完成、游戏失败等)紧密结合。

    4 备忘录模式的优点与缺点

    C++ 备忘录模式的优点主要包括:

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

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

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

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

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

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

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

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

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

  • 相关阅读:
    web大作业 静态网页 HTML+CSS+JavaScript橙色的时尚服装购物商城
    【Node.js项目】大事件项目:后台架构图(含具体技术栈)、典型代码
    FPGA高端项目:图像采集+GTP+UDP架构,高速接口以太网视频传输,提供2套工程源码加QT上位机源码和技术支持
    git 踩坑记录
    远程拷贝Windows上的文件到Linux指定的文件夹
    记录学习--分页时会查两遍数据库吗
    JavaScript FileReader API 处理文件示例
    如何实现接口幂等性
    【深度学习 | Transformer】释放注意力的力量:探索深度学习中的 变形金刚,一文带你读通各个模块 —— Positional Encoding(一)
    Rabin-Karp字符串哈希算法
  • 原文地址:https://blog.csdn.net/h8062651/article/details/136678875