对备忘录模式的明白

[复制链接]
发表于 2025-9-30 03:09:55 | 显示全部楼层 |阅读模式
一、场景

1、标题【泉源】

1.1 标题形貌

小明正在计划一个简朴的计数器应用,支持增长(Increment)和淘汰(Decrement)操纵,以及打消(Undo)和重做(Redo)操纵,请你利用备忘录模式帮他实现。
1.2 输入形貌

输入包罗多少行,每行包罗一个字符串,体现计数器应用的操纵,操纵包罗 “Increment”、“Decrement”、“Undo” 和 “Redo”。
1.3 输出形貌

对于每个 “Increment” 和 “Decrement” 操纵,输出当前计数器的值,计数器数值从0开始 对于每个 “Undo” 操纵,输出打消后的计数器值。 对于每个 “Redo” 操纵,输出重做后的计数器值。
1.4 输入示例

  1. Increment
  2. Increment
  3. Decrement
  4. Undo
  5. Redo
  6. Increment
复制代码
1.5 输出示例

  1. 1
  2. 2
  3. 1
  4. 2
  5. 1
  6. 2
复制代码
2、明白需求



  • 增长(Increment)和淘汰(Decrement)操纵比力好明白,不赘述了。
  • 重点明白下:打消(Undo)和重做(Redo)操纵。


    • 一样寻常编辑器,都支持Undo和Redo操纵。
    • Undo操纵:由于操纵导致值发生厘革,比方,0酿成1。我们必要记下厘革的值,如许才方便用户回退。

      • 很显着,应该用栈来纪录。比方:0 -> 1 -> 2 -> 3。 当前处于3,接下来Undo,应该从3酿成2。也就是把3从栈中弹出。

    • Redo操纵:依然基于“0 -> 1 -> 2 -> 3”举行分析,当前处于3,用户Undo后,3酿成2,接下来,用户Redo了,也就是渴望2又变回3。

      • 也就是,我们必要纪录Undo栈中弹出来的值。很显然,也是一个栈。


二、不接纳备忘录计划模式

1、代码

  1. public class Main {
  2.     public static void main(String[] args) {
  3.         Scanner scanner = new Scanner(System.in);
  4.         int res = 0;
  5.         // 栈
  6.         Deque<Integer> undoStack = new ArrayDeque<>();
  7.         undoStack.push(res);
  8.         Deque<Integer> redoStack = new ArrayDeque<>();
  9.         while (scanner.hasNextLine()) {
  10.             String command = scanner.nextLine();
  11.             res = runCommand(command, res, undoStack, redoStack);
  12.             System.out.println(res);
  13.         }
  14.     }
  15.     private static Integer runCommand(String command, Integer res, Deque<Integer> undoStack, Deque<Integer> redoStack) {
  16.         if ("Increment".equals(command)) {
  17.             res += 1;
  18.             undoStack.push(res);
  19.             return res;
  20.         } else if ("Decrement".equals(command)) {
  21.             res -= 1;
  22.             undoStack.push(res);
  23.             return res;
  24.         } else if ("Undo".equals(command)) {
  25.             if (undoStack.size() == 1) {
  26.                 // 相当于还没有做任何操作,用户就执行了Undo
  27.                 return undoStack.peek();
  28.             } else if (undoStack.size() > 1) {
  29.                 Integer value = undoStack.pop();
  30.                 redoStack.push(value);
  31.                 return undoStack.peek();
  32.             }
  33.         } else if ("Redo".equals(command)) {
  34.             if (!redoStack.isEmpty()) {
  35.                 Integer value = redoStack.pop();
  36.                 undoStack.push(value);
  37.                 return value;
  38.             }
  39.         }
  40.         return res;
  41.     }
  42. }
复制代码
2、标题



  • Increment等操纵是客户端(main方法)的下令,客户端不应该看到undoStack、redoStack等数据。

    • 上面的写法是典范的面向过程开发,我们必要利用面向对象开发。


  • 很显然,我们必要计划一个Calculator。

      1. public class Calculator {
      2.     private int value;
      3.     public Calculator() {
      4.         this.value = 0;
      5.     }
      6.     public Integer runCommand(String command) {
      7.         return null;
      8.     }
      9. }
      10. public class Main {
      11.     public static void main(String[] args) {
      12.         Scanner scanner = new Scanner(System.in);
      13.         Calculator calculator = new Calculator();
      14.         while (scanner.hasNextLine()) {
      15.             String command = scanner.nextLine();
      16.             Integer res = calculator.runCommand(command);
      17.             System.out.println(res);
      18.         }
      19.     }
      20. }
      复制代码

  • 为了实现Undo、Redo,这个类的对象有一个特点,必要生存和规复对象之前的状态。

    • 备忘录模式是一种举动计划模式, 答应在不袒露对象实现细节的情况下生存和规复对象之前的状态。[先有场景,后有计划模式]

