1. 程式人生 > >Java設計模式詳談(三):觀察者

Java設計模式詳談(三):觀察者

        觀察者模式又叫作(釋出-訂閱)模式,屬於行為型模式的一種,它定義了一種一對多的關係,當多個觀察者同時監聽到被觀察者出現的變化,就會作出對應的處理。

        廢話不多說,今天分享一下關於觀察者模式相關的內容。廢話不多說,接下來直接進入程式碼例項。


觀察者介面:


/**
 * 觀察者介面
 * @author lyr
 * @date 2017年11月27日
 */
public interface Observer {
	
	void update(Observable o);
}

被觀察者:

public class ConcreateObserver1 implements Observer{

	public void update(Observable o) {
		System.out.println("觀察者1發現"+o.getClass().getSimpleName()+"出現變化!");
		System.out.println("觀察者1作出迴應...");
	}

}
public class ConcreateObserver2 implements Observer{

	public void update(Observable o) {
		System.out.println("觀察者2發現 "+o.getClass().getSimpleName()+"出現變化!");
		System.out.println("觀察者2作出迴應...");
	}

}


具體的操作:

public class Observable {

	List<Observer> observers = new ArrayList<Observer>();
	
	public void addObserver(Observer o){
		observers.add(o);
	}
	
	public void change(){
		System.out.println("我是被觀察者,我已經發生變化了!");
		notifyObservers();
	}
	
	public void notifyObservers(){
		for(Observer obs:observers){
			obs.update(this);
		}
	}
}

測試

public class Client {

	public static void main(String[] args) {
		Observable obsable = new Observable();
		obsable.addObserver(new ConcreateObserver1());
		obsable.addObserver(new ConcreateObserver2());
		obsable.change();
	}
}


下面是測試結果


        雖然上面的程式碼很low,不過足夠說明問題。從上面的幾步下來可以很清楚的看到,被觀察者出現變化的時候,它會通知其它的觀察者,當觀察者捕獲到被觀察者發出的通知,就會根據各自的需要作出相應的處理。不過這裡需要注意的是,這些類都是自定義的類,並不是JDK中的相關類,所以要注意區分。

       還有一點的就是,觀察者模式分離了觀察者和被觀察者的責任,讓它們自己去維護各自的功能模組區域,從而提高系統的可重用性和可維護性。


Java內建的觀察者模式

在java.util包裡面,包含了基本的Observer介面和Observable抽象類,在使用方面除了上述實現的功能還有註冊、刪除等等,並且通知觀察者的那些功能都已經內建了。

先來看下觀察者介面:


//觀察者介面,每一個觀察者都必須實現這個介面
public interface Observer {
    //這個方法是觀察者在觀察物件產生變化時所做的響應動作,從中傳入了觀察的物件和一個預留引數
    void update(Observable o, Object arg);
}


而下面則是被觀察類:


import java.util.Vector;

//被觀察者類
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);
    }
    //notifyObservers(Object arg)的過載方法
    public void notifyObservers() {
           notifyObservers(null);
    }
    //通知所有觀察者,被觀察者改變了,你可以執行你的update方法了。
    public void notifyObservers(Object arg) {
        //一個臨時的陣列,用於併發訪問被觀察者時,留住觀察者列表的當前狀態,這種處理方式其實也算是一種設計模式,即備忘錄模式。
        Object[] arrLocal;
    //注意這個同步塊,它表示在獲取觀察者列表時,該物件是被鎖定的
    //也就是說,在我獲取到觀察者列表之前,不允許其他執行緒改變觀察者列表
    synchronized (this) {
        //如果沒變化直接返回
        if (!changed)
                return;
            //這裡將當前的觀察者列表放入臨時陣列
            arrLocal = obs.toArray();
            //將改變標識重新置回未改變
            clearChanged();
        }
        //注意這個for迴圈沒有在同步塊,此時已經釋放了被觀察者的鎖,其他執行緒可以改變觀察者列表
        //但是這並不影響我們當前進行的操作,因為我們已經將觀察者列表複製到臨時陣列
        //在通知時我們只通知陣列中的觀察者,當前刪除和新增觀察者,都不會影響我們通知的物件
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

    //刪除所有觀察者
    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();
    }
}


在使用Java內建的觀察者模式的時候還需要注意一個問題,就是notifyObservers這個方法中的一段程式碼:

for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);

你會發現當在update的過程中出現異常的時候,自此之後的其它觀察者就接收不到通知資訊了,這個問題我是在觀看一位前輩介紹關於觀察者模式的時候他提到的,我估計sun公司應該會考慮到這個問題,但是現在看來並沒有對他進行處理。所以按照正常的情況下,我在發現這個問題的時候一般的都會用try...catch語句塊包裹這個方法,也就是如下程式碼所示:


for (int i = arrLocal.length-1; i>=0; i--){
        try {
		((Observer)arrLocal[i]).update(this, arg);
	} catch (Exception e) {
		e.printStackTrace();
	}
}

這樣對它進行處理之後,如果存在問題,也不會影響後面的觀察者進行更新的狀態。

當然了,你說除此之外觀察者模式沒有其它的問題是不可能的,一般來說應該是設計上的問題,畢竟被觀察者和觀察者是一對多的關係,如果反過來就沒法用了。而且,每一個觀察者都要實現觀察者介面,才能新增到被觀察者的列表中。如果說一個觀察者已經存在,而且沒辦法更改其程式碼,那麼就沒辦法成為一個真正的觀察者,不過這個問題可以通過介面卡模式處理掉。

這次的分享就到這裡吧,下一篇再見!