觀察者模式
一、現實生活中的實例
早上起牀,推開窗,看到漫天雪花,我們知道天氣降溫並下着大雪,於是我們穿上棉服打着雨傘出門;在過馬路的時候,看到了紅燈,
於是我們停下腳步等着綠燈;當穿過馬路,走到公交站臺看到公交車剛剛走遠,於是我們擺手攔下一輛出租車坐上去上班。
從天氣變化來選擇出門的穿着,從交通燈的顏色變化來選擇停留,從公交車遠走來重新選擇車子,我們總是觀察者事物的變化來改變
我們的動作。
二、軟件設計
從生活中的啓發,我們把通過觀察事物的變化來改變另一事物的行爲演變成爲了一種新的設計模式—觀察者模式。
觀察者模式的模式動機是建立一種對象與對象之間的依賴關係,一個對象發生改變時將自動通知其他對象,其他對象將相應做出反應。
在此,發生改變的對象稱爲觀察目標,而被通知的對象稱爲觀察者。
從實例中思考也可知道,多個觀察者可以觀察同一個對象,而且這些觀察者之間沒有相互聯繫。比如,開車的人、汽車的人、走路的
人都在看交通燈,並且都各自根據交通燈的變化而進行各自的動作。
觀察者模式(Observer Pattern):定義對象間的一種一對多依賴關係,使得每當一個對象狀態發生改變時,其相關依賴對象皆得到通知並被自動更新。
觀察者模式,是一種對象行爲型模式;一個觀察對象的變化,其相關依賴的N個觀察者都會被通知併發生相應的變化。
三、Java中的觀察者模式
Java是面向對象的編程,Java在java.util中提供了Observer接口和Observable類。
3.1、Observer
/**
* A class can implement the <code>Observer</code> interface when it
* wants to be informed of changes in observable objects.
*/
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);
}
該接口約定了觀察者的行爲,所有觀察者需要在被觀察目標發生變化時做出相應的響應,具體的就是實現Observer接口的update()方法。
使用觀察者模式,需要有調用者(主要應該是具體的業務類)、觀察者、觀察目標。
當前的Observer接口是觀察者,觀察者要觀察觀察的目標,目標變化時調用更新方法,因此update()方法參數需要傳入具體的觀察目標Observable的對象引用。
調用者如何傳遞數據給觀察者呢?當然是調用觀察目標的notifyObservers(Object arg)方法傳入數據,把數據再通過觀察者的update(Observable o, Object arg)的
方法把數據傳遞給觀察者,這樣就實現了調用者與觀察者的通信。換句話說:就是通過Observer的update(Observable o, Object arg)方法,把調用者、觀察目標、觀察者進行關聯和通信。
3.1、Observable
此類表示可觀察對象或模型視圖範例中的“數據”。 它可以被子類化以表示應用程序想要觀察到的對象。
可觀察對象可以有一個或多個觀察者。 觀察者可以是實現接口Observer的任何對象。 在可觀察到的實例發生變化之後,調用Observable的notifyObservers方法的應用程序會使其所有觀察者通過調用其update方法通知更改。
通知的發送順序是未指定的。 Observable類中提供的默認實現將以註冊感興趣的順序通知Observers,但子類可能會更改此順序,不使用保證的順序,在單獨的線程上發送通知,或者可以保證其子類遵循此順序,因爲它們選擇。
當新創建一個可觀察對象時,其觀察者集合爲空。 當且僅當equals方法爲它們返回true時,兩個觀察者被認爲是相同的。
主要屬性和方法
//是否變化
private boolean changed = false;
//存放觀察者容器
private Vector<Observer> obs;
/** Construct an Observable with zero Observers. */
//構造函數
public Observable() {
//初始化觀察者容器
obs = new Vector<>();
}
void addObserver(Observer o)
將一個觀察者添加到該對象的觀察者組中,前提是它與集合中已有的一些觀察者不一樣。
int countObservers()
返回此 Observable對象的觀察者數。
void deleteObserver(Observer o)
從該對象的觀察者組中刪除觀察者。
void deleteObservers()
清除觀察者列表,使此對象不再有任何觀察者。
boolean hasChanged()
測試此對象是否已更改。
protected void setChanged()
將此Observable對象標記爲已更改; hasChanged方法現在將返回true 。
protected void clearChanged()
表示此對象已不再更改,或已經通知其所有觀察者其最近更改,以便 hasChanged方法現在將返回 false 。
void notifyObservers()
如果該對象發生了變化,由所示 hasChanged方法,則通知其所有觀察者,並調用 clearChanged方法來指示該對象不再改變。
void notifyObservers(Object arg)
如果該對象發生了變化,由所示 hasChanged方法,則通知其所有觀察者,並調用 clearChanged方法來指示該對象不再改變。
屬性
1)Observable含有一個boolean型的變量changed,代表是否發生改變,Observable類只提供這個boolean值來表明是否發生變化,而不定義什麼叫變化,這是因爲每個業務中對變化的具體定義不一樣,因此需要子類來判斷是否發生變化;該變量既提供了一種抽象(變與不變),同時提供了一種觀察者更新狀態的可延遲加載,通過後面的notifyObservers方法分析可知觀察者是否會調用update方法,依賴於changed變量,因此即使被觀察者在邏輯上發生改變了,只要不調用setChanged,update是不會被調用的。如果我們在某些業務場景不需要頻繁觸發update,則可以適時調用setChanged方法來延遲刷新。
2)Observable含有一個集合類Vector,該類泛型爲Observer,主要用來存放所有觀察自己的對象的引用,以便在更新時可以遍歷集合中的觀察者,逐個調用update方法。
方法
1)操作changed變量的方法爲setChanged()設置變化狀態,clearChanged()清除變化狀態,這兩個的訪問權限都是protected,表明這兩個方法由子類去調用,由子類來決定是否告訴觀察者發生變化了,什麼時候變化消失。都有同步關鍵字保證變量的讀寫操作線程安全。
2)操作Vector類型變量obs的方法爲addObserver(Observer o), deleteObserver(Observer o), deleteObservers(),countObservers(),這四個方法分別實現了動態添加觀察者,刪除觀察者,刪除所有觀察者,獲取觀察者數量。四個方法的訪問權限都是公有的,這是提供給調用者的方法,讓調用者來實時動態的控制哪些觀察者來觀察該被觀察對象。
3)公開的方法notifyObservers(),notifyObservers(Object arg),該方法由調用者來操作,用來通知所有的觀察者需要做更新操作了。
四、具體實例
“在學校,上課鈴聲響了,學生進教師上課;下課鈴聲響了,學生下課休息;放學鈴聲響了,學生放學。”
實例分析
涉及的對象包括:鈴聲和學生。學生根據鈴聲的變化進行相應的響應(上課、下課、放學)。不難發現,學生是觀察者,鈴聲是學生觀察的
對象。因此需要建立一個觀察者類Listener實現Observer接口,並根據鈴聲的變化,進行上課、下課、放學。還需要創建一個觀察目標,繼承Observable。
代碼
import java.util.Observable;
import java.util.Observer;
/**
* 觀察者
* @author JavaProgress
* date 2020/1/7 19:26
*/
public class Listener implements Observer {
/**
* 學生姓名
*/
private String name;
public Listener(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
switch (arg.toString()) {
case "1":
attendClass();
break;
case "-1":
afterClass();
break;
case "0":
afterSchool();
break;
}
}
/**
* 上課
*/
private void attendClass() {
System.out.println(name + " 上課了");
}
/**
* 下課
*/
private void afterClass() {
System.out.println(name + " 下課了");
}
/**
* 放學
*/
private void afterSchool() {
System.out.println(name + " 下課了");
}
}
import java.util.Observable;
/**
* 觀察目標
*
* @author JavaProgress
* date 2020/1/7 19:36
*/
public class RingSubject extends Observable {
/**
* 鈴聲
*
* @param num 1上課;-1下課;0放學
*/
public void ring(String num) {
switch (num) {
case "1":
System.out.println("上課鈴響了");
break;
case "-1":
System.out.println("下課鈴響了");
break;
case "0":
System.out.println("放學鈴響了");
break;
}
//改變
setChanged();
//通知觀察者
notifyObservers(num);
}
}
/**
* 觀察者模式測試
*
* @author JavaProgress
* date 2020/1/7 19:43
*/
public class Test {
public static void main(String[] args) {
//創建觀察目標
RingSubject ringSubject = new RingSubject();
for (int i = 0; i < 50; i++) {
//創建觀察者
Listener listener = new Listener(String.valueOf(i + 1));
//觀察目標添加觀察者
ringSubject.addObserver(listener);
}
//調用,鈴聲響起
ringSubject.ring("0");
}
}
五、總結
5.1 觀察者的優點
5.1.1、觀察者模式可以實現表示層和數據邏輯層的分離,並定義了穩定的消息更新傳遞機制,抽象了更新接口,使得可以有各種各樣不同的表示層作爲具體觀察者角色。
5.1.2、觀察者模式在觀察目標和觀察者之間建立一個抽象的耦合。
5.1.3、觀察者模式支持廣播通信。
5.1.4、觀察者模式符合“開閉原則”的要求。
5.2 觀察者的缺點
5.2.1、如果一個觀察目標對象有很多直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
5.2.2、如果在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能導致系統崩潰。
5.2.3、觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。
5.3 觀察者的使用場景
5.3.1、一個抽象模型有兩個方面,其中一個方面依賴於另一個方面。將這些方面封裝在獨立的對象中使它們可以各自獨立地改變和複用。
5.3.2、一個對象的改變將導致其他一個或多個對象也發生改變,而不知道具體有多少對象將發生改變,可以降低對象之間的耦合度。
5.3.3、一個對象必須通知其他對象,而並不知道這些對象是誰。
5.3.4、需要在系統中創建一個觸發鏈,A對象的行爲將影響B對象,B對象的行爲將影響C對象……,可以使用觀察者模式創建一種鏈式觸發機制。