观察者模式之二:JDK自带的观察者模式

 《观察者模式之一:java实现观察者模式

 《观察者模式之二:JDK自带的观察者模式

一、介绍一下JDK自带的观察者模式

subject -> java.util.Observable(类)

void addObserver(Observer o) 
          如果观察者与集合中已有的观察者不同,则向对象的观察者集中添加此观察者。 
protected  void clearChanged() 
          指示对象不再改变,或者它已对其所有的观察者通知了最近的改变,所以 hasChanged 方法将返回 falseint 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类中实现了

public class SpecialRepoter extends Observable {
    public void getNewNews(String msg){
        this.setChanged();
        this.notifyObservers(msg);
    }
}

public class NewspaperOffice {
}

public class PeopleDaily extends NewspaperOffice implements Observer {
    private Observable observable;
    
    public PeopleDaily(SpecialRepoter repoter){
        this.observable = repoter;
        repoter.addObserver(this);
    }
 
    public void update(Observable o, Object arg) {
        if(o instanceof SpecialRepoter){
            System.out.println("People's Daily brings you the latest news!");  
        }
    }
    
    public void remove(){
        observable.deleteObserver(this);
    }
}

public class XinhuaDaily extends NewspaperOffice implements Observer{
    private Observable observable;
    
    public XinhuaDaily(SpecialRepoter repoter){
        this.observable = repoter;
        repoter.addObserver(this);
    }
 
    public void update(Observable o, Object arg) {
        if(o instanceof SpecialRepoter){
            System.out.println("Xinhua Daily brings you the latest news!");
        }
    }
}


public class GlobalTimes extends NewspaperOffice implements Observer{
    private Observable observable;
    
    public GlobalTimes(SpecialRepoter repoter){
        this.observable = repoter;
        repoter.addObserver(this);
    }
 
    public void update(Observable o, Object arg) {
        if(o instanceof SpecialRepoter){
            System.out.println("Global Timse brings you the latest news!");    
        }
    }
}

 

测试一下:

public class Test {
    public static void main(String[] args) {
        SpecialRepoter repoter = new SpecialRepoter();
        GlobalTimes n1 = new GlobalTimes(repoter);
        PeopleDaily n2 = new PeopleDaily(repoter);
        XinhuaDaily n3 = new XinhuaDaily(repoter);
        
        repoter.getNewNews("new news!");
        n2.remove();
        repoter.getNewNews("another new news!");
    }
}

Xinhua Daily brings you the latest news!
People's Daily brings you the latest news!
Global Timse brings you the latest news!
Xinhua Daily brings you the latest news!
Global Timse brings you the latest news!
在注册的时候,先注册的GlobalTimes,然后是PeopleDaily,最后才是XinhuaDaily,但是最后结果打印的却不是这个顺序,可以看出通知顺序和注册的顺序没有关系,即通知顺序不依赖于注册的顺序,这要特别注意

下面使用Observer Pattern的pull模式再次重写:

 

public class SpecialRepoter extends Observable {
    private String msg;
    
    public void getNewNews(String msg){
        this.msg = msg;
        this.setChanged();
        this.notifyObservers();
    }
    
    public String getMsg(){
        return msg;
    }
}

public class NewspaperOffice {
}

public class PeopleDaily extends NewspaperOffice implements Observer {
    private Observable observable;
    
    public PeopleDaily(SpecialRepoter repoter){
        this.observable = repoter;
        repoter.addObserver(this);
    }
 
    public void update(Observable o, Object arg) {
        if(o instanceof SpecialRepoter){
            SpecialRepoter repoter = (SpecialRepoter)o;
            String msg = repoter.getMsg();
            System.out.println("People's Daily brings you the latest news!");  
            System.out.println(msg.toString());
        }
    }
    
    public void remove(){
        observable.deleteObserver(this);
    }
}

public class XinhuaDaily extends NewspaperOffice implements Observer{
    private Observable observable;
    
    public XinhuaDaily(SpecialRepoter repoter){
        this.observable = repoter;
        repoter.addObserver(this);
    }
 
    public void update(Observable o, Object arg) {
        if(o instanceof SpecialRepoter){
            SpecialRepoter repoter = (SpecialRepoter)o;
            String msg = repoter.getMsg();
            System.out.println("Xinhua Daily brings you the latest news!");
            System.out.println(msg.toString());
        }
    }
}

public class GlobalTimes extends NewspaperOffice implements Observer{
    private Observable observable;
    
    public GlobalTimes(SpecialRepoter repoter){
        this.observable = repoter;
        repoter.addObserver(this);
    }
 
    public void update(Observable o, Object arg) {
        if(o instanceof SpecialRepoter){
            SpecialRepoter repoter = (SpecialRepoter)o;
            String msg = repoter.getMsg();
            System.out.println("Global Timse brings you the latest news!");    
            System.out.println(msg.toString());
        }
    }
}

测试一下:

public class Test {
    public static void main(String[] args) {
        SpecialRepoter repoter = new SpecialRepoter();
        GlobalTimes n1 = new GlobalTimes(repoter);
        PeopleDaily n2 = new PeopleDaily(repoter);
        XinhuaDaily n3 = new XinhuaDaily(repoter);
        
        repoter.getNewNews("new news!");
        n2.remove();
        repoter.getNewNews("another new news!");
    }
}

Xinhua Daily brings you the latest news!
new news!
People's Daily brings you the latest news!
new news!
Global Timse brings you the latest news!
new news!
Xinhua Daily brings you the latest news!
another new news!
Global Timse brings you the latest news!
another new news!

二: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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值