22.1 韩非子身边的卧底是谁派来的
《孙子兵法》有云:“知彼知己,百战不殆;不知彼而知己,一胜一负;不知彼,不知己,每战必殆”,那怎么才能知己知彼呢?知己是很容易的,自己的军队嘛,很容易知根知底,那怎么知彼呢?安插间谍是个好办法,这是古今中外屡试不爽的方法,我们今天就来讲一个间谍的故事。
韩非子大家都应该记得吧,法家的代表人物,主张建立法制社会,实施重罚制度,真是非常有远见呀!看看现在社会在呼吁什么,建立法制化的社会,这在2000多年前就已经提出了。大家可能还不知道,法家还有一个非常重要的代表人物——李斯。李斯是秦国的丞相,最终被残忍车裂的那位,李斯和韩非子都是荀子的学生,李斯是师兄,韩非子是师弟,若干年后,李斯成为最强诸侯国秦国的上尉,致力于统一全国,于是安插了间谍到各个国家的重要人物的身边,以获取必要的信息,韩非子作为韩国的重量级人物,身边自然有不少间谍,韩非子做的事,李斯都了如指掌,那可是相隔千里!怎么做到的呢?间谍呀!我们先通过程序把这个过程展现一下,看看李斯是怎么监控韩非子的,先看两个主角的类图,如图22-1所示。
图22-1 监控者和被监控者
仅有这两个对象还是不够的,我们要解决的是李斯是怎么监控韩非子的?创建一个后台线程一直处于运行状态,一旦发现韩非子在吃饭或者娱乐就触发事件?这是真实世界的翻版,安排了一个间谍,观察韩非子的生活起居,并上报给李斯,然后李斯再触发update事件,类图继续扩充,如图22-2所示。
图22-2 通过后台线程监控
这个类图应该是程序员最容易想到的,你要监控,我就给你找个间谍角色(Spy类),我们来看程序的实现,先看我们的主角韩非子的接口(类似于韩非子这样的人,被观察者角色),如代码清单22-1所示。
代码清单22-1 被观察者接口
public interface IHanFeiZi { //韩非子也是人,也要吃早饭的 public void haveBreakfast; //韩非之也是人,是人就要娱乐活动 public void haveFun;}
对接口进行扩充,增加了两个状态isHavingBreakfast(是否在吃早饭)和isHavingFun(是否在娱乐),以方便Spy进行监控,如代码清单22-2所示。
代码清单22-2 具体的被观察者
public class HanFeiZi implements IHanFeiZi{ //韩非子是否在吃饭,作为监控的判断标准 private boolean isHavingBreakfast = false; //韩非子是否在娱乐 private boolean isHavingFun = false; //韩非子要吃饭了 public void haveBreakfast{ System.out.println(/"韩非子:开始吃饭了.../"); this.isHavingBreakfast =true; } //韩非子开始娱乐了 public void haveFun{ System.out.println(/"韩非子:开始娱乐了.../"); this.isHavingFun = true; } //以下是bean的基本方法,getter/setter,不多说 public boolean isHavingBreakfast { return isHavingBreakfast; } public void setHavingBreakfast(boolean isHavingBreakfast) { this.isHavingBreakfast = isHavingBreakfast; } public boolean isHavingFun { return isHavingFun; } public void setHavingFun(boolean isHavingFun) { this.isHavingFun = isHavingFun; }}
其中有两个getter/setter方法,这个就没有在类图中表示出来,比较简单,通过isHavingBreakfast和isHavingFun这两个布尔型变量来判断韩非子是否在吃饭或者娱乐,韩非子属于被观察者,那还有观察者李斯,我们来看李斯的接口,如代码清单22-3所示。
代码清单22-3 抽象观察者
public interface ILiSi { //一发现别人有动静,自己也要行动起来 public void update(String context);}
李斯这类人比较简单,一发现自己观察的对象发生了变化,比如吃饭、娱乐,自己立刻也要行动起来,怎么行动呢?如代码清单22-4所示。
代码清单22-4 韩非子
public class LiSi implements ILiSi{ //首先李斯是个观察者,一旦韩非子有活动,他就知道,他就要向老板汇报 public void update(String str){ System.out.println(/"李斯:观察到韩非子活动,开始向老板汇报了.../"); this.reportToQinShiHuang(str); System.out.println(/"李斯:汇报完毕...n/"); } //汇报给秦始皇 private void reportToQinShiHuang(String reportContext){ System.out.println(/"李斯:报告,秦老板!韩非子有活动了--->/"+reportContext); }}
两个重量级的人物都定义出来了,间谍这个“卑鄙”小人是不是也要登台了,如代码清单22-5所示。
代码清单22-5 间谍
class Spy extends Thread{ private HanFeiZi hanFeiZi; private LiSi liSi; private String type; //通过构造函数传递参数,我要监控的是谁,谁来监控,要监控什么 public Spy(HanFeiZi _hanFeiZi,LiSi _liSi,String _type){ this.hanFeiZi =_hanFeiZi; this.liSi = _liSi; this.type = _type; } @Override public void run{ while(true){ if(this.type.equals(/"breakfast/")){ //监控是否在吃早餐 //如果发现韩非子在吃饭,就通知李斯 if(this.hanFeiZi.isHavingBreakfast){ this.liSi.update(/"韩非子在吃饭/"); //重置状态,继续监控 this.hanFeiZi.setHavingBreakfast(false); } }else{//监控是否在娱乐 if(this.hanFeiZi.isHavingFun){ this.liSi.update(/"韩非子在娱乐/"); this.hanFeiZi.setHavingFun(false); } } } }}
监控程序继承了java.lang.Thread类,可以同时启动多个线程进行监控,Java的多线程机制还是比较简单的,继承Thread类,重写run方法,然后new SubThread,再然后subThread.start就可以启动一个线程了。我们建立一个场景类来回顾一下这段历史,如代码清单22-6所示。
代码清单22-6 场景类
public class Client { public static void main(String args) throws InterruptedException { //定义出韩非子和李斯 LiSi liSi = new LiSi; HanFeiZi hanFeiZi = new HanFeiZi; //观察早餐 Watch watchBreakfast = new Watch(hanFeiZi,liSi,/"breakfast/"); //开始启动线程,监控 watchBreakfast.start; //观察娱乐情况 Watch watchFun = new Watch(hanFeiZi,liSi,/"fun/"); watchFun.start; //然后我们看看韩非子在干什么 Thread.sleep(1000); //主线程等待1秒后后再往下执行 hanFeiZi.haveBreakfast; //韩非子娱乐了 Thread.sleep(1000); hanFeiZi.haveFun; }}
运行结果如下所示:
韩非子:开始吃饭了...
李斯:观察到韩非子活动,开始向老板汇报了...
李斯:报告,秦老板!韩非子有活动了--->韩非子在吃饭
李斯:汇报完毕
韩非子:开始娱乐了...
李斯:观察到韩非子活动,开始向老板汇报了...
李斯:报告,秦老板!韩非子有活动了--->韩非子在娱乐
李斯:汇报完毕
结果出来,韩非子一吃早饭李斯就知道,韩非子一娱乐李斯也知道,非常正确!结果正确但并不表示你有成绩,我告诉你:你的成绩是0,甚至是负的!你有没有看到你的CPU飙升,Eclipse不响应状态?看到了?看到了你还不想为什么?!看看上面的程序,别的就不多说了,使用了一个死循环while(true)来做监听,要是用到项目中,你要多少硬件投入进来?你还让不让别人的程序运行了?!一台服务器就跑你这一个程序就完事!
错误也看到了,我们必须要修改,这个没法应用到项目中,而且这个程序根本就不是面向对象的程序,这完全是面向过程的,不改不行,怎么修改呢?我们来想,既然韩非子一吃饭李斯就知道了,那我们为什么不把李斯这个类聚集到韩非子那个类上呢?说改就改,立马动手,我们来看修改后的类图,如图22-3所示。
类图非常简单,就是在HanFeiZi类中引用了LiSi实例,看我们程序代码怎么修改,IHanFeiZi接口完全没有修改,可以参考代码清单22-1所示。我们来看实现类的修改,如代码清单22-7所示。
代码清单22-7 通过聚集方式的被观察者
public class HanFeiZi implements IHanFeiZi{ //把李斯声明出来 private ILiSi liSi =new LiSi; //韩非子要吃饭了 public void haveBreakfast{ System.out.println(/"韩非子:开始吃饭了.../"); //通知李斯 this.liSi.update(/"韩非子在吃饭/"); } //韩非子开始娱乐了 public void haveFun{ System.out.println(/"韩非子:开始娱乐了.../"); this.liSi.update(/"韩非子在娱乐/"); }}
图22-3 通过聚集方式监控
韩非子HanFeiZi实现类就把接口的两个方法实现就可以了,在每个方法中调用LiSi.update方法,完成李斯观察韩非子的职责,李斯的接口和实现类都没有任何改变,请参考代码清单22-3、22-4。我们再来看看Client程序的变更,如代码清单22-8所示。
代码清单22-8 通过聚集方式的场景类
public class Client { public static void main(String args) { //定义出韩非子 HanFeiZi hanFeiZi = new HanFeiZi; //然后我们看看韩非子在干什么 hanFeiZi.haveBreakfast; //韩非子娱乐了 hanFeiZi.haveFun; }}
李斯就不用在场景类中定义了,非常简单,运行结果相同,不再赘述。
我们思考一下,修改后的程序运行结果正确,效率也比较高,是不是应该乐呵乐呵了?大功告成了?稍等等,你想在战国争雄的时候,韩非子这么有名望、有实力的人,就只有秦国关心他吗?想想也不可能呀,确实有一大帮的各国类似于李斯这样的人在看着他,监视着他的一举一动,但是看看我们的程序,你在HanFeiZi这个类中定义:
private ILiSi liSi =new LiSi;
这样一来只有李斯才能观察到韩非子,这是不对的,也就是说韩非子的活动只通知了李斯一个人,这不可能;再者说了,李斯只观察韩非子的吃饭、娱乐吗?政治倾向不关心吗?思维倾向不关心吗?杀人放火不关心吗?也就说韩非子的一系列活动都要通知李斯,这可怎么办?要按照上面的例子,我们如何修改?这和开闭原则严重违背呀,我们的程序有问题,修改如图22-4所示。
图22-4 改进后的观察者和被观察者
我们把原有类图做了两个修改:
● 增加Observable
实现该接口的都是被观察者,那韩非子是被观察者,他当然也要实现该接口了,同时他还有与其他庸人相异的事要做,因此他还是要实现IHanFeizi接口。
● 修改ILiSI接口名称为Observer
接口名称修改了一下,这样显得更抽象化,所有实现该接口的都是观察者(类似李斯这样的)。
Observable是被观察者,就是类似韩非子这样的人,在Observable接口中有三个比较重要的方法,分别是addObserver增加观察者,deleteObserver删除观察者,notifyObservers通知所有的观察者,这是什么意思呢?我这里有一个信息,一个对象,我可以允许有多个对象来察看,你观察也成,我观察也成,只要是观察者就成,也就是说我的改变或动作执行,会通知其他的对象,看程序会更明白一点,先看Observable接口,如代码清单22-9所示。
代码清单22-9 被观察者接口
public interface Observable { //增加一个观察者 public void addObserver(Observer observer); //删除一个观察者 public void deleteObserver(Observer observer); //既然要观察,我发生改变了他也应该有所动作,通知观察者 public void notifyObservers(String context);}
这是一个通用的被观察者接口,所有的被观察者都可以实现这个接口。再来看韩非子的实现类,如代码清单22-10所示。
代码清单22-10 被观察者实现类
public class HanFeiZi implements Observable ,IHanFeiZi{ //定义个变长数组,存放所有的观察者 private ArrayList<Observer> observerList = new ArrayList<Observer>; //增加观察者 public void addObserver(Observer observer){ this.observerList.add(observer); } //删除观察者 public void deleteObserver(Observer observer){ this.observerList.remove(observer); } //通知所有的观察者 public void notifyObservers(String context){ for(Observer observer:observerList){ observer.update(context); } } //韩非子要吃饭了 public void haveBreakfast{ System.out.println(/"韩非子:开始吃饭了.../"); //通知所有的观察者 this.notifyObservers(/"韩非子在吃饭/"); } //韩非子开始娱乐了 public void haveFun{ System.out.println(/"韩非子:开始娱乐了.../"); this.notifyObservers(/"韩非子在娱乐/"); }}
观察者只是把原有的ILiSi接口修改了一个名字而已,如代码清单22-11所示。
代码清单22-11 观察者接口
public interface Observer { //一发现别人有动静,自己也要行动起来 public void update(String context);}
然后是三个很无耻的观察者,咱先看看真实的李斯,如代码清单22-12所示。
代码清单22-12 具体的观察者
public class LiSi implements Observer{ //首先李斯是个观察者,一旦韩非子有活动,他就知道,他就要向老板汇报 public void update(String str){ System.out.println(/"李斯:观察到韩非子活动,开始向老板汇报了.../"); this.reportToQinShiHuang(str); System.out.println(/"李斯:汇报完毕...n/"); } //汇报给秦始皇 private void reportToQinShiHuang(String reportContext){ System.out.println(/"李斯:报告,秦老板!韩非子有活动了-->/"+reportContext); }}
李斯是真有其人,以下两个观察者王斯和刘斯是杜撰出来的,如代码清单22-13所示。
代码清单22-13 杜撰的观察者
public class WangSi implements Observer{ //王斯,看到韩非子有活动 public void update(String str){ System.out.println(/"王斯:观察到韩非子活动,自己也开始活动了.../"); this.cry(str); System.out.println(/"王斯:哭死了...n/"); } //一看韩非子有活动,他就痛哭 private void cry(String context){ System.out.println(/"王斯:因为/"+context+/",--所以我悲伤呀!/"); }}public class LiuSi implements Observer{//刘斯,观察到韩非子活动后,自己也得做一些事 public void update(String str){ System.out.println(/"刘斯:观察到韩非子活动,开始动作了.../"); this.happy(str); System.out.println(/"刘斯:乐死了n/"); } //一看韩非子有变化,他就快乐 private void happy(String context){ System.out.println(/"刘斯:因为/" +context+/",--所以我快乐呀!/" ); }}
所有的历史人物都在场了,那我们来看看这场历史闹剧是如何演绎的,如代码清单22-14所示。
代码清单22-14 场景类
public class Client { public static void main(String args) { //三个观察者产生出来 Observer liSi = new LiSi; Observer wangSi = new WangSi; Observer liuSi = new LiuSi;//定义出韩非子 HanFeiZi hanFeiZi = new HanFeiZi; //我们后人根据历史,描述这个场景,有三个人在观察韩非子 hanFeiZi.addObserver(liSi); hanFeiZi.addObserver(wangSi); hanFeiZi.addObserver(liuSi);//然后这里我们看看韩非子在干什么 hanFeiZi.haveBreakfast; }}
运行结果如下所示:
韩非子:开始吃饭了...
李斯:观察到韩非子活动,开始向老板汇报了...
李斯:报告,秦老板!韩非子有活动了--->韩非子在吃饭
李斯:汇报完毕...
王斯:观察到韩非子活动,自己也开始活动了...
王斯:因为韩非子在吃饭——所以我悲伤呀!
王斯:哭死了...
刘斯:观察到韩非子活动,开始动作了...
刘斯:因为韩非子在吃饭——所以我快乐呀!
刘斯:乐死了
好了,结果也正确了,也符合开闭原则了,同时也实现类间解耦,想再加观察者?继续实现Observer接口就成了,这时候必须修改Client程序,因为你的业务都发生了变化。这就是观察者模式。