首页 » 设计模式之禅(第2版) » 设计模式之禅(第2版)全文在线阅读

《设计模式之禅(第2版)》第24章 备忘录模式

关灯直达底部

24.1 如此追女孩子,你还不乐

大家有没有看过尼古拉斯·凯奇主演的《Next》(中文译名为《预见未来》)?尼古拉斯·凯奇饰演一个可以预视并且扭转未来的人,其中有一个情节很是让人心动——男女主角见面的那段情节:Cris Johnson(尼古拉斯·凯奇饰演)坐在咖啡吧台前,看着离自己近在咫尺的Callie Ferris(朱莉安·摩尔饰演),计划着怎么认识这个命中注定的女人,看Cris Johnson如何利用自己的特异功能:

● Cris Johnson端着一杯咖啡走过去,说“你好,可以认识你吗?”被拒绝,恢复到坐在咖啡吧台前的状态。

● 走过去询问是否可以搭车,被拒绝,恢复原状。

● 帮助解决困境,被拒绝,恢复原状。

● 采用嬉皮士的方式解决困境,被拒绝,恢复原状。

● 帮助解决困境,被打伤,装可怜,Callie Ferris怜惜,于是乎相识了。

看看这是一件多么幸福的事情,追求一个女生可以多次反复地实验,直到找到好的方法和途径为止,这估计是大多数男生都希望获得的特异功能。想想看,看到一个心仪的女生,我们若反复尝试,总会有一个方法打动她的,多美好的一件事。现在我们还得回到现实生活,我们来分析一下类似事情的经过:

● 复制一个当前状态,保留下来,这个状态就是等会儿搭讪女孩子失败后要恢复的状态,你不恢复原始状态,这不就露馅儿了吗?

● 每次试探性尝试失败后,都必须恢复到这个原始状态。

● N次试探总有一次成功吧,成功以后即可走成功路线。

想想看,我们这里的场景中最重要的是哪一块?对的,是原始状态的保留和恢复这块,如何保留一个原始,如何恢复一个原始状态才是最重要的,那想想看,我们应该怎么实现呢?很简单呀,我们可以定义一个中间变量,保留这个原始状态。我们先看看类图,如图24-1所示。

图24-1 男孩状态类图

太简单的类图了,我们来解释一下图中的状态state是什么意思,在某一时间点的所有位置信息、心理信息、环境信息都属于状态,我们这里用了一个标识性的名词state代表所有状态,比如在追女孩子前心情是期待、心理是焦躁不安等。每一次去认识女孩子都是会发生状态变化的,我们使用changeState方法来代替,由于程序比较简单,就没有编写接口,我们来看实现,如代码清单24-1所示。

代码清单24-1 男孩状态类

