[关闭]
@w1992wishes 2018-03-13T14:30:24.000000Z 字数 7180 阅读 940

设计模式--备忘录模式

设计模式 行为型模式


目录

本文的结构如下:

一、引言

晚上躺在被窝里,突兀的,脑海主动把往事翻出来倒带重播,那些旧时光的画面很清晰,就像投影片一样投在雪白的墙面,回忆着,便思绪万千,下意识发出一声轻叹,哎,看样子要失眠了。

有很多遗憾,有很多舍不得,只是怎么也回不去啊,人生又不是一部重生小说,夏洛也只出现在荧幕中。

回不去的从前

所以说,好好敲代码吧,代码敲错了,可以ctrl+z,代码提交错了,可以git reset。

在软件开发中,很多时候需要记录一个对象的内部状态,目的就是为了允许用户取消不确定或者错误的操作,能够恢复到原先的状态,使得有“后悔药”可吃。备忘录模式是一种给软件提供后悔药的机制,通过它可以使系统恢复到某一特定的历史状态。

二、什么是备忘录模式

备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤。

备忘录模式定义如下:

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

三、模式的结构

备忘录模式UML类图如下:

UML类图

备忘录模式主要包含入下几个角色:

在备忘录模式中,最重要的就是备忘录Memento了。由于在备忘录中存储的是原发器的中间状态,因此需要防止原发器以外的其他对象访问备忘录,特别是不允许其他对象来修改备忘录。

为了不破坏备忘录的封装性,我们需要对备忘录的访问做些控制:

所以就备忘录模式而言理想的情况就是只允许生成该备忘录的那个原发器访问备忘录的内部状态。

四、典型代码

在真实业务中,原发器类是一个具体的业务类,它包含一些用于存储成员数据的属性,原发器典型代码如下:

  1. public class Originator {
  2. private String state;
  3. public void restoreMemento(Memento m){
  4. this.state = m.getState();
  5. }
  6. public Memento createMemento(){
  7. return new Memento(state);
  8. }
  9. public String getState() {
  10. return state;
  11. }
  12. public void setState(String state) {
  13. this.state = state;
  14. }
  15. public String toString(){
  16. return "originator---" + state;
  17. }
  18. }

对于备忘录类Memento而言,它通常提供了与原发器相对应的属性(可以是全部,也可以是部分)用于存储原发器的状态。

在设计备忘录类时需要考虑其封装性,除了Originator类,不允许其他类来调用备忘录类Memento的构造函数与相关方法,如果不考虑封装性,允许其他类调用setState()等方法,将导致在备忘录中保存的历史状态发生改变,通过撤销操作所恢复的状态就不再是真实的历史状态,备忘录模式也就失去了本身的意义。

在使用Java语言实现备忘录模式时,一般通过将Memento类与Originator类定义在同一个包(package)中来实现封装,在Java语言中可使用默认访问标识符来定义Memento类,即保证其包内可见。只有Originator类可以对Memento进行访问,而限制了其他类对Memento的访问。在Memento中保存了Originator的state值,如果Originator中的state值改变之后需撤销,可以通过调用它的restoreMemento()方法进行恢复。

典型代码如下:

  1. class Memento {
  2. private String state;
  3. public Memento(String state) {
  4. this.state = state;
  5. }
  6. public String getState() {
  7. return state;
  8. }
  9. public void setState(String state) {
  10. this.state = state;
  11. }
  12. }

负责人类Caretaker用于保存备忘录对象,并提供getMemento()方法用于向客户端返回一个备忘录对象,原发器通过使用这个备忘录对象可以回到某个历史状态。负责人典型代码如下:

  1. public class Caretaker {
  2. private Memento memento;
  3. public Memento getMemento() {
  4. return memento;
  5. }
  6. public void setMemento(Memento memento) {
  7. this.memento = memento;
  8. }
  9. }

类关系

简单测试一下:

  1. public class MementoDemo {
  2. public static void main(String[] args) {
  3. Originator originator = new Originator();
  4. originator.setState("state1");
  5. System.out.println(originator);
  6. Caretaker caretaker = new Caretaker();
  7. caretaker.setMemento(originator.createMemento());
  8. originator.setState("state2");
  9. System.out.println(originator);
  10. originator.restoreMemento(caretaker.getMemento());
  11. System.out.println(originator);
  12. }
  13. }

五、代码示例

刚上大学那会迷上篮球,玩游戏也都和篮球相关,大一暑假,便安装了2K 11,自建了一个大中锋,然后修改器身高调到最高,力量调到最大,速度调到最快,投篮调到最好,为得当然是刷数据了(捂脸)。记得有一场比赛,辛苦打了40分钟,数据超好,比分落后2分,还剩最后1s,有三分绝杀的机会,这个情况下当然是先存档,绝杀不中可以回档,总会投中的。

这里就以此为例。

