简说设计模式——备忘录模式
一、什么是备忘录模式
备忘录这个词汇大家应该都不陌生,我就经常使用备忘录来记录一些比较重要的或者容易遗忘的信息,与之相关的最常见的应用有许多,比如游戏存档,我们玩游戏的时候肯定有存档功能,旨在下一次登录游戏时可以从上次退出的地方继续游戏,或者对复活点进行存档,如果挂掉了则可以读取复活点的存档信息重新开始。与之相类似的就是数据库的事务回滚,或者重做日志redo log等。
备忘录模式(memento),在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存着这个状态。这样以后就可将该对象恢复到原先保存的状态。uml结构图如下:
其中,originator是发起人,负责创建一个备忘录memento,用以记录当前时刻它的内部状态,并可使用备忘录恢复内部状态;memento是备忘录,负责存储originator对象的内部状态,并可防止originator以外的其他对象访问备忘录memento;caretaker是管理者,负责保存好备忘录的memento,不能对备忘录的内容进行操作或检查。
1. 发起人角色
记录当前时刻的内部状态,并负责创建和恢复备忘录数据,允许访问返回到先前状态所需的所有数据。
1 public class originator { 2 3 private string state; 4 5 public string getstate() { 6 return state; 7 } 8 9 public void setstate(string state) { 10 this.state = state; 11 } 12 13 public memento createmento() { 14 return (new memento(state)); 15 } 16 17 public void setmemento(memento memento) { 18 state = memento.getstate(); 19 } 20 21 public void show() { 22 system.out.println("state = " + state); 23 } 24 25 }
2. 备忘录角色
负责存储originator发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。
1 public class memento { 2 3 private string state; 4 5 public memento(string state) { 6 this.state = state; 7 } 8 9 public string getstate() { 10 return state; 11 } 12 13 }
3. 备忘录管理员角色
对备忘录进行管理、保存和提供备忘录,只能将备忘录传递给其他角色。
1 public class caretaker { 2 3 private memento memento; 4 5 public memento getmemento() { 6 return memento; 7 } 8 9 public void setmemento(memento memento) { 10 this.memento = memento; 11 } 12 13 }
4. client客户端
下面编写一小段代码测试一下,即先将状态置为on,保存后再将状态置为off,然后通过备忘录管理员角色恢复初始状态。
1 public class client { 2 3 public static void main(string[] args) { 4 originator originator = new originator(); 5 originator.setstate("on"); //originator初始状态 6 originator.show(); 7 8 caretaker caretaker = new caretaker(); 9 caretaker.setmemento(originator.createmento()); 10 11 originator.setstate("off"); //originator状态变为off 12 originator.show(); 13 14 originator.setmemento(caretaker.getmemento()); //回复初始状态 15 originator.show(); 16 } 17 18 }
运行结果如下:
二、备忘录模式的应用
1. 何时使用
- 需要记录一个对象的内部状态时,为了允许用户取消不确定或者错误的操作,能够恢复到原先的状态
2. 方法
- 通过一个备忘录类专门存储对象状态
3. 优点
- 给用户提供了一种可以恢复状态的机制,可以使用能够比较方便地回到某个历史的状态
- 实现了信息的封装,使得用户不需要关心状态的保存细节
4. 缺点
- 消耗资源
5. 使用场景
- 需要保存和恢复数据的相关场景
- 提供一个可回滚的操作,如ctrl+z、浏览器回退按钮、backspace键等
- 需要监控的副本场景
6. 应用实例
- 游戏存档
- ctrl+z键、浏览器回退键等(撤销/还原)
- 棋盘类游戏的悔棋
- 数据库事务的回滚
7. 注意事项
- 为了符合迪米特法则,需要有一个管理备忘录的类
- 不要在频繁建立备份的场景中使用备忘录模式。为了节约内存,可使用原型模式+备忘录模式
三、备忘录模式的实现
下面以游戏存档为例,看一下如何用备忘录模式实现。uml图如下:
1. 游戏角色
简单记录了游戏角色的生命力、攻击力、防御力,通过savestate()方法来保存当前状态,通过recoverystate()方法来恢复角色状态。
1 public class gamerole { 2 3 private int vit; //生命力 4 private int atk; //攻击力 5 private int def; //防御力 6 7 public int getvit() { 8 return vit; 9 } 10 public void setvit(int vit) { 11 this.vit = vit; 12 } 13 public int getatk() { 14 return atk; 15 } 16 public void setatk(int atk) { 17 this.atk = atk; 18 } 19 public int getdef() { 20 return def; 21 } 22 public void setdef(int def) { 23 this.def = def; 24 } 25 26 //状态显示 27 public void statedisplay() { 28 system.out.println("角色当前状态:"); 29 system.out.println("体力:" + this.vit); 30 system.out.println("攻击力:" + this.atk); 31 system.out.println("防御力: " + this.def); 32 system.out.println("-----------------"); 33 } 34 35 //获得初始状态 36 public void getinitstate() { 37 this.vit = 100; 38 this.atk = 100; 39 this.def = 100; 40 } 41 42 //战斗后 43 public void fight() { 44 this.vit = 0; 45 this.atk = 0; 46 this.def = 0; 47 } 48 49 //保存角色状态 50 public rolestatememento savestate() { 51 return (new rolestatememento(vit, atk, def)); 52 } 53 54 //恢复角色状态 55 public void recoverystate(rolestatememento memento) { 56 this.vit = memento.getvit(); 57 this.atk = memento.getatk(); 58 this.def = memento.getdef(); 59 } 60 61 }
2. 角色状态存储箱
备忘录类,用于存储角色状态。
1 public class rolestatememento { 2 3 private int vit; //生命力 4 private int atk; //攻击力 5 private int def; //防御力 6 7 public rolestatememento(int vit, int atk, int def) { 8 this.vit = vit; 9 this.atk = atk; 10 this.def = def; 11 } 12 13 public int getvit() { 14 return vit; 15 } 16 17 public void setvit(int vit) { 18 this.vit = vit; 19 } 20 21 public int getatk() { 22 return atk; 23 } 24 25 public void setatk(int atk) { 26 this.atk = atk; 27 } 28 29 public int getdef() { 30 return def; 31 } 32 33 public void setdef(int def) { 34 this.def = def; 35 } 36 37 }
3. 角色状态管理者
备忘录管理者。
1 public class rolestatecaretaker { 2 3 private rolestatememento memento; 4 5 public rolestatememento getmemento() { 6 return memento; 7 } 8 9 public void setmemento(rolestatememento memento) { 10 this.memento = memento; 11 } 12 13 }
4. client客户端
下面编写一个简单的程序测试一下,编写逻辑大致为打boss前存档,打boss失败了,读档。
1 public class client { 2 3 public static void main(string[] args) { 4 //打boss前 5 gamerole gamerole = new gamerole(); 6 gamerole.getinitstate(); 7 gamerole.statedisplay(); 8 9 //保存进度 10 rolestatecaretaker caretaker = new rolestatecaretaker(); 11 caretaker.setmemento(gamerole.savestate()); 12 13 //打boss失败 14 gamerole.fight(); 15 gamerole.statedisplay(); 16 17 //恢复状态 18 gamerole.recoverystate(caretaker.getmemento()); 19 gamerole.statedisplay(); 20 } 21 22 }
运行结果如下: