最近在学习javaAPI,看到了Observer和Observable,为了加深理解,写了个简单的例子。
Observer,从字面意思来理解,就是对象服务,声明了一个方法:
void update(Observable o, Object arg);
这个方法,在Observable的类中用setchanged()和notifyObservers(Object)才能被调用,而不需要我们自己编码来调用,实现了“自动监视”的效果。
Observable是一个Class,使用void addObserver(Observer o)方法可以为自己添加观察者,一个Observable可以注册多个Observer,在Observable中是用一个Vector来存储的。
Observable共有两个属性,除去存储Observable的Vector属性,还有一个boolean型的changed属性,来记录是否发生改变。
Observer和Observable的典型使用如下:
public class SomeThing extends Observable{ //被观察对象 public void someThingHappen(){ this.setChanged(); //设置改变状态 this.notifyObservers(); //通知所有已注册的观察者 } } public class MyObserver implements Observer { //观察者 public void update(Observable o, Object arg) { System.out.println("in update"); } } public class Test { public static void main(String args[]){ SomeThing st=new SomeThing();//实例化被观察对象 MyObserver mo=new MyObserver ();//实例化观察者 st.addObserver(mo);//为被观察对象 注册一个观察者 st.someThingHappen();//被观察者执行操作 } }
这样会输出“in update”。有点神奇吧。我们来看看Observable的源码。
private boolean changed = false; //是否修改 private Vector obs; //保存观察者的数组 protected synchronized void setChanged() { //设置修改状态为true changed = true; } public synchronized void addObserver(Observer o) { //添加观察者 if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } public void notifyObservers() { //通知观察者 notifyObservers(null); } public void notifyObservers(Object arg) { //通知观察者 Object[] arrLocal; synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); clearChanged(); } for (int i = arrLocal.length-1; i>=0; i--) //注意这里的循环次序 ((Observer)arrLocal[i]).update(this, arg); } //....其他代码
从这里不难看出,为什么我们在Observable中必须先调用setChanged() 方法,再调用notifyObservers()方法了。
Observable类有两个私有变量。一个boolean型的标志位,setChange()将它设为真,只有它为真时,notifyObservers方法才会调用Observer的update方法,clearChange()设标志位为假,hasChange返回当前标志位的值。另一个是一个Vector,保存着一个所有要通知的Observer列表,addObserver添加Observer到列表,deleteObserver从列表中删除指定Observer,deleteObservers清空列表,countObservers返回列表中Observer的数目,在Observer对象销毁前一定要用deleteObserver将其从列表中删除,不然因为还存在对象引用的关系,Observer对象不会被垃圾收集,造成内存泄漏,并且已死的Observer仍会被通知到,有可能造成意料外的错误,而且随着列表越来越大,notifyObservers操作也会越来越慢。Observable的所有方法都是同步的,保证了在一个线程对其标志位、列表进行操作时,不会有其它线程也在操作它。Observable的notifyObservers(Object obj)形式可以再调用update时将参数传进去。
通知顺序通常时越晚加入列表的越先通知。update会被依次调用,由于一个update返回后下一个update才被调用,这样当update中有大量操作时,最好将其中的工作拿到另一个线程或者Observer本身同时也是一个Thread类,Observer先挂起,在update中被唤醒,这样会有一个隐患,Observer线程还没来得及挂起,update就被调用了,通知消息就这样被错过了,一种解决办法是在Observer中实现一个同步的队列结构,并有一个类来封装参数,update实现一个参数类的对象把接收到的通知消息的参数封装在里面,然后把其加进队列,run方法从队列中移除参数对象,并进行处理,这保证了没有通知信息被丢失。
在设计模式中,这一个接口一个Class构成的这种模式叫观察者模式。
为了加深记忆,写一个小例子娱乐一下:
public class Media implements Observer {//我们的观察者 String name; String actionName; public Media(String name,String actionName) { this.name = name;//观察者名称 this.actionName=actionName;//观察者手段 } public Media(String name,String actionName,Observable ob) { this.name = name; this.actionName=actionName; ob.addObserver(this); } public void update(Observable o, Object arg) { SomeThing st=(SomeThing)o; System.out.println(name+actionName+":"+st.name+arg); } } public class SomeThing extends Observable{//SomeThing,就是我们现在关注的一些东西 String name;//名称 public SomeThing(String name) { this.name = name; } public void someThingHappen(String action){ this.setChanged(); this.notifyObservers(action); } } public class Test { public static void main(String args[]){ SomeThing xilige=new SomeThing("犀利哥"); Media someBody=new Media("某网友","上传",xilige); Media net=new Media("网络","疯狂点击",xilige); Media cctv=new Media("CCTV","关注",xilige); xilige.someThingHappen("抽烟啦!"); SomeThing ss=new SomeThing("ShouShou"); ss.addObserver(cctv); ss.addObserver(net); ss.addObserver(someBody); ss.someThingHappen("出新专辑!"); } }
打印结果:
CCTV关注:犀利哥抽烟啦! 网络疯狂点击:犀利哥抽烟啦! 某网友上传:犀利哥抽烟啦! 某网友上传:ShouShou出新专辑! 网络疯狂点击:ShouShou出新专辑! CCTV关注:ShouShou出新专辑!
大家注意打印的次序,和注册观察者的次序是刚好相反的。
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
观察者模式的优点:
1.在被观察者和观察者之间建立一个抽象的松耦合;
2.支持广播通信。被观察者会向所有登记过的观察者发出通知。
观察者模式也有如下缺点:
1.如果一个被观察者对象有很多直接或间接的观察者的话,将所有的观察者都通知到会花费很多时间;
2.如果在被观察者之间有循环依赖的话,被观察者会触发它们进行循环调用,导致系统崩溃,在使用此模式时应特别注意这一点;
3.如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的;
4.虽然观察者模式可以使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制让观察者知道所观察的对象是怎么发生变化的。
在《head First设计模式》一书中有详细的分析,设计模式在于理解,如果JDK提供的这个Observable不能满足或者限制了你的实现,你完全可以自己再实现一个。
另外大家可以注意到Observable的内聚是非常好的,体现了JDK编码的严格和规范,值得我们学习!
本来是汉字,无奈shoushou的汉字不让发...