5.1、第一版

  1. public class Game {
  2. private int ourScore;//我方分数
  3. private int oppositeScore;//对方分数
  4. private boolean end;
  5. public Game(int ourScore, int oppositeScore) {
  6. this.ourScore = ourScore;
  7. this.oppositeScore = oppositeScore;
  8. }
  9. /**
  10. * 我方投篮
  11. * @param goal
  12. * @param point
  13. */
  14. public void shoot(boolean goal, int point){
  15. if (end){
  16. System.out.println("比赛已经结束,接收现实少年");
  17. }else {
  18. if (goal){
  19. this.ourScore += point;
  20. }
  21. }
  22. }
  23. /**
  24. * 对方投篮
  25. * @param goal
  26. * @param point
  27. */
  28. public void autoShoot(boolean goal, int point){
  29. if (end){
  30. System.out.println("比赛已经结束,接收现实少年");
  31. }else {
  32. if (goal){
  33. this.oppositeScore += point;
  34. }
  35. }
  36. }
  37. /**
  38. * 回档
  39. * @param record
  40. */
  41. public void restoreRecord(Record record){
  42. this.end = false;
  43. this.ourScore = record.getOurScore();
  44. this.oppositeScore = record.getOppositeScore();
  45. }
  46. /**
  47. * 存档
  48. * @return
  49. */
  50. public Record saveRecord(){
  51. return new Record(ourScore, oppositeScore);
  52. }
  53. public void showGame(){
  54. System.out.println("我方得分:" + ourScore + ",对方得分:" + oppositeScore);
  55. if (end){
  56. System.out.println((ourScore > oppositeScore ? ",我方获胜" : ourScore == oppositeScore ? ",打平进入加时" : ",对方获胜"));
  57. }
  58. }
  59. public int getOurScore() {
  60. return ourScore;
  61. }
  62. public int getOppositeScore() {
  63. return oppositeScore;
  64. }
  65. public void setOurScore(int ourScore) {
  66. this.ourScore = ourScore;
  67. }
  68. public void setOppositeScore(int oppositeScore) {
  69. this.oppositeScore = oppositeScore;
  70. }
  71. public boolean isEnd() {
  72. return end;
  73. }
  74. public void setEnd(boolean end) {
  75. this.end = end;
  76. }
  77. }

存档:

  1. class Record {
  2. private int ourScore;//我方分数
  3. private int oppositeScore;//对方分数
  4. public Record(int ourScore, int oppositeScore) {
  5. this.ourScore = ourScore;
  6. this.oppositeScore = oppositeScore;
  7. }
  8. public void setOurScore(int ourScore) {
  9. this.ourScore = ourScore;
  10. }
  11. public void setOppositeScore(int oppositeScore) {
  12. this.oppositeScore = oppositeScore;
  13. }
  14. public int getOurScore() {
  15. return ourScore;
  16. }
  17. public int getOppositeScore() {
  18. return oppositeScore;
  19. }
  20. }

GameCaketaker持有存档:

  1. public class GameCaretaker {
  2. private Record record;
  3. public Record getRecord() {
  4. return record;
  5. }
  6. public void setRecord(Record record) {
  7. this.record = record;
  8. }
  9. }

测试:

  1. public class MementoDemo {
  2. public static void main(String[] args) {
  3. GameCaretaker caretaker = new GameCaretaker();
  4. Game game = new Game(97, 99);
  5. //先存档
  6. caretaker.setRecord(game.saveRecord());
  7. shoot(game, false, 3);
  8. //回档
  9. game.restoreRecord(caretaker.getRecord());
  10. shoot(game,false, 3);
  11. //再回档
  12. game.restoreRecord(caretaker.getRecord());
  13. shoot(game, true, 3);
  14. }
  15. private static void shoot(Game game, boolean goal, int point ){
  16. game.shoot(goal, point);
  17. game.setEnd(true);
  18. game.showGame();
  19. }
  20. }

已经可以用了,但是会发现这里只能回退一步,只能回到上一个最新的存档,下面看看多步回退。

5.2、第二版

  1. public class GameCaretaker {
  2. private Map<Integer, Record> records = new HashMap<Integer, Record>();
  3. public Record getRecords(Integer key) {
  4. if (records.containsKey(key)){
  5. return records.get(key);
  6. }else {
  7. throw new RuntimeException();
  8. }
  9. }
  10. public void setRecords(Integer key, Record record) {
  11. records.put(key, record);
  12. }
  13. public void showRecords(){
  14. System.out.println("有" + records.keySet().size() + "个存档,如下:");
  15. for (Integer key: records.keySet()){
  16. System.out.println("存档" + key);
  17. }
  18. }
  19. }

测试:

  1. public class MementoDemo {
  2. private static GameCaretaker caretaker = new GameCaretaker();
  3. private static Game game = new Game(98,100);
  4. private static Integer key = 1;
  5. public static void main(String[] args) {
  6. play(false, 2, false, 3);
  7. game.showGame();
  8. showRecords();
  9. System.out.println("================================================================");
  10. play(true, 2, true, 2);
  11. game.showGame();
  12. showRecords();
  13. System.out.println("================================================================");
  14. play(true, 3, true, 1);
  15. game.showGame();
  16. showRecords();
  17. System.out.println("================================================================");
  18. play(false, 2, true, 3);
  19. game.setEnd(true);
  20. game.showGame();
  21. System.out.println("================================================================");
  22. //回到存档1
  23. System.out.println("回到存档1");
  24. game.restoreRecord(caretaker.getRecords(1));
  25. play(false, 2, true, 3);
  26. game.setEnd(true);
  27. game.showGame();
  28. }
  29. private static void play(boolean goal1, int point1, boolean goal2, int point2){
  30. game.autoShoot(goal1, point1);
  31. game.shoot(goal2, point2);
  32. caretaker.setRecords(key++, game.saveRecord());
  33. }
  34. private static void showRecords(){
  35. caretaker.showRecords();
  36. }
  37. }

六、优点和缺点

6.1、优点

备忘录模式的主要优点如下:

6.2、缺点

备忘录模式的主要缺点如下:

七、适用环境

备忘录模式在很多软件的使用过程中普遍存在,但是在应用软件开发中,它的使用频率并不太高,因为现在很多基于窗体和浏览器的应用软件并没有提供撤销操作。

在以下情况下可以考虑使用备忘录模式:

八、模式应用

在一些字处理软件、图像编辑软件、数据库管理系统等软件中备忘录模式都得到了很好的应用。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注