1. 程式人生 > >設計模式之:解剖觀察者模式

設計模式之:解剖觀察者模式

為了便於理解,首先我舉一個現實生活中的例子:在快樂男生比賽過程其實就是觀察者的一個體現,可以這樣說吉傑是一個被觀察者,而楊二,包小柏,還有巫啟賢就是3個觀察者,被觀察者操作(唱歌)時,觀察者們就開始操作(評分),被觀察者唱歌就是通知觀察者們進行評分。

GoF說道:Observer模式的意圖是“定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被自動更新”。從這段話裡我們可以得到兩個資訊,如下:

1, 觀察者(具體執行操作的物件,有多個)
2, 被觀察者(顧名思義是被觀察的物件,如果該物件發生某些變化則通知觀察者執行對應的操)

接下來我們看一下附件中的圖(請下載附件中的圖

http://www.iteye.com/topics/download/eed23def-b8a8-43dc-a753-73d740a3ded0 ),這個圖是觀察者模式的真實體現,在這個圖中有兩個類,java.util.Observable,在我們實現觀察者模式的時候,我們的被觀察者應該繼承這個類,這個observable類把持住了被觀察者所持有的觀察者列表:

Java程式碼 複製程式碼 收藏程式碼
  1. publicclass Observable { 
  2.     privateboolean changed = false
  3.     private Vector obs; 
  4.     //建立被觀察者時就建立一個它持有的觀察者列表,注意,這個列表是需要同步的。
  5.     public Observable() { 
  6.     obs = new Vector(); 
  7.     } 
  8.     /**
  9.      * 新增觀察者到觀察者列表中去
  10.      */
  11.     publicsynchronizedvoid addObserver(Observer o) { 
  12.         if (o == null
  13.             thrownew NullPointerException(); 
  14.     if (!obs.contains(o)) { 
  15.         obs.addElement(o); 
  16.     } 
  17.     } 
  18.     /**
  19.      * 刪除一個觀察者
  20.      */
  21.     publicsynchronizedvoid deleteObserver(Observer o) { 
  22.         obs.removeElement(o); 
  23.     } 
  24.     /**
  25.      * 通知操作,即被觀察者發生變化,通知對應的觀察者進行事先設定的操作,不傳引數的通知方法
  26.      */
  27.     publicvoid notifyObservers() { 
  28.     notifyObservers(null); 
  29.     } 
  30.     /**
  31.      * 與上面的那個通知方法不同的是,這個方法接受一個引數,這個引數一直傳到觀察者裡,以供觀察者使用
  32.      */
  33.     publicvoid notifyObservers(Object arg) { 
  34.         Object[] arrLocal; 
  35.     synchronized (this) { 
  36.         if (!changed) 
  37.                 return
  38.             arrLocal = obs.toArray(); 
  39.             clearChanged(); 
  40.         } 
  41.         for (int i = arrLocal.length-1; i>=0; i--) 
  42.             ((Observer)arrLocal[i]).update(this, arg); 
  43.     } 
public class Observable {
    private boolean changed = false;
    private Vector obs;
   
    //建立被觀察者時就建立一個它持有的觀察者列表,注意,這個列表是需要同步的。
    public Observable() {
	obs = new Vector();
    }

    /**
     * 新增觀察者到觀察者列表中去
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
	if (!obs.contains(o)) {
	    obs.addElement(o);
	}
    }

    /**
     * 刪除一個觀察者
     */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(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);
    }
}



這裡只列出了一些常用的操作,大家如有不明白可以自己看java.util包下的Observable,每一個方法前都有詳細的解釋。但是以上列出的方法對於這個例子來說已經夠用了。

當我們自己的被觀察者繼承這個Observable類是,我們就自動的獲取到被觀察者的一切條件了。很方便是不是,這也是為什麼sun要把Observable放到java.util包中的原因,就是為了方便開發者。

講完了被觀察者再讓我們來看看觀察者,在上面的程式碼中有一個addObserver(Observer o)方法,由此我們可以看出來,給被觀察者新增觀察者時就是用這個方法,那麼也就是說觀察者一定是Observer的子類或者實現,我們看一下java.util.Observer吧:

Java程式碼 複製程式碼 收藏程式碼
  1. publicinterface Observer { 
  2.     /**
  3.      * This method is called whenever the observed object is changed. An
  4.      * application calls an <tt>Observable</tt> object's
  5.      * <code>notifyObservers</code> method to have all the object's
  6.      * observers notified of the change.
  7.      *
  8.      * @param   o     the observable object.
  9.      * @param   arg   an argument passed to the <code>notifyObservers</code>
  10.      *                 method.
  11.      */
  12.     void update(Observable o, Object arg); 
public interface Observer {
    /**
     * This method is called whenever the observed object is changed. An
     * application calls an <tt>Observable</tt> object's
     * <code>notifyObservers</code> method to have all the object's
     * observers notified of the change.
     *
     * @param   o     the observable object.
     * @param   arg   an argument passed to the <code>notifyObservers</code>
     *                 method.
     */
    void update(Observable o, Object arg);
}
}


很好,這是一個介面,介面中就只有一個方法,update,方法中有兩個引數,Observable和一個object,第一個引數就是被觀察的物件,而第二個引數就得看業務需求了,需要什麼就傳進去什麼。我們自己的觀察者類必須實現這個方法,這樣在被觀察者呼叫notifyObservers操作時被觀察者所持有的所有觀察者都會執行update操作了(當然如果你override這個方法,你甚至可以指定何種情況下只執行某種observer了,是不是比較像責任鏈模式了)。

那麼到這裡我們也差不多可以把觀察者模式應用到專案中去了。首先讓我們來實現被觀察者(因為實現被觀察者非常簡單)

首先讓我們來實現一個傳送郵件的觀察者:

Java程式碼 複製程式碼 收藏程式碼
  1. /**
  2. * @author 張榮華(ahuaxuan)
  3. * @version $Id$
  4. */
  5. publicclass MailObserver implements Observer{ 
  6.     /**
  7.      * 這個類取名為MailObserver,顧名思義,她是一個用來發送郵件的觀察者
  8.      */
  9.     publicvoid update(Observable o, Object arg) { 
  10.         System.out.println("傳送郵件的觀察者已經被執行"); 
  11.     } 
/**
 * @author 張榮華(ahuaxuan)
* @version $Id$
 */
public class MailObserver implements Observer{

	/**
	 * 這個類取名為MailObserver,顧名思義,她是一個用來發送郵件的觀察者
	 */
	public void update(Observable o, Object arg) {
		System.out.println("傳送郵件的觀察者已經被執行");
	}
}


接著再讓我們來實現一個傳送jms訊息的觀察者:

Java程式碼 複製程式碼 收藏程式碼
  1. /**
  2. * @author 張榮華(ahuaxuan)
  3. * @version $Id$
  4. */
  5. publicclass JMSObserver implements Observer{ 
  6.     publicvoid update(Observable o, Object arg) { 
  7.         System.out.println("傳送訊息給jms伺服器的觀察者已經被執行"); 
  8.     } 
/**
 * @author 張榮華(ahuaxuan)
 * @version $Id$
 */
public class JMSObserver implements Observer{

	public void update(Observable o, Object arg) {
		System.out.println("傳送訊息給jms伺服器的觀察者已經被執行");
	}
}


如上所見,觀察者的實現完全跟業務相關。是否複雜就得看你得業務是否複雜了。

接下來讓我們再來實現被觀察者,示例如下:

Java程式碼
  1. /**
  2. * @author 張榮華(ahuaxuan)
  3. * @version $Id$
  4. */
  5. publicclass Subject extends Observable{ 
  6.     /**
  7.      * 業務方法,一旦執行某個操作,則通知觀察者
  8.      */
  9.     publicvoid doBusiness(){ 
  10.         if (true) { 
  11.             super.setChanged(); 
  12.         } 
  13.         notifyObservers("現在還沒有的引數"); 
  14.     } 
  15.     publicstaticvoid main(String [] args) { 
  16.         //建立一個被觀察者
  17.         Subject subject = new Subject(); 
  18.         //建立兩個觀察者
  19.         Observer mailObserver = new MailObserver(); 
  20.         Observer jmsObserver = new JMSObserver(); 
  21.         //把兩個觀察者加到被觀察者列表中
  22.         subject.addObserver(mailObserver); 
  23.         subject.addObserver(jmsObserver); 
  24.         //執行業務操作
  25.         subject.doBusiness(); 
  26.     } 
/**
 * @author 張榮華(ahuaxuan)
* @version $Id$
 */
public class Subject extends Observable{
	
	/**
	 * 業務方法,一旦執行某個操作,則通知觀察者
	 */
	public void doBusiness(){
		if (true) {
			super.setChanged();
		}
		notifyObservers("現在還沒有的引數");
	}

	
	public static void main(String [] args) {
		//建立一個被觀察者
		Subject subject = new Subject();
		
		//建立兩個觀察者
		Observer mailObserver = new MailObserver();
		Observer jmsObserver = new JMSObserver();
		
		//把兩個觀察者加到被觀察者列表中
		subject.addObserver(mailObserver);
		subject.addObserver(jmsObserver);
		
		//執行業務操作
		subject.doBusiness();
	}
}


到此為止,我們已經簡單得實現了觀察者模式,讓我們來執行一下上面main方法,執行結果如下:

傳送訊息給jms伺服器的觀察者已經被執行
傳送郵件的觀察者已經被執行


有了jdk對觀察者模式的支援,程式設計師在實現基本的觀察者模式時應該說是易如反掌,這也從一個側面反應出觀察者模式的地位,它的地位應該是非常重要的,引用Nicholas Lesiecki(此人是上面所指的ibm那篇文章的作者)的話說,它是“設計模式中的皇后”。

通過上面的介紹和簡單的例項,現在你也可以實現一個自己的觀察者了(有時候這個簡單的模型並不滿足我們的需要,在這種情況下只好自己去寫一個類似的但是是為業務定製的observable和observer類了,但顯然這中情況並不常見)。事實上spring對觀察者模式也有很好的支援(spring可以通過配置檔案來執行addobserver方法,這樣就可以隱式的把觀察者加到被觀察者的列表中去了,前提是你的被觀察者是spring管理的一個bean之一,詳見3樓回覆,或者也可以利用afterPropertiesSet方法在被觀察者這個bean初始化完成之後立刻把需要的觀察者新增進去),除了spring,hibernate中也大量的使用了觀察者模式,同時,如果你想再深入一點,可以看看本文第二段給的ibm技術文章的連結。