3、错误的备忘录模式

  1. public class Calculator {
  2.     private int value;
  3.     private Deque<Integer> undoStack;
  4.     private Deque<Integer> redoStack;
  5.     public Calculator() {
  6.         this.value = 0;
  7.         this.undoStack = new ArrayDeque<>();
  8.         undoStack.push(value);
  9.         this.redoStack = new ArrayDeque<>();
  10.     }
  11.     public Integer runCommand(String command) {
  12.         if ("Increment".equals(command)) {
  13.             value += 1;
  14.             undoStack.push(value);
  15.             return value;
  16.         } else if ("Decrement".equals(command)) {
  17.             value -= 1;
  18.             undoStack.push(value);
  19.             return value;
  20.         } else if ("Undo".equals(command)) {
  21.             if (undoStack.size() == 1) {
  22.                 // 相当于还没有做任何操作,用户就执行了Undo
  23.                 return undoStack.peek();
  24.             } else if (undoStack.size() > 1) {
  25.                 Integer v = undoStack.pop();
  26.                 redoStack.push(v);
  27.                 return undoStack.peek();
  28.             }
  29.         } else if ("Redo".equals(command)) {
  30.             if (!redoStack.isEmpty()) {
  31.                 Integer v = redoStack.pop();
  32.                 undoStack.push(v);
  33.                 return v;
  34.             }
  35.         }
  36.         return value;
  37.     }
  38. }
复制代码


  • Calculator这个类是违背单一职责的,按照备忘录模式的经典计划,应该具有3个脚色:

    • Originator(原发器):状态持有者,而且可以哀求生存状态和规复状态。
    • Memento(备忘录):负责生存状态和规复状态。
    • Caretaker(负责人):负责管理备忘录。

三、接纳备忘录计划模式

1、代码

1.1 Originator(原发器)

  1. public class Counter {
  2.     private int value;
  3.     public Counter() {
  4.         this.value = 0;
  5.     }
  6.     public int getValue() {
  7.         return value;
  8.     }
  9.     public void increment() {
  10.         this.value++;
  11.     }
  12.     public void decrement() {
  13.         this.value--;
  14.     }
  15.     public Memento createMemento() {
  16.         return new Memento(this.value);
  17.     }
  18.     public void restoreMemento(Memento memento) {
  19.         this.value = memento.getValue();
  20.     }
  21. }
复制代码
1.2 Memento(备忘录)

  1. public class Memento {
  2.     private int value;
  3.     public Memento(int value) {
  4.         this.value = value;
  5.     }
  6.     public int getValue() {
  7.         return value;
  8.     }
  9. }
复制代码
1.3 Caretaker(负责人)

  1. public class Calculator {
  2.     private Counter counter;
  3.     private Deque<Memento> undoStack;
  4.     private Deque<Memento> redoStack;
  5.     public Calculator() {
  6.         counter = new Counter();
  7.         undoStack = new ArrayDeque<>();
  8.         redoStack = new ArrayDeque<>();
  9.     }
  10.     public Integer runCommand(String command) {
  11.         if (command.equals("Increment")) {
  12.             counter.increment();
  13.             undoStack.push(counter.createMemento());
  14.             redoStack.clear(); // redoStack是专门用来记录undoStack弹出的状态的,undoStack放入新状态后,redoStack里面的状态就无效了
  15.         } else if (command.equals("Decrement")) {
  16.             counter.decrement();
  17.             undoStack.push(counter.createMemento());
  18.             redoStack.clear();
  19.         } else if (command.equals("Undo")) {
  20.             if (!undoStack.isEmpty()) {
  21.                 Memento memento = undoStack.pop();
  22.                 redoStack.push(memento);
  23.                 counter.restoreMemento(undoStack.peek());
  24.             }
  25.         } else if (command.equals("Redo")) {
  26.             if (!redoStack.isEmpty()) {
  27.                 Memento memento = redoStack.pop();
  28.                 counter.restoreMemento(memento);
  29.                 undoStack.push(memento);
  30.             }
  31.         } else {
  32.             throw new RuntimeException("Unknown command");
  33.         }
  34.         return counter.getValue();
  35.     }
  36. }
复制代码
1.4 客户端

  1. public class Main {
  2.     public static void main(String[] args) {
  3.         Scanner scanner = new Scanner(System.in);
  4.         Calculator calculator = new Calculator();
  5.         while (scanner.hasNextLine()) {
  6.             String command = scanner.nextLine();
  7.             Integer res = calculator.runCommand(command);
  8.             System.out.println(res);
  9.         }
  10.     }
  11. }
复制代码
2、思索



  • 相比“3、错误的备忘录模式”,每个类的职责更单逐一些。但,Memento好贫苦啊。相当于把Counter的字段复制了一遍。以后Counter加一个字段,Memento就要补一个字段。太贫苦了。
  • 一种不错的办理办法是:序列化。
    1. public class Counter {
    2.     private int value;
    3.     public Counter() {
    4.     }
    5.     public void setValue(int value) {
    6.         this.value = value;
    7.     }
    8.     public int getValue() {
    9.         return value;
    10.     }
    11.     public void increment() {
    12.         this.value++;
    13.     }
    14.     public void decrement() {
    15.         this.value--;
    16.     }
    17.     public void restoreMemento(Memento memento) {
    18.         String backup = memento.getBackup();
    19.         Counter tmpCounter = JSON.parseObject(backup, Counter.class);
    20.         this.value = tmpCounter.value;
    21.     }
    22.     public Memento createMemento() {
    23.         String backup = JSON.toJSONString(this);
    24.         return new Memento(backup);
    25.     }
    26. }
    27. public class Memento {
    28.     private String backup;
    29.     public Memento(String backup) {
    30.         this.backup = backup;
    31.     }
    32.     public String getBackup() {
    33.         return this.backup;
    34.     }
    35. }
    复制代码

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表