學習、探究Java設計模式——觀察者模式
前言
觀察者模式是面向對象編程中較為常用的一種設計模式,觀察者模式又稱為訂閱-發布模式,特別是適用于GUI圖形界面中,比如Android的View中就大量使用了此模式。那么觀察者模式到底是什么以及我們應該怎么使用它呢?相信通過本文的學習,你們會有一個更為清晰的答案。
定義
觀察者模式:定義了對象之間的一對多依賴,當一個對象改變狀態時,它的所有依賴者都會收到通知并自動更新。
由以上的定義,我們可以知道,觀察者模式有兩個重要要素:觀察者、被觀察者。用生活中一個常見的例子來描述:我們通常會訂閱天氣預報信息,假如天氣出現了變化,氣象App就會通知我們天氣變了要做出相應的準備,那么在這個場景中,我們就充當了“觀察者”角色,而氣象App則充當了“被觀察者”角色,當天氣發生了變化,“被觀察者”就會通知我們,讓我們做出相應改變。
那么,我們已經知道了觀察者和被觀察者的概念,但我們還不知道它們兩個是怎樣聯系起來的,接下來我們正要解決這個問題,為了更好地理解,我們先來看看觀察者模式的UML類圖。
UML類圖
UML類圖
我們來分析下上圖各個類或者接口的含義:
Observerable:被觀察者接口,規定了幾個方法,分別是registerObserver():表示將觀察者注冊到被觀察者中,即“訂閱”;removeObserver():表示將觀察者從被觀察者中移除,即“取消訂閱”;notifyObservers():當被觀察者狀態改變的時候,這個方法被調用,通知所有已經注冊的觀察者。
ConcreteObserverable:被觀察者,實現了Observerable接口,對以上的三個方法進行了具體實現,同時有一個List集合,用以保存注冊的觀察者,等需要通知觀察者時,遍歷該集合即可。 注意 ,該集合的泛型參數應該是Observer,接口類型,而不應該是具體的Observer實現類,這樣做的原因是一個被觀察者可能會有多個不同實現類的觀察者(但都實現了Observer接口),如果限定了某一個具體的類型,那么會導致以后要加入新類型的時候而不得不修改當前類,耦合度過高,這是一個非常不好的行為。( 設計原則:面向接口編程而不是面向實現編程 )
Observer:觀察者接口,規定了update()方法,當被觀察者調用notifyObservers()方法時,觀察者的update()方法會被回調。
ConcreteObserver:觀察者,實現了update()方法。
那么了解了以上各部分的含義后,應該不難得出觀察者模式的實現過程了,那么我們接下來看看實現觀察者模式的一般過程。
實現步驟
我們需要準備Observerable接口,聲明注冊、移除、通知更新這三個方法,接著需要Observer接口,聲明update()方法,然后我們要實現以上兩個接口,分別是ConcreteObserverable和ConcreteObserver這兩個實現類。其中,ConcreteObserverable應該有一個成員變量用戶保存注冊的觀察者,當需要通知更新的時候,調用ConcreteObserverable#notifyObservers()方法,該方法內部遍歷所有的觀察者,并調用他們的update()方法,這樣便實現了訂閱——發布流程。
A Sample
接下來,我們實現一個簡單的例子,讓大家更熟悉觀察者模式。
我們來模擬一個場景:A、B、C三人在書店訂閱了同一本雜志,當該雜志上架的時候,會通知他們雜志上架的信息(包括雜志的期數、最新價格),讓他們過來購買。
Step 1.建立接口
新建Observerable.java文件:
public interface Observerable {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
新建Observer.java文件:
public interface Observer {
public void update(int edition,float cost);
}
Step 2.實現被觀察者接口
新建MagazineData.java文件:
public class MagazineData implements Observerable {
private List<Observer> mObservers;
private int edition;
private float cost;
public MagazineData() {
mObservers = new ArrayList<>();
}
@Override
public void registerObserver(Observer o) {
mObservers.add(o);
}
@Override
public void removeObserver(Observer o) {
int i = mObservers.indexOf(o);
if(i >= 0)
mObservers.remove(i);
}
@Override
public void notifyObservers() {
for(int i = 0; i < mObservers.size(); i++){
Observer observer = mObservers.get(i);
observer.update(edition, cost);
}
}
public void setInfomation(int edition,float cost){
this.edition = edition;
this.cost = cost;
//信息更新完畢,通知所有觀察者
notifyObservers();
}
}</code></pre>
Step 3.實現觀察者接口
新建Customer.java文件:
public class Customer implements Observer {
private String name;
private int edition;
private float cost;
public Customer(String name){
this.name = name;
}
@Override
public void update(int edition, float cost) {
this.edition = edition;
this.cost = cost;
buy();
}
public void buy(){
System.out.println(name+"購買了第"+edition+"期的雜志,花費了"+cost+"元。");
}
}</code></pre>
經過以上三個步驟,已經實現了觀察者模式了,那么我們最后再編寫一個測試類,來進行測試:
public class Test {
public static void main(String[] args) {
//創建被觀察者
MagazineData magazine = new MagazineData();
//創建三個不同的觀察者
Observer customerA = new Customer("A");
Observer customerB = new Customer("B");
Observer customerC = new Customer("C");
//將觀察者注冊到被觀察者中
magazine.registerObserver(customerA);
magazine.registerObserver(customerB);
magazine.registerObserver(customerC);
//更新被觀察者中的數據,當數據更新后,會自動通知所有已注冊的觀察者
magazine.setInfomation(5, 12);
}
}</code></pre>
運行,結果如下:

運行結果.jpg
通過上面的例子,我們可以看出,MagazineData和Customer兩者處于松耦合狀態,甚至是零耦合狀態,如果某一者需求改變了,需要進行代碼的修改,這并不會涉及到另一者,這是非常良好的松耦合設計,只需要實現接口即可。
認識推模型和拉模型
通過上面的一個例子,我們簡單實現了觀察值模式,我們注意到在被觀察者通知觀察者的過程中,即在Observerable內調用Observer的update(int edition, float cost)方法時,我們把MagazineData的幾個數據傳遞了進去,使得觀察者能夠從參數中得到數據,這就像從MagazineData中把數據“推”到了觀察者中。這也就是 推模型 。與推模型相對的,假如MagazineData并不是把具體的數據傳遞過去,而是把自身的引用作為參數傳遞過去,那么觀察者得到的是MagazineData實例的引用,那么我們可以直接通過一系列的get方法來獲取數據,這也就是 拉模型 。下面,我們給推模型和拉模型下定義:
推模型:被觀察者主動向觀察者推送自身的信息,可以是全部信息或者是部分信息。
拉模型:被觀察者通過把自身的引用傳遞給觀察者,需要觀察者自行通過該引用來獲取相關的信息。
其實,我們上面實現的例子,就是使用了推模型,把MagazineData的兩個成員變量推送給了觀察者,如果要把以上例子修改為拉模型也很簡單,在update()方法中,把參數修改為:update(Observerable observerable)即可,然后在觀察者的實現類中,把得到的Observerable引用強制轉換為具體的實現類型即可。
比較:推模型適用于提前知道觀察者所需要的數據的情況,而拉模型由于把自身傳遞了過去,因此適用于大多數場景。
認識Java內置的觀察者模式
上面我們自行實現了觀察者模式,其實在Java中,它已經為我們準備了Observerable類以及Observer接口,只要繼承或實現,就能快速實現我們的被觀察者和觀察者了,那么,我們學習下該內置的觀察者模式。
首先,我們來看看 java.util.Observerable 類,這與我們上面自行實現的不同,它是一個類而不是一個接口,因此我們的被觀察者要繼承該類,另外,Observerable類已經幫我們實現了addObserver()、deleteObserver()、notifyObservers()等方法,因此我們在子類不需要再重寫這些方法了,另外,該父類還提供了一個setChanged()方法,該方法用來標記狀態已經改變的事實,因此要先調用該方法再調用notifyObservers()方法。其實,該類提供了兩個notifyObservers()方法,一個有參數,一個無參數,而有參數的適用于推模型,無參數的適用于拉模型。
而 java.util.Observer 則是一個接口,與我們上面定義的基本一樣。
那么,我們來利用Java內置的觀察者模式結合拉模型來實現我們上面的小例子。
新建JournalData.java:
public class JournalData extends Observable {
private int edition;
private float cost;
public void setInfomation(int edition,float cost){
this.edition = edition;
this.cost = cost;
setChanged();
//調用無參數的方法,使用拉模型
notifyObservers();
}
//提供get方法
public int getEdition(){
return edition;
}
public float getCost(){
return cost;
}
}</code></pre>
新建Consumer.java:
public class Consumer implements Observer {
private String name;
private int edition;
private float cost;
public Consumer(String name){
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
//判斷o是否是JournalData的一個實例
if(o instanceof JournalData){
//強制轉化為JournalData類型
JournalData journalData = (JournalData) o;
//拉取數據
this.edition = journalData.getEdition();
this.cost = journalData.getCost();
buy();
}
}
public void buy(){
System.out.println(name+"購買了第"+edition+"期的雜志,花費了"+cost+"元。");
}
}</code></pre>
測試類Test.java:
public static void main(String[] args) {
//創建被觀察者
JournalData journal = new JournalData();
//創建三個不同的觀察者
Consumer consumerA = new Consumer("A");
Consumer consumerB = new Consumer("B");
Consumer consumerC = new Consumer("C");
//將觀察者注冊到被觀察者中
journal.addObserver(consumerA);
journal.addObserver(consumerB);
journal.addObserver(consumerC);
//更新被觀察者中的數據
journal.setInfomation(6, 11);
}
}</code></pre>
運行結果如下所示:

運行結果
經過上面的幾個例子,相信大家對觀察者模式有了一個比較深刻的認識了,最后我們再說說觀察者模式可以用在什么地方。比如我們有兩個對象,一個對象依賴于另一個對象的變化而變化,此時我們可以將這兩個對象抽象出來,做成接口,利用觀察者模式來進行解耦,又或者,當一個對象發生變化的時候,需要通知別的對象來做出改變,但又不知道這樣的對象有多少個,此時利用觀察者模式非常合適。
來自:http://www.jianshu.com/p/3459188bc8f9