22.1 韩非子身边的卧底是谁派来的
《孙子兵法》有云:“知彼知己,百战不殆;不知彼而知己,一胜一负;不知彼,不知己,每战必殆”,那怎么才能知己知彼呢?知己是很容易的,自己的军队嘛,很容易知根知底,那怎么知彼呢?安插间谍是个好办法,这是古今中外屡试不爽的方法,我们今天就来讲一个间谍的故事。
韩非子大家都应该记得吧,法家的代表人物,主张建立法制社会,实施重罚制度,真是非常有远见呀!看看现在社会在呼吁什么,建立法制化的社会,这在2000多年前就已经提出了。大家可能还不知道,法家还有一个非常重要的代表人物——李斯。李斯是秦国的丞相, 最终被残忍车裂的那位,李斯和韩非子都是荀子的学生,李斯是师兄,韩非子是师弟,若干年后,李斯成为最强诸侯国秦国的上尉,致力于统一全国,于是安插了间谍到各个国家的重要人物的身边,以获取必要的信息,韩非子作为韩国的重量级人物,身边自然有不少间谍, 韩非子做的事,李斯都了如指掌,那可是相隔千里!怎么做到的呢?间谍呀!我们先通过程序把这个过程展现一下,看看李斯是怎么监控韩非子的,先看两个主角的类图,如图22-1所示。
图22-1 监控者和被监控者
仅有这两个对象还是不够的,我们要解决的是李斯是怎么监控韩非子的?创建一个后台线程一直处于运行状态,一旦发现韩非子在吃饭或者娱乐就触发事件?这是真实世界的翻版,安排了一个间谍,观察韩非子的生活起居,并上报给李斯,然后李斯再触发update事件,类图继续扩充,如图22-2所示。
图22-2 通过后台线程监控
这个类图应该是程序员最容易想到的,你要监控,我就给你找个间谍角色(Spy类), 我们来看程序的实现,先看我们的主角韩非子的接口(类似于韩非子这样的人,被观察者角色),如代码清单22-1所示。
代码清单22-1 被观察者接口
1 2 3 4 5 6
| public interface IHanFeiZi { public void haveBreakfast(); public void haveFun(); }
|
对接口进行扩充,增加了两个状态isHavingBreakfast(是否在吃早饭)和isHavingFun(是否在娱乐),以方便Spy进行监控,如代码清单22-2所示。
代码清单22-2 具体的被观察者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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; } 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 抽象观察者
1 2 3 4
| public interface ILiSi { public void update(String context); }
|
李斯这类人比较简单,一发现自己观察的对象发生了变化,比如吃饭、娱乐,自己立刻也要行动起来,怎么行动呢?如代码清单22-4所示。
代码清单22-4 韩非子
1 2 3 4 5 6 7 8 9 10 11 12
| 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 间谍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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 场景类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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); hanFeiZi.haveBreakfast(); Thread.sleep(1000); hanFeiZi.haveFun(); } }
|
运行结果如下所示:
1 2 3 4 5 6 7 8
| 韩非子:开始吃饭了... 李斯:观察到韩非子活动,开始向老板汇报了... 李斯:报告,秦老板!韩非子有活动了--->韩非子在吃饭 李斯:汇报完毕 韩非子:开始娱乐了... 李斯:观察到韩非子活动,开始向老板汇报了... 李斯:报告,秦老板!韩非子有活动了--->韩非子在娱乐 李斯:汇报完毕
|
结果出来,韩非子一吃早饭李斯就知道,韩非子一娱乐李斯也知道,非常正确!结果正确但并不表示你有成绩,我告诉你:你的成绩是0,甚至是负的!你有没有看到你的CPU飙升,Eclipse不响应状态?看到了?看到了你还不想为什么?!看看上面的程序,别的就不多说了,使用了一个死循环while(true)来做监听,要是用到项目中,你要多少硬件投入进来? 你还让不让别人的程序运行了?!一台服务器就跑你这一个程序就完事!
错误也看到了,我们必须要修改,这个没法应用到项目中,而且这个程序根本就不是面向对象的程序,这完全是面向过程的,不改不行,怎么修改呢?我们来想,既然韩非子一吃饭李斯就知道了,那我们为什么不把李斯这个类聚集到韩非子那个类上呢?说改就改,立马动手,我们来看修改后的类图,如图22-3所示。
类图非常简单,就是在HanFeiZi类中引用了LiSi实例,看我们程序代码怎么修改,IHanFeiZi接口完全没有修改,可以参考代码清单22-1所示。我们来看实现类的修改,如代码清单22-7所示。
代码清单22-7 通过聚集方式的被观察者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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 通过聚集方式的场景类
1 2 3 4 5 6 7 8 9 10
| public class Client { public static void main(String[] args) { HanFeiZi hanFeiZi = new HanFeiZi(); hanFeiZi.haveBreakfast(); hanFeiZi.haveFun(); } }
|
李斯就不用在场景类中定义了,非常简单,运行结果相同,不再赘述。
我们思考一下,修改后的程序运行结果正确,效率也比较高,是不是应该乐呵乐呵了? 大功告成了?稍等等,你想在战国争雄的时候,韩非子这么有名望、有实力的人,就只有秦国关心他吗?想想也不可能呀,确实有一大帮的各国类似于李斯这样的人在看着他,监视着他的一举一动,但是看看我们的程序,你在HanFeiZi这个类中定义:
1
| private ILiSi liSi =new LiSi();
|
这样一来只有李斯才能观察到韩非子,这是不对的,也就是说韩非子的活动只通知了李斯一个人,这不可能;再者说了,李斯只观察韩非子的吃饭、娱乐吗?政治倾向不关心吗? 思维倾向不关心吗?杀人放火不关心吗?也就说韩非子的一系列活动都要通知李斯,这可怎么办?要按照上面的例子,我们如何修改?这和开闭原则严重违背呀,我们的程序有问题, 修改如图22-4所示。
图22-4 改进后的观察者和被观察者
我们把原有类图做了两个修改:
实现该接口的都是被观察者,那韩非子是被观察者,他当然也要实现该接口了,同时他还有与其他庸人相异的事要做,因此他还是要实现IHanFeizi接口。
接口名称修改了一下,这样显得更抽象化,所有实现该接口的都是观察者(类似李斯这样的)。
Observable是被观察者,就是类似韩非子这样的人,在Observable接口中有三个比较重要的方法,分别是addObserver增加观察者,deleteObserver删除观察者,notifyObservers通知所有的观察者,这是什么意思呢?我这里有一个信息,一个对象,我可以允许有多个对象来察看,你观察也成,我观察也成,只要是观察者就成,也就是说我的改变或动作执行,会通知其他的对象,看程序会更明白一点,先看Observable接口,如代码清单22-9所示。
代码清单22-9 被观察者接口
1 2 3 4 5 6 7 8
| public interface Observable { public void addObserver(Observer observer); public void deleteObserver(Observer observer); public void notifyObservers(String context); }
|
这是一个通用的被观察者接口,所有的被观察者都可以实现这个接口。再来看韩非子的实现类,如代码清单22-10所示。
代码清单22-10 被观察者实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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 观察者接口
1 2 3 4
| public interface Observer { public void update(String context); }
|
然后是三个很无耻的观察者,咱先看看真实的李斯,如代码清单22-12所示。
代码清单22-12 具体的观察者
1 2 3 4 5 6 7 8 9 10 11 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 杜撰的观察者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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 场景类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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(); } }
|
运行结果如下所示:
1 2 3 4 5 6 7 8 9 10
| 韩非子:开始吃饭了... 李斯:观察到韩非子活动,开始向老板汇报了... 李斯:报告,秦老板!韩非子有活动了--->韩非子在吃饭 李斯:汇报完毕... 王斯:观察到韩非子活动,自己也开始活动了... 王斯:因为韩非子在吃饭——所以我悲伤呀! 王斯:哭死了... 刘斯:观察到韩非子活动,开始动作了... 刘斯:因为韩非子在吃饭——所以我快乐呀! 刘斯:乐死了
|
好了,结果也正确了,也符合开闭原则了,同时也实现类间解耦,想再加观察者?继续实现Observer接口就成了,这时候必须修改Client程序,因为你的业务都发生了变化。这就是观察者模式。