Java設計模式詳談(三):觀察者
阿新 • • 發佈:2019-01-14
觀察者模式又叫作(釋出-訂閱)模式,屬於行為型模式的一種,它定義了一種一對多的關係,當多個觀察者同時監聽到被觀察者出現的變化,就會作出對應的處理。
廢話不多說,今天分享一下關於觀察者模式相關的內容。廢話不多說,接下來直接進入程式碼例項。
觀察者介面:
/** * 觀察者介面 * @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();
}
}
這樣對它進行處理之後,如果存在問題,也不會影響後面的觀察者進行更新的狀態。
當然了,你說除此之外觀察者模式沒有其它的問題是不可能的,一般來說應該是設計上的問題,畢竟被觀察者和觀察者是一對多的關係,如果反過來就沒法用了。而且,每一個觀察者都要實現觀察者介面,才能新增到被觀察者的列表中。如果說一個觀察者已經存在,而且沒辦法更改其程式碼,那麼就沒辦法成為一個真正的觀察者,不過這個問題可以通過介面卡模式處理掉。
這次的分享就到這裡吧,下一篇再見!