企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
第24章 备忘录模式 24.1 如此追女孩子,你还不乐 大家有没有看过尼古拉斯·凯奇主演的《Next》(中文译名为《预见未来》)?尼古拉斯·凯奇饰演一个可以预视并且扭转未来的人,其中有一个情节很是让人心动——男女主角见面的那段情节:Cris Johnson(尼古拉斯·凯奇饰演)坐在咖啡吧台前,看着离自己近在咫尺的Callie Ferris(朱莉安·摩尔饰演),计划着怎么认识这个命中注定的女人,看Cris Johnson如何利用自己的特异功能: ● Cris Johnson端着一杯咖啡走过去,说“你好,可以认识你吗?”被拒绝,恢复到坐在咖啡吧台前的状态。 ● 走过去询问是否可以搭车,被拒绝,恢复原状。 ● 帮助解决困境,被拒绝,恢复原状。 ● 采用嬉皮士的方式解决困境,被拒绝,恢复原状。 ● 帮助解决困境,被打伤,装可怜,Callie Ferris怜惜,于是乎相识了。 看看这是一件多么幸福的事情,追求一个女生可以多次反复地实验,直到找到好的方法和途径为止,这估计是大多数男生都希望获得的特异功能。想想看,看到一个心仪的女生,我们若反复尝试,总会有一个方法打动她的,多美好的一件事。现在我们还得回到现实生活,我们来分析一下类似事情的经过: ● 复制一个当前状态,保留下来,这个状态就是等会儿搭讪女孩子失败后要恢复的状态,你不恢复原始状态,这不就露馅儿了吗? ● 每次试探性尝试失败后,都必须恢复到这个原始状态。 ● N次试探总有一次成功吧,成功以后即可走成功路线。 想想看,我们这里的场景中最重要的是哪一块?对的,是原始状态的保留和恢复这块,如何保留一个原始,如何恢复一个原始状态才是最重要的,那想想看,我们应该怎么实现呢?很简单呀,我们可以定义一个中间变量,保留这个原始状态。我们先看看类图,如图24-1所示。 ![](https://box.kancloud.cn/2016-08-14_57b0036946161.jpg) 图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所示。 ![](https://box.kancloud.cn/2016-08-14_57b003695a552.jpg) 图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所示。 ![](https://box.kancloud.cn/2016-08-14_57b003696e5ec.jpg) 图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());      } } 注意看黑体部分,就修改了这么多,看看程序的逻辑是不是清晰了很多,需要备份的时候就创建一个备份,然后丢给备忘录管理者进行管理,要取的时候再从管理者手中拿到这个备份。这个备份者就类似于一个备份的仓库管理员,创建一个丢进去,需要的时候再拿出来。这就是备忘录模式。