`
安静思考
  • 浏览: 12671 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

设计模式之观察者模式

阅读更多

一、定义

    参考"Gang of Four"所著的设计模式一书对观察者模式的定义:"a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically."

在对象之间定义一对多依赖,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新。这个模式用到的设计原则就是“为交互对象之间的松耦合而努力”。

观察者模式又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、事件源-监听器(Source/Listener)模式或从属者(Dependents)模式。

二、应用场合

    可以在下面场合中应用观察者模式:

  • 当一个抽象体具有两个相互依赖的方面时,可以将这两方面封装成独立的对象,使用户可以独立的更改并重用这两个对象。
  • 当一个对象状态发生变化,需要同时通知其他对象并且不知道有多少对象需要通知时。
  • 当一个对象在不知道对方是谁的情况下,应该能够通知到其他对象时,也就是说当你不想让这些对象紧密的耦合时。

三、参与者

    1、Subject 可以是一个抽象类,也可以是一个接口,提供了增加删除观察者的接口,并且保存了一份私有的包含所有观察者的列表,提供了以下方法:

  • Attach - 增加一个新的观察者到观察者列表中
  • Detach - 在观察者列表中删除指定观察者
  • Notify - 当状态发生变化时,通过观察者的update方法通知每一个观察者

    2、ConcreteSubject 这是被观察者对象,提供了观察者类感兴趣的数据,当它的数据发生变化的时候,可以通过调用其父类提供的notify方法通知所有观察者,提供了以下方法:

  • GetState - 获取Subject的状态

    3、Observer 定义了用来更新通知的接口。提供以下方法:

  • Update - 一个抽象的方法,需要子类实现,用来从Subject对象接收通知

    4、ConcreteObserver 保存了一个concreteSubject的引用,当被通知时用来接收被观察者的状态,并保存了需要跟Subject对象保持一致的状态,提供下面方法:

  • Update - 实现Observer的update接口方法,用来保持与Subject状态的一致性。

下面通过一个图示来说明上面这些参与者之间的协作关系:

observer

四、观察者模式的java内置实现

    在java类库中提供了Observable/Observer类来支持观察者模式。

    1、java.util.Observable,是基于上面提到的Subject的一个抽象类,任何想成为被观察者的类都需要继承这个类。Observable提供了:

  • 实现了增加/删除观察者的方法:addObserver(Observer o)和deleteObserver(Observer o)
  • 实现了通知所有观察者的方法:notifyObservers()和notifyObservers(Object arg),当调用没有参数的notifyObservers方法,表示是利用“拉”的方式获取数据,而调用带参数的notifyObservers方法,表示是利用“推”的方式由Subject负责像观察者传送数据。
  • 提供了setChanged()方法用来标记状态已经改变的事实,好让notifyObservers知道当它被调用的时候同时通知所有观察者,如果在调用notifyObservers时没有调用setChanged()方法,则观察者将不会被通知,原因可从其源代码获得:
public void notifyObservers(Object arg) {   
    /*  
         * a temporary array buffer, used as a snapshot of the state of  
         * current Observers.  
         */  
        Object[] arrLocal;   
  
    synchronized (this) {   
        /* 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  
         */  
        if (!changed)   
                return;   
            arrLocal = obs.toArray();   
            clearChanged();   
        }   
  
        for (int i = arrLocal.length-1; i>=0; i--)   
            ((Observer)arrLocal[i]).update(this, arg);   
} 

  

  •   用了Vector来保存所有的观察者(Observer)的引用,注意Vector是线程安全的。

    2、java.util.Observer接口,对应上面提到的Observer接口,所有的观察者类必须实现这个接口。提供了:

  • 提供了void update(Observable o, Object arg)接口方法,当Observer的状态变化需要通知观察者的时候,这个update方法会被调用;第一个参数传递的是一个Subject对象,好让观察者知道是哪一个Subject通知的它,第二个参数是一个Object对象,当一个Observable调用notifyObservers()方法时,这个arg为null,如果Observable调用notifyObservers(Object obj)时,那么这个arg就是这个obj。

五、利用java内置的Observable/Observer机制实现观察者模式(代码参考wiki网站)

 

/* File Name : EventSource.java */  
    
package obs;   
import java.util.Observable;          //Observable is here   
import java.io.BufferedReader;   
import java.io.IOException;   
import java.io.InputStreamReader;   
    
public class EventSource extends Observable implements Runnable    
{   
    public void run()   
    {   
        try  
        {      
            final InputStreamReader isr = new InputStreamReader( System.in );   
            final BufferedReader br = new BufferedReader( isr );   
            while( true )   
            {   
                final String response = br.readLine();   
                setChanged();   
                notifyObservers( response );   
            }   
        }   
        catch (IOException e)   
        {   
            e.printStackTrace();   
        }   
    }   
} 

 

/* File Name: ResponseHandler.java */  
    
package obs;   
    
import java.util.Observable;   
import java.util.Observer;  /* this is Event Handler */  
    
public class ResponseHandler implements Observer   
{   
    private String resp;   
    public void update (Observable obj, Object arg)   
    {   
        if (arg instanceof String)   
        {   
            resp = (String) arg;   
            System.out.println("\nReceived Response: "+ resp );   
        }   
    }   
}  

 

/* Filename : myapp.java */  
/* This is main program */  
    
package obs;   
    
public class MyApp   
{   
    public static void main(String args[])   
    {               
        System.out.println("Enter Text >");   
    
        // create an event source - reads from stdin   
        final EventSource evSrc = new EventSource();   
    
        // create an observer   
        final ResponseHandler respHandler = new ResponseHandler();   
    
        // subscribe the observer to the event source   
        evSrc.addObserver( respHandler );   
    
        // starts the event thread   
        Thread thread = new Thread(evSrc);   
        thread.start();   
    }   
}  

 六、用java内置的Observable/Observer机制来实现观察者模式 

    由于java.util.Observable是一个抽象类,不是一个接口,并且java类库内部没有一个类似于Subject的接口,因此,如果一个已经继承了某个类的类想成为一个被观察者,则不能用上面的实现方式。解决方法如下:

    1、重新写一个继承java.util.Observable的类,为了方便起见,命名为DelegatedObservable,由于Observable类的setChanged()和clearChanged()被java设计者置以protected访问权限,只有Observable子类内才可以调用,因此我们需要在子类里覆盖这两个方法,按照java的规则,当覆盖一个父类的方法时可以修改这个方法的访问权限,但是只能扩大方法的访问权限,不能缩小,比如:把一个具有protected访问权限的方法修改为一个具有public访问权限的方法是可以的,但是把他修改为一个具有private访问权限的方法则不允许。

    2、在这个已经继承了某个类的类中包含一个私有的DelegatedObservable对象。

    3、在包含有DelegatedObservable对象的类中增加两个方法:增加/删除观察者方法

通过以上步骤,基本解决了上述问题,代码如下(只列出了改变的两个类,其余的类跟上面的代码一样,对于EventSourceParent类,就不列出来了):

import java.util.Observable;   
/**  
 *继承Observable的子类,并覆盖clearChanged()跟setChanged()方法  
 */  
public class DelegatedObservable extends Observable {   
       
    public void clearChanged() {   
        super.clearChanged();   
    }   
  
    public void setChanged() {   
        super.setChanged();   
    }   
}
/**  
 * 为了让这个类具有被观察者的功能,里面包含了DelegatedObservable的对象属性,  
 */  
public class EventSource extends EventSourceParent implements Runnable {   
  
    private DelegatedObservable obs;   
  
    public EventSource() {   
        this.obs = new DelegatedObservable();   
    }   
  
    public void run() {   
  
        try {   
            final InputStreamReader isr = new InputStreamReader(System.in);   
            final BufferedReader br = new BufferedReader(isr);   
            while (true) {   
                final String response = br.readLine();   
                obs.setChanged();   
                obs.notifyObservers(response);   
            }   
        } catch (IOException e) {   
            e.printStackTrace();   
        }   
  
    }   
    //由于不能把DelegatedObservable暴露给外面,因此这里需要   
    //完成增加跟删除观察者的方法   
    public void addObserver(Observer o) {   
        obs.addObserver(o);   
    }   
  
    public void deleteObserver(Observer o) {   
        obs.deleteObserver(o);   
    }   
  
}  

  

 七、用java内置的事件生成机制来实现观察者模式

    观察者模式在java API里面出现了两次:一次是上面提到的在java.util包里的Observable/Observer来实现;还有就是在JavaBeans/1.1 AWT/Swing用到的事件生成机制。用Observable/Observer来实现观察者模式有许多缺点,也不是最好的java实现实现方式,具体缺点列于下:

    1、Observable是一个类,因此要实现一个Observable,必须继承这个类,这点上面已经提到

    2、对于观察者,仅仅需要实现一Observer接口并实现update方法就可以了,这种设计方式可以适用于任何情况;但正是由于这种通用的设计,使得程序员如果不去研究源代码仅仅根据Observable/Observer很难知道这个事件处理的真正意义,对比一下下面的事件处理:继承一个MouseAdapter并且覆盖mouseReleased()方法,一看这个方法的名字我们就知道这个方法的作用。

    3、One final reason I turned away from Observer/Observable is simply that using the event delegation model used in JavaBeans in non-bean classes eases any future transformation of a given class into a JavaBean. (Note that the AWT and Swing components, which use this event delegation model, are themselves JavaBeans.) (还理解不了,先把英文附在这)。

    下面一步步的用事件生成机制来实现观察者模式:

    步骤1:定义事件类别类

  • 为每一个主要的事件类别定义一个事件类别类,这些属于此类别的事件都是由事件生成器产生的。
  • 使每一个事件类别类都继承java.util.EventObject类
  • 设计这个事件类别类,使他能够封装一些需要从被观察者(Observable)到此事件的监听者传送的信息。
  • 给出这个事件类别类的类名,后面以Event结尾,比如:TelephoneEvent。

    步骤2:定义监听接口

  • 对每一个事件类别,定义一个继承java.util.EventListener 的监听接口,然后在这个监听接口中对每一个属于此类别的事件,都声明一个事件处理方法,这个方法将会触发从事件生成器到这个监听的信息传送。
  • 命名这个监听接口,一般原则是用Listener来代替事件类别类名的Event。比如TelephoneEvent事件类别的监听器命名为:TelephoneListener。
  • 给监听接口的方法指定一个基于动词的方法名称。比如,当电话响起时,TelephoneListener会接收到一个TelephoneEvent,可以将这个方法命名为telephoneRang.
  • 监听接口的方法应该返回一个void,并且有一个指向合适事件类别类的引用,比如TelephoneRang的签名为:void telephoneRang(TelephoneEvent e)。

    步骤3:定义一个适配器Adapter(可选)

  • 对于每一个事件监听接口,如果包含有多于一个方法时,一般需要定义一个适配器,这个适配器实现这个接口,但是方法都是空实现。
  • 命名规则是用Adapter来替换监听接口名称里面的Listener,比如TelephoneListener的适配器是TelephoneAdapter。

    步骤4:定义Observable类

  • 对于任何属于事件类别的事件,都将是这个类的一个实例,还需要定义增加/删除监听的方法
  • 增加监听方法命名为add<listener-interface-name>(),删除方法命名为remove<listener-interface-name>()。
  • 对事件监听接口里的每一个方法,定义一个私有的不带任何参数的返回类型为void的事件生成方法,这个方法是用来触发事件的。
  • 事件生成方法的命名规则是fire<listener-method-name>,比如,对于TelephoneListener接口的telephoneRang方法对应的事件生成方法就是fireTelephoneRang()。
  • 完善事件生成类的代码,使之能够在正确的时间调用正确的事件生成方法

    步骤5:定义监听对象

  • 要成为一个特定类别事件的监听者,类必须实现监听这个事件类别的监听接口。

   结构图 下面将Telephone例子的UML图贴于下:

 exampletelephone

 

  例子代码

    第一个类是定义事件类别的类,叫做TelephoneEvent。

public class TelephoneEvent extends java.util.EventObject {   
    public TelephoneEvent(Telephone source) {   
        super(source);   
    }   
} 

  

  说明:TelephoneEvent继承了EventObject类,其构造函数含有一个指向事件生成器Telephone的对象引用,这个构造函数将这个引用传递到父类的构造函数中,这样,由于在事件监听器里面的每个方法都含有一个TelephoneEvent的参数,因此可以通过这个对象调用getSource()来获取事件源,也就是Telephone对象。

    每当一个事件对象生成的时候提供一个指向事件源的引用,这可以使一个单一的监听器可以注册多个同种类事件源,比如一个秘密设备可以注册为多个电话的监听器,当一个电话事件生成的时候,他可以通过事件对象查询是哪一个电话生成的这个事件。

    另外允许事件处理对象能够获取事件源对象引用,这样可以从事件源获取更多的信息,也是观察者模式中的“拉(pull)”模式。

    当然,这个事件类别类还可以封装一些对事件处理者有用的额外信息,类似于事件发生时间等之类的信息。

下一步,给出监听接口代码:

public interface TelephoneListener extends java.util.EventListener {   
    void telephoneRang(TelephoneEvent e);   
    void telephoneAnswered(TelephoneEvent e);   
}  

  

说明:这个监听接口继承了java.util.EventListener接口,而这个EventListener接口只是一个标记接口,并不包含任何方法。这个接口定义了处理属于TelephoneEvent类别的两种事件的方法,telephoneRang()和telephoneAnswered(),注意这两个方法都接收一个指向TelephoneEvent对象的参数引用。

下一步,由于TelephoneListener提供了多于一个的方法,因此,下面提供这个监听接口的适配器:

public class TelephoneAdapter implements TelephoneListener {   
    public void telephoneRang(TelephoneEvent e) {   
    }   
    public void telephoneAnswered(TelephoneEvent e) {   
    }   
} 

   

说明:这个适配器仅仅是对接口所有方法提供空实现,主要是为了监听器的实现类只需要关注自己感兴趣的事件监听方法。

下面,实现事件生成类:Telephone类

import java.util.Vector;   
public class Telephone {   
    private Vector telephoneListeners = new Vector();   
    public void ringPhone() {   
        fireTelephoneRang();   
    }   
    public void answerPhone() {   
        fireTelephoneAnswered();   
    }   
    public synchronized void addTelephoneListener(TelephoneListener l) {   
        if (telephoneListeners.contains(l)) {   
            return;   
        }   
        telephoneListeners.addElement(l);   
    }   
    public synchronized void removeTelephoneListener(TelephoneListener l) {   
        telephoneListeners.removeElement(l);   
    }   
    private void fireTelephoneRang() {   
        Vector tl;   
        synchronized (this) {   
            tl = (Vector) telephoneListeners.clone();   
        }   
        int size = tl.size();   
        if (size == 0) {   
            return;   
        }   
        TelephoneEvent event = new TelephoneEvent(this);   
        for (int i = 0; i < size; ++i) {   
            TelephoneListener listener = (TelephoneListener) tl.elementAt(i);   
            listener.telephoneRang(event);   
        }   
    }   
    private void fireTelephoneAnswered() {   
        Vector tl;   
        synchronized (this) {   
            tl = (Vector) telephoneListeners.clone();   
        }   
        int size = tl.size();   
        if (size == 0) {   
            return;   
        }   
        TelephoneEvent event = new TelephoneEvent(this);   
        for (int i = 0; i < size; ++i) {   
            TelephoneListener listener = (TelephoneListener) tl.elementAt(i);   
            listener.telephoneAnswered(event);   
        }   
    }   
}  

  

 说明:这个类有两个注册/取消注册 监听器到Telephone对象的方法addTelephoneListener()和removeTelephoneListener(),这两个方法能够保证存储在Vector对象的所有监听器没有重复,因此每一个事件只会通知监听器一次;fire***()方法在生成事件之前将Vector对象克隆了一份,在这种实现方式下,当一个事件被触发,这时所通知的监听器都取自这个Vector对象的快照,这意味着:一个监听即使已经取消注册了,也可能会被通知,因为,如果这个监听是在事件触发之后取消注册的。

继续完善代码,使之能够完整展示我们的例子

 

public class AnsweringMachine
    implements TelephoneListener {
    public void telephoneRang(TelephoneEvent e) {
        System.out.println("AM hears the phone ringing.");
    }
    public void telephoneAnswered(TelephoneEvent e) {
        System.out.println("AM sees that the phone was answered.");
    }
}
// In file eventgui/ex1/Person.java
public class Person {
    public void listenToPhone(Telephone t) {
        t.addTelephoneListener(
            new TelephoneAdapter() {
                public void telephoneRang(TelephoneEvent e) {
                    System.out.println("I'll get it!");
                }
            }
        );
    }
}

 说明:AnsweringMachine是直接实现TelephoneListener接口,而Person是实例化一个继承自TelephoneAdapter的匿名内部类,内部类仅仅实现了telephoneRang方法。

下面提供一个可以运行上面所提供的类的例子:

public class Example1 {
    public static void main(String[] args) { 
        Telephone ph = new Telephone();
        Person bob = new Person();
        AnsweringMachine am = new AnsweringMachine();
        ph.addTelephoneListener(am);
        bob.listenToPhone(ph);
        ph.ringPhone();
        ph.answerPhone();
    }
}

 

程序运行结果如下:

写道
The answering machine hears the phone ringing.
I'll get it!
The answering machine sees that the phone was answered.

 八、利用java的内置事件生成机制实现观察者模式的指导方针

这一小节所列出的指导方针是实现java观察者模式的默认方式,默认的意思就是如果你没有特殊的改变实现方式的原因,那么就应该按照下面所列的来实现,指导方针是:

  • 当事件触发时,仅仅创建一个事件对象,并将其传送给所有的监听者
  • 让事件对象是不可更改的,因此监听器没有机会去修改他所获得的事件源的对象状态。
  • 用一个单线程去通知所有的监听器,也就是说在fire方法里面,遍历所有的监听器,并调用监听器的合适方法来通知监听器。
  • 在触发过程之前,首先取一份注册监听器列表(克隆一份列表)的快照,然后仅仅对快照里面的监听器进行发送事件操作。
  • 要使事件处理方法(就是监听器里的方法)尽可能的短;由于监听器在一个时间内只被通知一次,因此要求监听器的方法执行时间要快速,也就是说一个监听器只有等到通知队列中它前面的所有监听器全部执行完毕之后它才会被通知。因此对于监听器来说,它的事件处理方法有责任快速执行。如果一个监听器的处理方法需要相当大的工作量,那么就需要重新设计这个方法,让它启动一个线程来做这些耗费时间的工作。
  • 不要写一个依赖于通知顺序的监听器。也就是说通知顺序并不是按照固定的顺序来执行的。
  • 如果一个监听器只是对时间类别下的某些时间感兴趣,可以考虑子类化一个Adapter,然后实现自己感兴趣的事件处理方法。

 九、利用java的内置事件生成机制实现观察者模式的指导方针的变体

下面是第八条所提出的指导方针的变体,也就说说对某些特殊情况,上面的默认的实现方式可能不太适合,这时候可以参考下面的指导方针:

    1、如果同一个事件类别下面的事件之间发生的频率大不一样,可以考虑将发生频率高的事件写成一个监听接口,发生频率低的事件写成一个监听接口。在java类库中就有这样的情况,比如在java.awt.event包下面的MouseListener和MouseMotionListener两个接口,这两个接口都是定义的MouseEvent的事件接口,但是由于MouseEvent类别下的像“鼠标移动”事件要比同属于MouseEvent类别下的“鼠标按下”事件发生的频率多得多。因此像这种发生频率多的事件有它自己的监听接口,比如“鼠标移动”事件的监听接口就是MouseMotionListener,而对于发生频率不高的事件则定义到普通的事件监听器里面:MouseListener。

    2、如果事件监听器之间需要相互合作的话,可以考虑让事件源成为可修改的,然后事件监听器之间可以通过事件源来相互合作,这在java类库中同样存在,比如java.awt.AWTEvent类,这个类是所有java.awt.event包下面的类的超类,这个类有两个方法:consume()和isConsumed();这两个方法就能使AWT 事件的监听器之间相互合作。一个监听器通过在事件对象上调用consume()能够“consume”一个事件,随后的那些监听器可以在事件对象上通过调用isConsumed()来确定这个事件是否已经被“consumed”,如果isConsumed()返回true,也就是说已经有个监听器调用了consume()在同一个对象上,那么这个监听器就可以忽略这个事件。

十、结语

纵观上面所述,如果要在java中实现观察者模式,那么首推的实现方式就是事件生成机制。

 

---------------------------------------------------注意--------------------------------------------------------------

 

如果您对本文感兴趣,可以转载,但请注明引自本站,这并不费劲,尊重别人的劳动成果也光荣,谢谢。

 

另外感谢Bill Venners在javaword网站所著的"The 'event generator' idiom"这篇精彩文章,让我受益匪浅

  • 大小: 5.6 KB
  • 大小: 4.5 KB
  • 大小: 3.6 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics