小明正在设计一个简单的计数器应用,支持增加(Increment)和减少(Decrement)操作,以及撤销(Undo)和重做(Redo)操作,请你使用备忘录模式帮他实现。
输入包含若干行,每行包含一个字符串,表示计数器应用的操作,操作包括 “Increment”、“Decrement”、“Undo” 和 “Redo”。
对于每个 “Increment” 和 “Decrement” 操作,输出当前计数器的值,计数器数值从0开始 对于每个 “Undo” 操作,输出撤销后的计数器值。 对于每个 “Redo” 操作,输出重做后的计数器值。
Increment
Increment
Decrement
Undo
Redo
Increment
1
2
1
2
1
2
增加(Increment)和减少(Decrement)操作比较好理解,不赘述了。
重点理解下:撤销(Undo)和重做(Redo)操作。
一般编辑器,都支持Undo和Redo操作。
Undo操作:因为操作导致值发生变化,例如,0变成1。我们需要记下变化的值,这样才方便用户回退。
Redo操作:依然基于“0 -> 1 -> 2 -> 3”进行说明,当前处于3,用户Undo后,3变成2,接下来,用户Redo了,也就是希望2又变回3。
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int res = 0;
// 栈
Deque<Integer> undoStack = new ArrayDeque<>();
undoStack.push(res);
Deque<Integer> redoStack = new ArrayDeque<>();
while (scanner.hasNextLine()) {
String command = scanner.nextLine();
res = runCommand(command, res, undoStack, redoStack);
System.out.println(res);
}
}
private static Integer runCommand(String command, Integer res, Deque<Integer> undoStack, Deque<Integer> redoStack) {
if ("Increment".equals(command)) {
res += 1;
undoStack.push(res);
return res;
} else if ("Decrement".equals(command)) {
res -= 1;
undoStack.push(res);
return res;
} else if ("Undo".equals(command)) {
if (undoStack.size() == 1) {
// 相当于还没有做任何操作,用户就执行了Undo
return undoStack.peek();
} else if (undoStack.size() > 1) {
Integer value = undoStack.pop();
redoStack.push(value);
return undoStack.peek();
}
} else if ("Redo".equals(command)) {
if (!redoStack.isEmpty()) {
Integer value = redoStack.pop();
undoStack.push(value);
return value;
}
}
return res;
}
}
Increment等操作是客户端(main方法)的命令,客户端不应该看到undoStack、redoStack等数据。
很显然,我们需要设计一个Calculator。
public class Calculator {
private int value;
public Calculator() {
this.value = 0;
}
public Integer runCommand(String command) {
return null;
}
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Calculator calculator = new Calculator();
while (scanner.hasNextLine()) {
String command = scanner.nextLine();
Integer res = calculator.runCommand(command);
System.out.println(res);
}
}
}
为了实现Undo、Redo,这个类的对象有一个特点,需要保存和恢复对象之前的状态。
public class Calculator {
private int value;
private Deque<Integer> undoStack;
private Deque<Integer> redoStack;
public Calculator() {
this.value = 0;
this.undoStack = new ArrayDeque<>();
undoStack.push(value);
this.redoStack = new ArrayDeque<>();
}
public Integer runCommand(String command) {
if ("Increment".equals(command)) {
value += 1;
undoStack.push(value);
return value;
} else if ("Decrement".equals(command)) {
value -= 1;
undoStack.push(value);
return value;
} else if ("Undo".equals(command)) {
if (undoStack.size() == 1) {
// 相当于还没有做任何操作,用户就执行了Undo
return undoStack.peek();
} else if (undoStack.size() > 1) {
Integer v = undoStack.pop();
redoStack.push(v);
return undoStack.peek();
}
} else if ("Redo".equals(command)) {
if (!redoStack.isEmpty()) {
Integer v = redoStack.pop();
undoStack.push(v);
return v;
}
}
return value;
}
}
Calculator这个类是违背单一职责的,按照备忘录模式的经典设计,应该具有3个角色:
public class Counter {
private int value;
public Counter() {
this.value = 0;
}
public int getValue() {
return value;
}
public void increment() {
this.value++;
}
public void decrement() {
this.value--;
}
public Memento createMemento() {
return new Memento(this.value);
}
public void restoreMemento(Memento memento) {
this.value = memento.getValue();
}
}
public class Memento {
private int value;
public Memento(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public class Calculator {
private Counter counter;
private Deque<Memento> undoStack;
private Deque<Memento> redoStack;
public Calculator() {
counter = new Counter();
undoStack = new ArrayDeque<>();
redoStack = new ArrayDeque<>();
}
public Integer runCommand(String command) {
if (command.equals("Increment")) {
counter.increment();
undoStack.push(counter.createMemento());
redoStack.clear(); // redoStack是专门用来记录undoStack弹出的状态的,undoStack放入新状态后,redoStack里面的状态就无效了
} else if (command.equals("Decrement")) {
counter.decrement();
undoStack.push(counter.createMemento());
redoStack.clear();
} else if (command.equals("Undo")) {
if (!undoStack.isEmpty()) {
Memento memento = undoStack.pop();
redoStack.push(memento);
counter.restoreMemento(undoStack.peek());
}
} else if (command.equals("Redo")) {
if (!redoStack.isEmpty()) {
Memento memento = redoStack.pop();
counter.restoreMemento(memento);
undoStack.push(memento);
}
} else {
throw new RuntimeException("Unknown command");
}
return counter.getValue();
}
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Calculator calculator = new Calculator();
while (scanner.hasNextLine()) {
String command = scanner.nextLine();
Integer res = calculator.runCommand(command);
System.out.println(res);
}
}
}
相比“3、错误的备忘录模式”,每个类的职责更单一一些。但,Memento好麻烦啊。相当于把Counter的字段复制了一遍。以后Counter加一个字段,Memento就要补一个字段。太麻烦了。
一种不错的解决办法是:序列化。
public class Counter {
private int value;
public Counter() {
}
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void increment() {
this.value++;
}
public void decrement() {
this.value--;
}
public void restoreMemento(Memento memento) {
String backup = memento.getBackup();
Counter tmpCounter = JSON.parseObject(backup, Counter.class);
this.value = tmpCounter.value;
}
public Memento createMemento() {
String backup = JSON.toJSONString(this);
return new Memento(backup);
}
}
public class Memento {
private String backup;
public Memento(String backup) {
this.backup = backup;
}
public String getBackup() {
return this.backup;
}
}