撤销功能的实现-备忘录模式

备忘录模式定义(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为型模式,其别名为Token。
备忘录模式的核心是备忘录类以及用于管理备忘录的负责人类的设计,其结构如图21-3所示:

在备忘录模式结构图中包含如下几个角色:
● Originator(原发器):它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。
●Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,原发器的设计在不同的编程语言中实现机制会有所不同。
●Caretaker(负责人):负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。
21.3 完整解决方案
为了实现撤销功能,Sunny公司开发人员决定使用备忘录模式来设计中国象棋软件,其基本结构如图21-4所示:

在图21-4中,Chessman充当原发器,ChessmanMemento充当备忘录,MementoCaretaker充当负责人,在MementoCaretaker中定义了一个ChessmanMemento类型的对象,用于存储备忘录。
原发器:
//象棋棋子类:原发器 class Chessman { private String label; private int x; private int y; public Chessman(String label,int x,int y) { this.label = label; this.x = x; this.y = y; } public void setLabel(String label) { this.label = label; } public void setX(int x) { this.x = x; } public void setY(int y) { this.y = y; } public String getLabel() { return (this.label); } public int getX() { return (this.x); } public int getY() { return (this.y); } //保存状态 public ChessmanMemento save() { return new ChessmanMemento(this.label,this.x,this.y); } //恢复状态 public void restore(ChessmanMemento memento) { this.label = memento.getLabel(); this.x = memento.getX(); this.y = memento.getY(); } }
备忘录:
//象棋棋子备忘录类:备忘录 class ChessmanMemento { private String label; private int x; private int y; public ChessmanMemento(String label,int x,int y) { this.label = label; this.x = x; this.y = y; } public void setLabel(String label) { this.label = label; } public void setX(int x) { this.x = x; } public void setY(int y) { this.y = y; } public String getLabel() { return (this.label); } public int getX() { return (this.x); } public int getY() { return (this.y); } }
负责人:
//象棋棋子备忘录管理类:负责人 class MementoCaretaker { private ChessmanMemento memento; public ChessmanMemento getMemento() { return memento; } public void setMemento(ChessmanMemento memento) { this.memento = memento; } }
客户端测试:
class Client { public static void main(String args[]) { MementoCaretaker mc = new MementoCaretaker(); Chessman chess = new Chessman("车",1,1); display(chess); mc.setMemento(chess.save()); //保存状态 chess.setY(4); display(chess); mc.setMemento(chess.save()); //保存状态 chess.setX(5); display(chess); System.out.println("******悔棋******"); chess.restore(mc.getMemento()); //恢复状态 display(chess); } public static void display(Chessman chess) { System.out.println("棋子" + chess.getLabel() + "当前位置为:" + "第" + chess.getX() + "行" + "第" + chess.getY() + "列。"); } }
编译并运行程序,输出结果如下:
棋子车当前位置为:第1行第1列。 棋子车当前位置为:第1行第4列。 棋子车当前位置为:第5行第4列。 ******悔棋****** 棋子车当前位置为:第1行第4列。
21.4实现多次撤销
Sunny软件公司开发人员通过使用备忘录模式实现了中国象棋棋子的撤销操作,但是使用上述代码只能实现一次撤销,因为在负责人类中只定义一个备忘录对象来保存状态,后面保存的状态会将前一次保存的状态覆盖,但有时候用户需要撤销多步操作。如何实现多次撤销呢?本节将提供一种多次撤销的解决方案,那就是在负责人类中定义一个集合来存储多个备忘录,每个备忘录负责保存一个历史状态,在撤销时可以对备忘录集合进行逆向遍历,回到一
个指定的历史状态,而且还可以对备忘录集合进行正向遍历,实现重做(Redo)操作,即取消撤销,让对象状态得到恢复。
改进之后的中国象棋棋子撤销功能结构图如图21-5所示:

在图21-5中,我们对负责人类MementoCaretaker进行了修改,在其中定义了一个ArrayList类型的集合对象来存储多个备忘录,其代码如下所示:
浙公网安备 33010602011771号
对于备忘录类Memento而言,它通常提供了与原发器相对应的属性(可以是全部,也可以是部分)用于存储原发器的状态,典型的备忘录类设计代码如下:
对于负责人类Caretaker,它用于保存备忘录对象,并提供getMemento()方法用于向客户端返回一个备忘录对象,原发器通过使用这个备忘录对象可以回到某个历史状态。典型的负责人类的实现代码如下:
在Caretaker类中不应该直接调用Memento中的状态改变方法,它的作用仅仅用于存储备忘录对象。将原发器备份生成的备忘录对象存储在其中,当用户需要对原发器进行恢复时再将存储在其中的备忘录对象取出。