一:观察者模式的定义
--->观察者模式(Observer Pattern)也叫做发布订阅模式(Publish/subscribe),它是一个在项目中经常使用的模式
--->定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新
二:观察者模式的角色
● Subject被观察者
定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。
● Observer观察者
观察者接收到消息后,即进行update(更新方法)操作,对接收到的信息进行处理。
● ConcreteSubject具体的被观察者
定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。
● ConcreteObserver具体的观察者
每个观察在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑。
三:观察者模式的应用
观察模式的优点
● 观察者和被观察者之间是抽象耦合
如此设计,则不管是增加观察者还是被观察者都非常容易扩展,而且在Java中都已经实现的抽象层级的定义,在系统扩展方面更是得心应手。
● 建立一套触发机制
根据单一职责原则,每个类的职责是单一的,那么怎么把各个单一的职责串联成真实世界的复杂的逻辑关系呢?比如,我们去打猎,打死了一只母鹿,母鹿有三个幼崽,因失去了母鹿而饿死,尸体又被两只秃鹰争抢,因分配不均,秃鹰开始斗殴,然后羸弱的秃鹰死掉,生存下来的秃鹰,则因此扩大了地盘......这就是一个触发机制,形成了一个触发链。观察者模式可以完美地实现这里的链条形式。
观察模式的缺点
●观察者模式需要考虑一下开发效率和运行效率问题,一个被观察者,多个观察者,开发和调试就会比较复杂,而且在Java中消息的通知默认是顺序执行,一个观察者卡壳,会影响整体的执行效率。在这种情况下,一般考虑采用异步的方式。
●多级触发时的效率更是让人担忧,大家在设计时注意考虑
观察模式的使用场景
● 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。
● 事件多级触发场景。
● 跨系统的消息交换场景,如消息队列的处理机制。
观察模式的注意事项
● 广播链的问题
--->在一个观察者模式中最多出现一个对象既是观察者也是被观察者,也就是说消息最多转发一次(传递两次),这
还是比较好控制的。
● 异步处理问题
--->这个EJB是一个非常好的例子,被观察者发生动作了,观察者要做出回应,如果观察者比较多,而且处理时间比较长怎么办?那就用异步呗,异步处理就要考虑线程安全和队列的问题,这个大家有时间看看Message Queue,就会有更深的了解。
四:观察者模式的实践
● 观察者和被观察者之间的消息沟通
--->观察者中的update方法接受两个参数,一个是被观察者,一个是DTO(Data Transfer Object,据传输对象),DTO一般是一个纯洁的JavaBean,由被观察者生成,由观察者消费。
● 观察者响应方式
--->在一个观察者多个被观察者的情况下,性能就需要提到日程上来考虑了,为什么呢?如果观察者来不及响应,被观察者的执行时间是不是也会被
拉长?那现在的问题就是:观察者如何快速响应?有两个办法:一是采用多线程技术,甭管是被观察者启动线程还是观察者启动线程,都可以明显地提高系统性能,这也就是大家通常所说的异步架构;二是缓存技术,甭管你谁来,我已经准备了足够的资源给你了,我保证快速响应,这当然也是一种比较好方案,代价就是开发难度很大,而且压力测试要做的足够充分,这种方案也就是大家说的同步架构。
● 被观察者尽量自己做主
--->这是什么意思呢?被观察者的状态改变是否一定要通知观察者呢?不一定吧,在设计的时候要灵活考虑,否则会加重观察者的处理逻辑,一般是这样做的,对被观察者的业务逻辑doSomething方法实现重载,如增加一个doSomething(boolean isNotifyObs)方法,决定是否通知观察者,而不是在消息到达观察者时才判断是否要消费。
五:观察者模式的实例
● 文件系统
比如,在一个目录下新建立一个文件,这个动作会同时通知目录管理器增加该目录,并通知磁盘管理器减少1KB的空间,也就说“文件”是一个被观察者,“目录管理器”和“磁盘管理器”则是观察者。
● 猫鼠游戏
夜里猫叫一声,家里的老鼠撒腿就跑,同时也吵醒了熟睡的主人,这个场景中,“猫”就是被观察者,老鼠和人则是观察者。
● ATM取钱
比如你到ATM机器上取钱,多次输错密码,卡就会被ATM吞掉,吞卡动作发生的时候,会触发哪些事件呢?第一,摄像头连续快拍,第二,通知监控系统,吞卡发生;第三,初始化ATM机屏幕,返回最初状态。一般前两个动作都是通过观察者模式来完成的,后一个动作是异常来完成。
● 广播收音机
电台在广播,你可以打开一个收音机,或者两个收音机来收听,电台就是被观察者,收音机就是观察者。
六:观察者模式的案例
<一>李斯监视韩非子,利用监视线程的案例(不符合观察者模式的版型,但是一种非面向对象的思想)
【1】韩非子接口(被观察者接口)
1 package com.yeepay.sxf.template17; 2 /** 3 * 被观察者的接口 4 * 5 * 韩非子 6 * @author sxf 7 * 8 */ 9 public interface IHanFeiZi { 10 11 //韩非子要吃早饭 12 public void haveBreakFast(); 13 //韩非子要娱乐活动 14 public void haveFun(); 15 }
【2】韩非子实现类(被观察者实现类)
1 package com.yeepay.sxf.template17; 2 /** 3 * 被观察接口的实现类 4 * @author sxf 5 * 6 */ 7 public class HanFeiZi implements IHanFeiZi { 8 9 //韩非子是否在吃饭 10 private boolean isHavingBreakFast=false; 11 //韩非子是否在娱乐 12 private boolean isHavingFun=false; 13 14 @Override 15 public void haveBreakFast() { 16 System.out.println("HanFeiZi.haveBreakFast()韩非子在吃饭"); 17 this.setHavingBreakFast(true); 18 } 19 20 @Override 21 public void haveFun() { 22 System.out.println("HanFeiZi.haveFun()韩非在在娱乐"); 23 this.setHavingFun(true); 24 System.out.println("HanFeiZi.haveFun(sssssssssssssss)"); 25 } 26 27 public synchronized boolean isHavingBreakFast() { 28 return isHavingBreakFast; 29 } 30 31 public synchronized void setHavingBreakFast(boolean isHavingBreakFast) { 32 this.isHavingBreakFast = isHavingBreakFast; 33 } 34 35 public boolean isHavingFun() { 36 return isHavingFun; 37 } 38 39 public void setHavingFun(boolean isHavingFun) { 40 this.isHavingFun = isHavingFun; 41 } 42 43 44 45 46 }
【3】李斯接口(观察者接口)
1 package com.yeepay.sxf.template17; 2 /** 3 * 观察者接口 4 * 李斯 5 * @author sxf 6 * 7 */ 8 public interface ILiSi { 9 //一发现别人有动静,就要行动起来 10 public void update(StringBuffer context); 11 }
【4】李斯实现(观察者实现)
1 package com.yeepay.sxf.template17; 2 /** 3 * 观察者 4 * 李斯监控韩非子 5 * @author sxf 6 * 7 */ 8 public class LiSi implements ILiSi { 9 10 @Override 11 public void update(StringBuffer context) { 12 System.out.println("LiSi.update()李斯发现韩非子有活动"); 13 System.out.println("LiSi.update()李斯行动"+context.toString()); 14 System.out.println("LiSi.update()李斯回报秦始皇完毕"); 15 } 16 17 18 }
【5】监视线程
1 package com.yeepay.sxf.template17; 2 /** 3 * 监视线程 4 * @author sxf 5 * 6 */ 7 public class Spy extends Thread{ 8 //韩非子 9 private HanFeiZi hanfeizi; 10 //李斯 11 private LiSi lisi; 12 //监控内容 13 private String type; 14 15 //构造函数 16 public Spy(HanFeiZi hanfeizi, LiSi lisi, String type) { 17 super(); 18 this.hanfeizi = hanfeizi; 19 this.lisi = lisi; 20 this.type = type; 21 } 22 23 24 @Override 25 public void run() { 26 while(true){ 27 if(this.type.equals("breakfast")){ 28 if(this.hanfeizi.isHavingBreakFast()){ 29 StringBuffer buffer=new StringBuffer().append("韩非子在吃饭"); 30 this.lisi.update(buffer); 31 this.hanfeizi.setHavingBreakFast(false); 32 } 33 }else if(this.type.equals("fun")){ 34 if(this.hanfeizi.isHavingFun()){ 35 StringBuffer buffer=new StringBuffer(); 36 buffer.append("韩非子在娱乐"); 37 this.lisi.update(buffer); 38 this.hanfeizi.setHavingFun(false); 39 } 40 } 41 } 42 } 43 44 45 }
<二>猫抓老鼠案例(符合观察者模式的标准版型)
【1】老鼠接口(被观察者自身行为接口)
1 package com.yeepay.sxf.template17; 2 /** 3 * 老鼠的接口 4 * 5 * 被观察者自身行为的接口 6 * @author sxf 7 * 8 */ 9 public interface IMouse { 10 //老鼠晚上吃饭 11 public void eatFoodByNin(); 12 }
【2】观察接口(实现该接口的类都是被观察者)
1 package com.yeepay.sxf.template17; 2 /** 3 * 4 * 被观察者要实现这个接口 5 * @author sxf 6 * 7 */ 8 public interface IObservable { 9 10 //增加一个观察者 11 public void addObserver(ICat cat); 12 //删除一个观察者 13 public void deleteObserver(ICat cat); 14 //既然要观察,通知观察者 15 public void notifyObserver(String notify); 16 }
【3】猫接口(观察者接口)
1 package com.yeepay.sxf.template17; 2 /** 3 * 观察者接口 4 * 猫 5 * @author sxf 6 * 7 */ 8 public interface ICat { 9 10 public void catchMouse(String content); 11 }
【4】猫1实现(观察者实现接口)
1 package com.yeepay.sxf.template17; 2 3 /** 4 * 黑猫观察者实现 5 * @author sxf 6 * 7 */ 8 public class BlackCat implements ICat{ 9 10 @Override 11 public void catchMouse(String content) { 12 System.out.println("Cat.catchMouse(黑猫抓老鼠)"+content); 13 } 14 15 16 17 }
【5】猫2实现(观察者实现接口)
1 package com.yeepay.sxf.template17; 2 /** 3 * 虹猫观察者实现 4 * @author sxf 5 * 6 */ 7 public class RedCat implements ICat { 8 9 @Override 10 public void catchMouse(String content) { 11 System.out.println("RedCat.catchMouse(虹猫抓老鼠)"+content); 12 } 13 14 15 }
【6】老鼠实现(被观察者实现接口)
1 package com.yeepay.sxf.template17; 2 3 import java.util.ArrayList; 4 /** 5 * 老鼠,被观察者 6 * @author sxf 7 * 8 */ 9 public class Mouse implements IMouse,IObservable { 10 11 //存放观察者 12 private ArrayList<ICat> cat=new ArrayList<ICat>(); 13 14 @Override 15 public void addObserver(ICat cat) { 16 this.cat.add(cat); 17 } 18 19 @Override 20 public void deleteObserver(ICat cat) { 21 this.cat.remove(cat); 22 } 23 24 @Override 25 public void notifyObserver(String notify) { 26 for(ICat catd:cat){ 27 catd.catchMouse(notify); 28 } 29 } 30 31 @Override 32 public void eatFoodByNin() { 33 System.out.println("Mouse.eatFoodByNin(老鼠出来吃食物)"); 34 //通知观察者 35 this.notifyObserver("a老鼠出来了"); 36 } 37 38 39 }
【7】客户端测试两个案例
1 package com.yeepay.sxf.template17; 2 /** 3 * 客户端测试类 4 * @author sxf 5 * 6 */ 7 public class ClientTest { 8 public static void main(String[] args) { 9 //test01(); 10 test02(); 11 } 12 13 /** 14 * 这个观察者模式不符合面向对象,而且把cpu搞的很疲惫 15 */ 16 public static void test01(){ 17 //定义出李斯,和韩非子 18 LiSi liSi=new LiSi(); 19 HanFeiZi hanFeiZi=new HanFeiZi(); 20 //观察早餐 21 Spy sd=new Spy(hanFeiZi, liSi, "breakfast"); 22 sd.start(); 23 //观察娱乐 24 Spy se=new Spy(hanFeiZi, liSi, "fun"); 25 se.start(); 26 27 //当前线程休息10000秒 28 try { 29 Thread.sleep(10000L); 30 } catch (InterruptedException e) { 31 // TODO Auto-generated catch block 32 e.printStackTrace(); 33 } 34 //韩非子吃早饭 35 hanFeiZi.haveBreakFast(); 36 try { 37 Thread.sleep(10000L); 38 } catch (InterruptedException e) { 39 // TODO Auto-generated catch block 40 e.printStackTrace(); 41 } 42 //韩非子娱乐 43 hanFeiZi.haveFun(); 44 } 45 46 /** 47 * 真正的观察者模式的标准版,但存在效率问题,可以用异步思想改善 48 */ 49 public static void test02(){ 50 //声明两个观察者 51 ICat cat1=new BlackCat(); 52 ICat cat2=new RedCat(); 53 //声明被观察者 54 IMouse mouse=new Mouse(); 55 IObservable iObservable=(IObservable) mouse; 56 //给被观察者加入观察者 57 iObservable.addObserver(cat1); 58 iObservable.addObserver(cat2); 59 //老鼠要吃食物 60 mouse.eatFoodByNin(); 61 } 62 }