我是荔园微风,作为一名在IT界整整25年的老兵,今天总结一下Windows环境下如何编程实现备忘录模式(设计模式)。
不知道大家有没有这样的感觉,看了一大堆编程和设计模式的书,却还是很难理解设计模式,无从下手。为什么?因为你看的都是理论书籍。
我今天就在Windows操作系统上安装好JAVA的IDE编程工具,并用JAVA语言来实现一个备忘录模式,真实的实现一个,你看懂代码后,自然就明白了。
备忘录模式Memento Pattern (行为型设计模式)
定义:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
上面定义听懂了吗?莫名其妙看不懂对吧。所以我们还是来看看实现生活中的例子。
2015年那个会下围棋的人工智能很厉害吧。那这里我要问一下,那既然是人工智能,如果他下错棋了,怎么办?所以,在研制人工智能时,还得给人工智能增加一个梅棋的功能。
备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,当前很多软件都提供了撤销(Undo)操作,比如说OFFICE,其中就使用了备忘录模式。
在实现撤销时,首先必须保存软件系统的历史状态,当用户需要取消错误操作并且返回到某个历史状态时,可以取出事先保存的历史状态来覆盖当前状态。
备忘录模式正为解决此类撤销问题而诞生,它为我们的软件提供了反悔的机制,通过使用备忘录模式可以使系统恢复到某一特定的历史状态。
在备忘录模式结构图中包含如下几个角色。 Originator(原发器):它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,原发器的设计在不同的编程语言中实现机制会有所不同。Caretaker(负责人):负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。
理解备忘录模式关键在于如何设计备忘录类和负责人类。由于在备忘录中存储的是原发器的中间状态,因此需要防止原发器以外的其他对象访问备忘录,特别是不允许其他对象来修改备忘录。
在使用备忘录模式时,首先应该存在一个原发器类Originator,在真实业务中,原发器类是一个具体的业务类,它包含一些用于存储成员数据的属性,典型代码如下所示:
package designpatterns.memento;
public class Originator {
private String state;
public Originator(String state){
this.state=state;
}
// 创建一个备忘录对象
public Memento createMemento() {
return new Memento(this);
}
// 根据备忘录对象恢复原发器状态
public void restoreMemento(Memento m) {
state = m.state;
}
public void setState(String state) {
this.state=state;
}
public String getState() {
return this.state;
}
}
对于备忘录类Memento而言,它通常提供了与原发器相对应的属性用于存储原发器的状态,典型的备忘录类设计代码如下:
package designpatterns.memento;
//备忘录类,默认可见性,包内可见
class Memento {
private String state;
public Memento(Originator o) {
state = o.getState();
}
public void setState(String state) {
this.state=state;
}
public String getState() {
return this.state;
}
}
在设计备忘录类时需要考虑其封装性,除了Originator类,不允许其他类来调用备忘录类Memento的构造函数与相关方法,如果不考虑封装性,允许其他类调用setState()等方法,将导致在备忘录中保存的历史状态发生改变,通过撤销操作所恢复的状态就不再是真实的历史状态。
为了实现对备忘录对象的封装,需要对备忘录的调用进行控制,对于原发器而言,它可以调用备忘录的所有信息,允许原发器访问返回到先前状态所需的所有数据;对于负责人而言,只负责备忘录的保存并将备忘录传递给其他对象;对于其他对象而言,只需要从负责人处取出备忘录对象并将原发器对象的状态恢复,而无须关心备忘录的保存细节。理想的情况是只允许生成该备忘录的那个原发器访问备忘录的内部状态。
在使用Java语言实现备忘录模式时,一般通过将Memento类与Originator类定义在同一个包中来实现封装,在Java语言中可使用默认访问标识符来定义Memento类,即保证其包内可见。只有Originator类可以对Memento进行访问,而限制了其他类对Memento的访问。在 Memento中保存了Originator的state值,如果Originator中的state值改变之后需撤销,可以通过调用它的restoreMemento()方法进行恢复。对于负责人类Caretaker,它用于保存备忘录对象,并提供getMemento()方法用于向客户端返回一个备忘录对象,原发器通过使用这个备忘录对象可以回到某个历史状态。典型的负责人类的实现代码如下:
package designpatterns.memento;
public class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento=memento;
}
}
在Caretaker类中不应该直接调用Memento中的状态改变方法,它的作用仅仅用于存储备忘录对象。将原发器备份生成的备忘录对象存储在其中,当用户需要对原发器进行恢复时再将存储在其中的备忘录对象取出。
在客户端中通过创建Memento对象来保存原发器的历史状态,在需要的时候再用历史状态来覆盖当前状态,代码如下:
package designpatterns. memento;
public class Client {
public static void main(String args[]){
//创建原发器对象
Originator ori= new Originator("状态1");
System. out. println(ori. getState());
//创建负责人对象,保存创建的备忘录对象
Caretaker ct new Caretaker();
ct. setMemento(ori. createMemento());
ori. setState("状态2");
System. out. println(ori. getState());
/从负责人对象中取出备忘录对象,实现撤销
ori. restoreMemento(ct. getMemento());
System. out. println(ori. getState());
}
}
输出结果为
状态1
状态2
状态1
为了实现撤销功能,我们使用备忘录模式来设计围棋软件,Goman充当原发器,GomanMemento充当备忘录,MementoCaretaker充当负责人,在MementoCaretaker中定义了一个GomanMemento类型的对象,用于存储备忘录。完整代码如下所示:
(1)Goman充当原发器
package designpatterns.memento;
public class Goman {
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 GomanMemento save() {
return new GomanMemento(this.label,this.x,this.y);
}
//恢复状态
public void restore(GomanMemento memento) {
this.label = memento.getLabel();
this.x = memento.getX();
this.y = memento.getY();
}
}
(2)GomanMemento充当备忘录
package designpatterns.memento;
public class GomanMemento {
private String label;
private int x;
private int y;
public GomanMemento(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);
}
}
(3)MementoCaretaker充当负责人
package designpatterns.memento;
public class MementoCaretaker {
private GomanMemento memento;
public GomanMemento getMemento() {
return memento;
}
public void setMemento(GomanMemento memento) {
this.memento = memento;
}
}
(4)Client客户端
package designpatterns.memento;
public class Client {
public static void main(String args[]) {
MementoCaretaker mc = new MementoCaretaker();
Goman go = new Goman("黑1",10,15);
display(go);
mc.setMemento(go.save()); //保存状态
go.setY(18);
display(go);
mc.setMemento(go.save()); //保存状态
display(go);
go.setX(16);
display(go);
System.out.println("******悔棋******");
go.restore(mc.getMemento()); //恢复状态
display(go);
}
public static void display(Goman go) {
System.out.println("棋子" + go.getLabel() + "当前位置为:" + "第" + go.getX() + "行" + "第" + go.getY() + "列。");
}
}
备忘录模式在很多软件的使用过程中普遍存在,但是在应用软件开发中,它的使用频率并不太高,因为现在很多基于窗体和浏览器的应用软件并没有提供撤销操作。如果需要为软件提供撤销功能,备忘录模式无疑是一种很好的解决方案。在一些字处理软件、图像编辑软件、数据库管理系统等软件中备忘录模式都得到了很好的应用。
各位小伙伴,这次我们就说到这里,下次我们再深入研究windows环境下的各类设计模式实现。
作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。