JDK自带的观察者模式
1、概述
观察者模式又称为发布/订阅(Publish/Subscribe)模式
observer模式简介
observer模型,又被称作listener模式。这里统一用observer来称呼。
设计模式里对其结构的描述:
意图:
一个对象变化,通知其他被依赖的对象。
适用性:
两种对象subject与observers,相对相互独立,有单向依赖关系,observer依赖subject。
不知道具体有多少observer对象被通知。
采用这种方式,主要针对接口编程,抽象。
有可能一对多,也有可能是多对一,多对多。
观察者设计模式涉及到两种角色:主题(Subject、被观察者、通知者、Publish,发布者)和观察者(Observer)
(1)Subject模块
Subjec模块有3个主要操作
addObserver():注册添加观察者(申请订阅)
deleteObserver():删除观察者(取消订阅)
notifyObserver():主题状态发生变化时通知所有的观察者对象
(2)Oserver模块
Oserver模块有1个核心操作update(),当主题Subject状态改变时,将调用每个观察者的update()方法,更新通知。
两种实现:推与拉
推模式的结构:
- 推模式
Subject主动向Observer推送消息,不管对方是否需要,推送的信息通常是目标对象的全部或部分数据,相当于广播通信。 - 拉模型
Subject在通知Observer时只传递少量信息,如果观察者需要更具体的信息,再由Observer主动去拉取数据。这样的模型实现中会把Subject自身通过update方法传入到Observer。
一、介绍一下JDK自带的观察者模式
subject -> java.util.Observable(类)
void addObserver(Observer o) 如果观察者与集合中已有的观察者不同,则向对象的观察者集中添加此观察者。 protected void clearChanged() 指示对象不再改变,或者它已对其所有的观察者通知了最近的改变,所以 hasChanged 方法将返回 false。 int countObservers() 返回 Observable 对象的观察者数目。 void deleteObserver(Observer o) 从对象的观察者集合中删除某个观察者。 void deleteObservers() 清除观察者列表,使此对象不再有任何观察者。 boolean hasChanged() 测试对象是否改变。 void notifyObservers() 如果 hasChanged 方法指示对象已改变,则通知其所有观察者,并调用 clearChanged 方法来指示此对象不再改变。 void notifyObservers(Object arg) 如果 hasChanged 方法指示对象已改变,则通知其所有观察者,并调用 clearChanged 方法来指示此对象不再改变。 protected void setChanged() 标记此 Observable 对象为已改变的对象;现在 hasChanged 方法将返回 true。
observer -> java.util.Observer(接口)
void update(Observable o, Object arg) 只要改变了observable对象(主题角色)就调用此方法。
需要特别说明下setChanged()、clearChanged()和hasChanged()这3个方法:
参见上面Observable类的notifyObservers(Object arg)方法,hasChanged()为true才会通知观察者数据有变化,并且在通知完成之后调用clearChanged()修改hasChanged()为false,所以当主题数据改变时,需要先调用setChanged()方法使hasChanged为true
Observable的伪码应该如下所示:
public class Observable { private boolean flag = false; private List<Observer> list = new ArrayList<Observer>(); public boolean hasChanged(){ return flag; } protected void setChanged(){ flag = true; } protected void clearChanged(){ flag = false; } public void addObserver(Observer o){ if(!list.contain(o)){ list.add(o); } } public void deleteObserver(Observer o){ if(list.contain(o)){ list.remove(o); } } public void notifyObservers(Object arg){ if(hasChanged()){ if(null != list && list.size > 0){ for(Observer o : list){ o.update(this, arg); } } } clearChanged(); } }
public class SpecialRepoter extends Observable { public void getNewNews(String msg){ if(msg.length()>100){ this.setChanged(); } this.notifyObservers(msg); } }
通过这段伪代码可以很清楚的了解这3个方法了,这3个方法使我们对何时进行push进行精确控制,在我们不想推送的时候,不调用setChanged()方法即可。
使用jdk自带的观察者模式重写一下以前的记者和报社例子:
想要进行通知,则必须调用Observable类的setChanged方法,但是Observable的setChanged方法为protected,故只能使用继承来实现自己的主题对象。
主题继承自Observable类,观察者实现Observer接口,并且主题需要的方法已经在Observable类中实现了
观察者:
package com.dxz.observer3; import java.util.Observable; import java.util.Observer; //新华社 public class XinhuaNewspaperObserver implements Observer { private String newspaperName = "新华社"; @Override public void update(Observable o, Object arg) { System.out.println("'" + newspaperName + "'读取推送信息:" + arg); System.out.println("'" + newspaperName + "'读取拉取信息:" + ((RepoterObservable)o).getNews()); } } package com.dxz.observer3; import java.util.Observable; import java.util.Observer; //人民日报 public class PeopleDailyNewspaperObserver implements Observer { private String newspaperName = "人民日报"; @Override public void update(Observable o, Object arg) { System.out.println("'" + newspaperName + "'读取推送信息:" + arg); System.out.println("'" + newspaperName + "'读取拉取信息:" + ((RepoterObservable)o).getNews()); } }
主题:
package com.dxz.observer3; import java.util.Observable; public class RepoterObservable extends Observable { private String news; public String getNews() { return news; } public void setNews(String news) { this.news = news; setChanged(); // 必须调用这个方法来通知Observer状态发生了改变 notifyObservers("you"); } }
入口调用类:
package com.dxz.observer3; public class Client { public static void main(String[] args) { RepoterObservable subject = new RepoterObservable(); PeopleDailyNewspaperObserver observer1 = new PeopleDailyNewspaperObserver(); XinhuaNewspaperObserver observer2 = new XinhuaNewspaperObserver(); subject.addObserver(observer1); subject.addObserver(observer2); subject.setNews("日韩贸易战最新消息,韩方一再要求撤回贸易管制 日方无松动迹象..."); } }
结果:
'新华社'读取推送信息:you '新华社'读取拉取信息:日韩贸易战最新消息,韩方一再要求撤回贸易管制 日方无松动迹象... '人民日报'读取推送信息:you '人民日报'读取拉取信息:日韩贸易战最新消息,韩方一再要求撤回贸易管制 日方无松动迹象...
PUSH和PULL模式
- notifyObserver(Object arg):带参数的notifyObservers(Object arg):这个参数Object arg 其实就是 Observer接口中的update(Observable o, Object arg)方法中的第二个参数 。就是通知观察者所改变的数据对象。简单理解就是由主题主动的PUSH需要改变的数据对象给观察者。
- notifyObserver():不带参数的方法,传递一个null数据对象给观察者,需要观察者主动到主题pull数据。
二:jdk实现观察者模式的源码
2.1:被观察者源码
public class Observable { private boolean changed = false;//是否被改变 private Vector obs;//维持一个Vector集合用来装观察者对象,注意Vector是线程安全的 public Observable() {//构造方法 obs = new Vector();//新创建一个装有观察者的集合 } public synchronized void addObserver(Observer o) {//添加观察者 if (o == null)//如果是null throw new NullPointerException();//抛出空指针异常 if (!obs.contains(o)) { //如果不包含该观察者对象 obs.addElement(o);//把观察者对象添加入集合 } } public synchronized void deleteObserver(Observer o) {//删除观察者对象 obs.removeElement(o);//调用vector的removeElement方法把观察者移除出去 } public void notifyObservers() {//通知所有的观察者更新 notifyObservers(null);//调用notifyObservers方法,传入一个null参数 } public void notifyObservers(Object arg) {//通知所有的观察者 Object[] arrLocal;//声明一个方法内部对象数组 synchronized (this) {//给当前的类实例上锁 if (!changed)//如果没有发生变化 return;//直接退出方法 arrLocal = obs.toArray();//将集合转化为数组赋给声明的对象数组 clearChanged();//调用clearChanged方法清除变化 } for (int i = arrLocal.length-1; i>=0; i--)//倒序遍历整个数组 ((Observer)arrLocal[i]).update(this, arg);//调用观察者的update方法 } public synchronized void deleteObservers() { //删除所有观察者 obs.removeAllElements(); } protected synchronized void setChanged() { changed = true; } protected synchronized void clearChanged() { //清除变化 changed = false; } public synchronized boolean hasChanged() { return changed; } public synchronized int countObservers() {//计数观察者 return obs.size();//返回观察者的数量 } }
其中可以看出被观察者内部维持着一个线程安全的vector,用来存放观察者,观察者可以有好多个,他们之间是一对多的关。被观察者可以对观察者进行注册、删除、通知、计数等操作。可以看出每个方法上面都添加了一个synchronized,并且基本上都是方法级别的,锁住当前的方法,这是考虑到线程安全问题。可能有很多个观察者同时对一个被观察者进行操作,防止线程之间出现的数据脏读等问题都采用了加锁的手段。
重点分析一下notifyObservers()方法:
public void notifyObservers(Object arg) {//通知所有的观察者 Object[] arrLocal;//声明一个方法内部对象数组 synchronized (this) {//给当前的类实例上锁 if (!changed)//如果没有发生变化 return;//直接退出方法 arrLocal = obs.toArray();//将集合转化为数组赋给声明的对象数组 clearChanged();//调用clearChanged方法清除变化 } for (int i = arrLocal.length-1; i>=0; i--)//倒序遍历整个数组 ((Observer)arrLocal[i]).update(this, arg);//调用观察者的update方法 }
这里采用了内部声明的对象数组的方法,在调用update方法的时候并没有上锁,因为vector是全局的,为什么要这么做呢?不会存在线程安全隐患吗?这里这个对象数组的好处就直接体现出来了,先看看这个方法的注释:
We don't want the Observer doing callbacks into arbitrary code while holding its own Monitor. The code where we extract each Observable from the Vector and store the state of the Observer needs synchronization, but notifying observers does not (should not). The worst result of any potential race-condition here is that: 1) a newly-added Observer will miss a notification in progress 2) a recently unregistered Observer will be wrongly notified when it doesn't care
综合注释来讲,这个方法注意点有以下5个:
①:如果该方法加锁,假如一个被观察者的观察者数量比较多的时候,那么进行依次遍历通知将会是一个非常耗时耗资源的操作。因为其它线程都将在这里阻塞,无法进入,只能等它完了 才能获取锁再进行通知。
②:arrLocal是方法内部变量,我们都知道内部变量存放在栈中,是私有的,也就是其它线程并不会影响这里。在进行集合转数组这块,是同步加锁的。其他线程不会访问到synchronized方法
③:arrLocal相当于对所有的观察者进行了一个缓存,如果不加缓存会怎样?不加缓存的话,如果一个线程添加观察者,一个线程删除观察者,那么vector将会出现ConcurrenModifyException,修改并发异常,并且很有可能出现空指针异常。
④:利用集合转换数组的方法可以把观察者保存起来,进行独立的操作,这部分数据的更新是完全不依赖于外部变化的。有这样一个问题,假如现在一个线程正在进行notifyObservers方法调用,而其他线程调用了deleteObserver()方法,这时候被删除的observer还会接受到通知吗?答案是不会,这也是这种缓存机制的缺点。一方面新添加的观察者不会立刻得到通知(除非在添加完立刻有线程执行了obs.toArray()方法).另一方面,新删除的观察者可能不会立刻停止通知,这也是设计上的不可避免的问题,期待后期jdk有解决的好方案。
⑤:jdk的设计者特意选择了vector这个线程安全的集合,而不是ArrayList、LinkedList等,在进行obs.toArray()的时候,其它线程无法访问obs,这个时候obs是不受影响的。
2.2:观察者源码
package java.util; public interface Observer { void update(Observable var1, Object var2); }
相比之下,观察者就简单多了,本身是一个接口,就一个update方法进行数据的更新,传入的参数为被观察者和需要传递的参数。
三、总结
3.1、使用jdk自带的观察者模式的缺点:
1,Observable是一个类,而不是一个接口,导致Observable类的扩展性不高,不如自己实现的观察者模式灵活
2,Observable将某些方法保护了起来(setChanged()和clearChanged()为protected),这意味着除非继承自Observable,否则将有关键的方法不能调用。导致无法通过组合的方式使其它类获得Observable类的功能
3.2、观察者模式的优势和不足
1、观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体观察者列表,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。
2:被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。如果被观察者和观察者都被扔到一起,那么这个对象必然跨越抽象化和具体化层次。2、观察者模式支持广播通讯。被观察者会向所有的登记过的观察者发出通知
观察者模式有下面的缺点:
3:如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间,这点在上面的notifyObservers方法中已经提到
4:如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式是要特别注意这一点。
5:如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。
6::虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的
参考:https://blog.csdn.net/a19881029/article/details/8975962