一, 什么是备忘录模式
备忘录这个词汇大家应该都不陌生, 我就经常使用备忘录来记录一些比较重要的或者容易遗忘的信息, 与之相关的最常见的应用有许多, 比如游戏存档, 我们玩游戏的时候肯定有存档功能, 旨在下一次登录游戏时可以从上次退出的地方继续游戏, 或者对复活点进行存档, 如果挂掉了则可以读取复活点的存档信息重新开始. 与之相类似的就是数据库的事务回滚, 或者重做日志 redo log 等.
备忘录模式 (Memento), 在不破坏封装性的前提下, 捕获一个对象的内部状态, 并在该对象之外保存着这个状态. 这样以后就可将该对象恢复到原先保存的状态. UML 结构图如下:
其中, Originator 是发起人, 负责创建一个备忘录 Memento, 用以记录当前时刻它的内部状态, 并可使用备忘录恢复内部状态; Memento 是备忘录, 负责存储 Originator 对象的内部状态, 并可防止 Originator 以外的其他对象访问备忘录 Memento;Caretaker 是管理者, 负责保存好备忘录的 Memento, 不能对备忘录的内容进行操作或检查.
1. 发起人角色
记录当前时刻的内部状态, 并负责创建和恢复备忘录数据, 允许访问返回到先前状态所需的所有数据.
- public class Originator {
- private String state;
- public String getState() {
- return state;
- }
- public void setState(String state) {
- this.state = state;
- }
- public Memento createMento() {
- return (new Memento(state));
- }
- public void setMemento(Memento memento) {
- state = memento.getState();
- }
- public void show() {
- System.out.println("state =" + state);
- }
- }
2. 备忘录角色
负责存储 Originator 发起人对象的内部状态, 在需要的时候提供发起人需要的内部状态.
- public class Memento {
- private String state;
- public Memento(String state) {
- this.state = state;
- }
- public String getState() {
- return state;
- }
- }
3. 备忘录管理员角色
对备忘录进行管理, 保存和提供备忘录, 只能将备忘录传递给其他角色.
- public class Caretaker {
- private Memento memento;
- public Memento getMemento() {
- return memento;
- }
- public void setMemento(Memento memento) {
- this.memento = memento;
- }
- }
4. Client 客户端
下面编写一小段代码测试一下, 即先将状态置为 On, 保存后再将状态置为 Off, 然后通过备忘录管理员角色恢复初始状态.
- public class Client {
- public static void main(String[] args) {
- Originator originator = new Originator();
- originator.setState("On"); //Originator 初始状态
- originator.show();
- Caretaker caretaker = new Caretaker();
- caretaker.setMemento(originator.createMento());
- originator.setState("Off"); //Originator 状态变为 Off
- originator.show();
- originator.setMemento(caretaker.getMemento()); // 回复初始状态
- originator.show();
- }
- }
运行结果如下:
二, 备忘录模式的应用
1. 何时使用
需要记录一个对象的内部状态时, 为了允许用户取消不确定或者错误的操作, 能够恢复到原先的状态
2. 方法
通过一个备忘录类专门存储对象状态
3. 优点
给用户提供了一种可以恢复状态的机制, 可以使用能够比较方便地回到某个历史的状态
实现了信息的封装, 使得用户不需要关心状态的保存细节
4. 缺点
消耗资源
5. 使用场景
需要保存和恢复数据的相关场景
提供一个可回滚的操作, 如 ctrl+z, 浏览器回退按钮, Backspace 键等
需要监控的副本场景
6. 应用实例
游戏存档
ctrl+z 键, 浏览器回退键等 (撤销 / 还原)
棋盘类游戏的悔棋
数据库事务的回滚
7. 注意事项
为了符合迪米特法则, 需要有一个管理备忘录的类
不要在频繁建立备份的场景中使用备忘录模式. 为了节约内存, 可使用原型模式 + 备忘录模式
三, 备忘录模式的实现
下面以游戏存档为例, 看一下如何用备忘录模式实现. UML 图如下:
1. 游戏角色
简单记录了游戏角色的生命力, 攻击力, 防御力, 通过 saveState() 方法来保存当前状态, 通过 recoveryState() 方法来恢复角色状态.
- public class GameRole {
- private int vit; // 生命力
- private int atk; // 攻击力
- private int def; // 防御力
- public int getVit() {
- return vit;
- }
- public void setVit(int vit) {
- this.vit = vit;
- }
- public int getAtk() {
- return atk;
- }
- public void setAtk(int atk) {
- this.atk = atk;
- }
- public int getDef() {
- return def;
- }
- public void setDef(int def) {
- this.def = def;
- }
- // 状态显示
- public void stateDisplay() {
- System.out.println("角色当前状态:");
- System.out.println("体力:" + this.vit);
- System.out.println("攻击力:" + this.atk);
- System.out.println("防御力:" + this.def);
- System.out.println("-----------------");
- }
- // 获得初始状态
- public void getInitState() {
- this.vit = 100;
- this.atk = 100;
- this.def = 100;
- }
- // 战斗后
- public void fight() {
- this.vit = 0;
- this.atk = 0;
- this.def = 0;
- }
- // 保存角色状态
- public RoleStateMemento saveState() {
- return (new RoleStateMemento(vit, atk, def));
- }
- // 恢复角色状态
- public void recoveryState(RoleStateMemento memento) {
- this.vit = memento.getVit();
- this.atk = memento.getAtk();
- this.def = memento.getDef();
- }
- }
2. 角色状态存储箱
备忘录类, 用于存储角色状态.
- public class RoleStateMemento {
- private int vit; // 生命力
- private int atk; // 攻击力
- private int def; // 防御力
- public RoleStateMemento(int vit, int atk, int def) {
- this.vit = vit;
- this.atk = atk;
- this.def = def;
- }
- public int getVit() {
- return vit;
- }
- public void setVit(int vit) {
- this.vit = vit;
- }
- public int getAtk() {
- return atk;
- }
- public void setAtk(int atk) {
- this.atk = atk;
- }
- public int getDef() {
- return def;
- }
- public void setDef(int def) {
- this.def = def;
- }
- }
3. 角色状态管理者
备忘录管理者.
- public class RoleStateCaretaker {
- private RoleStateMemento memento;
- public RoleStateMemento getMemento() {
- return memento;
- }
- public void setMemento(RoleStateMemento memento) {
- this.memento = memento;
- }
- }
4. Client 客户端
下面编写一个简单的程序测试一下, 编写逻辑大致为打 boss 前存档, 打 boss 失败了, 读档.
- public class Client {
- public static void main(String[] args) {
- // 打 boss 前
- GameRole gameRole = new GameRole();
- gameRole.getInitState();
- gameRole.stateDisplay();
- // 保存进度
- RoleStateCaretaker caretaker = new RoleStateCaretaker();
- caretaker.setMemento(gameRole.saveState());
- // 打 boss 失败
- gameRole.fight();
- gameRole.stateDisplay();
- // 恢复状态
- gameRole.recoveryState(caretaker.getMemento());
- gameRole.stateDisplay();
- }
- }
运行结果如下:
源码地址: https://gitee.com/adamjiangwh/GoF
来源: https://www.cnblogs.com/adamjwh/p/11018268.html