public class Boy {     //男孩的状态     private String state = /"/";     //认识女孩子后状态肯定改变,比如心情、手中的花等     public void changeState{     this.state = /"心情可能很不好/";     }     public String getState {     return state;     }     public void setState(String state) {     this.state = state;     }}  

程序是很简单,主要的业务逻辑是在场景类中,我们来看场景类是如何进行状态的保留、恢复的,如代码清单24-2所示。

代码清单24-2 场景类

public class Client {     public static void main(String args) {     //声明出主角     Boy boy = new Boy;     //初始化当前状态     boy.setState(/"心情很棒!/");     System.out.println(/"=====男孩现在的状态======/");     System.out.println(boy.getState);     //需要记录下当前状态呀     Boy backup = new Boy;     backup.setState(boy.getState);     //男孩去追女孩,状态改变     boy.changeState;     System.out.println(/"n=====男孩追女孩子后的状态======/");     System.out.println(boy.getState);     //追女孩失败,恢复原状     boy.setState(backup.getState);     System.out.println(/"n=====男孩恢复后的状态======/");     System.out.println(boy.getState);}}  

程序运行结果如下所示:

=====男孩现在的状态======

心情很棒!

=====男孩追女孩子后的状态======

心情可能很不好

=====男孩恢复后的状态======

心情很棒!

程序运行正确,输出结果也是我们期望的,但是结果正确并不表示程序是最优的,我们来看看场景类Client,它代表的是高层模块,或者说是非“近亲”模块的调用者,注意看backup变量的使用,它对于高层模块完全是多余的,为什么一个状态的保存和恢复要让高层模块来负责呢?这应该是Boy类的职责,而不应该让高层模块来完成,也就是破坏了Boy类的封装,或者说Boy类没有封装好,它应该是把backup的定义容纳进来,而不应该让高层模块来定义。

问题我们已经知道了,就是Boy类封装不够,那我们应该如何修改呢?如果在Boy类中再增加一个方法或者其他的内部类来保存这个状态,则对单一职责原则是一种破坏,想想看单一职责原则是怎么说的?一个类的职责应该是单一的,Boy类本身的职责是追求女孩子,而保留和恢复原始状态则应该由另外一个类来承担,那我们把这个类取名就叫做备忘录,这和大家经常在桌面上贴的那个便签是一个概念,分析到这里我们的思路已经非常清楚了,我们来修改一下类图,如图24-2所示。

图24-2 完善后的男孩状态类图

改动很小,增加了一个新的类Memento,负责状态的保存和备份;同时,在Boy类中增加了创建一份备忘录createMemento和恢复一个备忘录resotreMemento,我们先来看Boy类的变化,如代码清单24-3所示。

代码清单24-3 改进后的男孩状态类

public class Boy {     //男孩的状态     private String state = /"/";     //认识女孩子后状态肯定改变,比如心情、手中的花等     public void changeState{     this.state = /"心情可能很不好/";     }     public String getState {     return state;     }     public void setState(String state) {     this.state = state;     }     //保留一个备份     public Memento createMemento{     return new Memento(this.state);     }     //恢复一个备份     public void restoreMemento(Memento _memento){  this.setState(_memento.getState);     }}  

注意看,确实只增加了两个方法创建备份和恢复备份,至于在什么时候创建备份和恢复备份则是由高层模块决定的。我们再来看备忘录模块,如代码清单24-4所示。

代码清单24-4 备忘录

public class Memento {     //男孩的状态     private String state = /"/";     //通过构造函数传递状态信息     public Memento(String _state){     this.state = _state;     }     public String getState {     return state;     }     public void setState(String state) {     this.state = state;     }}  

这就是一个简单的JavaBean,保留男孩当时的状态信息。我们再来看场景类,稍做修改,如代码清单24-5所示。

代码清单24-5 改进后的场景类

public class Client {     public static void main(String args) {     //声明出主角     Boy boy = new Boy;     //初始化当前状态     boy.setState(/"心情很棒!/");     System.out.println(/"=====男孩现在的状态======/");     System.out.println(boy.getState);     //需要记录下当前状态呀     Memento mem = boy.createMemento;     //男孩去追女孩,状态改变     boy.changeState;     System.out.println(/"n=====男孩追女孩子后的状态======/");     System.out.println(boy.getState);     //追女孩失败,恢复原状     boy.restoreMemento(mem);     System.out.println(/"n=====男孩恢复后的状态======/");     System.out.println(boy.getState);     }}  

运行结果保持相同,虽然程序中不再重复定义Boy类的对象了,但是我们还是要关心备忘录,这对迪米特法则是一个亵渎,它告诉我们只和朋友类通信,那这个备忘录对象是我们必须要通信的朋友类吗?对高层模块来说,它最希望要做的就是创建一个备份点,然后在需要的时候再恢复到这个备份点就成了,它不用关心到底有没有备忘录这个类。那根据这一指导思想,我们就需要把备忘录类再包装一下,怎么包装呢?建立一个管理类,就是管理这个备忘录,如图24-3所示。

图24-3 完整的男孩追女生类图

又增加了一个JavaBean,Boy类和Memento没有任何改变,不再赘述。我们来看增加的备忘录管理类,如代码清单24-6所示。

代码清单24-6 备忘录管理者

public class Caretaker {     //备忘录对象     private Memento memento;     public Memento getMemento {     return memento;     }     public void setMemento(Memento memento) {     this.memento = memento;     }}  

这个太简单了,非常纯粹的一个JavaBean,甭管它多简单,只要有用就成,我们来看场景类如何调用,如代码清单24-7所示。

代码清单24-7 进一步改进后的场景类

public class Client {     public static void main(String args) {     //声明出主角     Boy boy = new Boy;     //声明出备忘录的管理者     Caretaker caretaker = new Caretaker;     //初始化当前状态     boy.setState(/"心情很棒!/");     System.out.println(/"=====男孩现在的状态======/");     System.out.println(boy.getState);     //需要记录下当前状态呀     caretaker.setMemento(boy.createMemento);     //男孩去追女孩,状态改变     boy.changeState;     System.out.println(/"n=====男孩追女孩子后的状态======/");     System.out.println(boy.getState);     //追女孩失败,恢复原状     boy.restoreMemento(caretaker.getMemento);     System.out.println(/"n=====男孩恢复后的状态======/");     System.out.println(boy.getState);     }}  

注意看黑体部分,就修改了这么多,看看程序的逻辑是不是清晰了很多,需要备份的时候就创建一个备份,然后丢给备忘录管理者进行管理,要取的时候再从管理者手中拿到这个备份。这个备份者就类似于一个备份的仓库管理员,创建一个丢进去,需要的时候再拿出来。这就是备忘录模式。