1. 程式人生 > >觀察者模式(Observer Pattern)。

觀察者模式(Observer Pattern)。

定義

觀察者模式也叫做釋出訂閱模式(Publish/subscribe),他是一個在專案中經常使用的模式,其定義如下:

定義物件間一種一對多的依賴關係,使得每當一個物件改變狀態,則所有依賴於他的物件都會得到通知並被自動更新。

我們現來解釋一下觀察者模式的幾個角色名稱:

  • Subject被觀察者

定義被觀察者必須實現的職責,他必須能夠動態的增加、取消觀察者。他一般是抽象者或者是實現類,僅僅完成作為被觀察者必須實現的職責:管理觀察者並通知觀察者。

  • Observer觀察者

觀察者接收到訊息後,即進行update(更新方法)操作,對接收到的資訊進行處理。

  • ConcreteSubject具體的被觀察者

定義被觀察者自己的業務邏輯,同時定義對哪些事件進行通知。

  • ConcreteObserver具體的觀察者

每個觀察在接受到訊息後的處理反應是不同,各個觀察者有自己的處理邏輯。

通用程式碼

各個名詞介紹完畢,我們來看看各自的通用程式碼,先看被觀察者角色,如下所示。

public abstract class Subject {
	// 定義一個觀察者陣列
	private Vector<Observer> obsVector = new Vector<Observer>();

	/**
	 * 增加一個觀察者
	 * 
	 * @param o
	 */
	public void addObserver(Observer o) {
		this.obsVector.add(o);
	}

	/**
	 * 刪除一個觀察者
	 * 
	 * @param o
	 */
	public void delObserver(Observer o) {
		this.obsVector.remove(o);
	}

	/**
	 * 通知所有觀察者
	 */
	public void notifyObservers() {
		for (Observer o : obsVector) {
			o.update();
		}
	}
}

被觀察者的職責非常簡單,就是定義誰能夠觀察,誰不能觀察,程式中使用ArrayList和Vector沒有太大的差別,ArrayList是執行緒非同步,不安全;Vector是執行緒同步,安全——就這點區別。我們再來看具體的被觀察者,如下所示。

public class ConcreteSubject extends Subject {
	/**
	 * 具體的業務
	 */
	public void doSomething() {
		/*
		 * do something
		 */
		super.notifyObservers();
	}
}

我們現在看到的是一個純淨的觀察者,在具體專案中該類有很多的變種。我們再來看觀察者角色,如下所示。

public interface Observer {
	/**
	 * 更新方法
	 */
	void update();
}

觀察者一般是一個介面,每一個實現該介面的實現類都是具體觀察者,如下所示。

public class ConcreteObserver implements Observer {

	@Override
	public void update() {
		System.out.println("接收到資訊,並進行處理!");
	}

}

那其他模組是怎麼來呼叫的呢?我們編寫一個Client類來描述,如下所示。

public class Client {
	public static void main(String[] args) {
		// 建立一個被觀察者
		ConcreteSubject subject = new ConcreteSubject();
		// 定義一個觀察者
		Observer obs = new ConcreteObserver();
		// 觀察者觀察被觀察者
		subject.addObserver(obs);
		// 被觀察者開始活動了
		subject.doSomething();
	}
}

優點

  • 觀察者和被觀察者之間是抽象耦合

如此設計,則不管是增加觀察者還是被觀察者都非常容易擴充套件,而且在Java中都已經實現的抽象層次的定義,在系統擴充套件方面更是得心應手。

  • 建立一套觸發機制

根據單一職責原則,每個類的職責是單一的,那麼怎麼把各個單一的職責串連成真實世界的複雜的邏輯關係呢?比如,我們去打獵,打死了一隻母鹿,母鹿有三個幼崽,因失去了母鹿而餓死,屍體又被兩隻禿鷹爭搶,因非陪不均,禿鷹開始鬥毆,然後羸弱的禿鷹死掉,生存下來的禿鷹,則因此擴大了地盤......這就是一個觸發機制,形成了一個觸發鏈。觀察者模式可以完美的實現這裡的鏈條形式。

缺點

  • 觀察者模式需要考慮一下開發效率和執行效率問題,一個被觀察者,多個觀察者,開發和除錯就會比較複雜,而且在Java中訊息的通知預設是順序執行,一個觀察者卡殼,會影響整體的執行效率。在這種情況下,一般考慮採用非同步的方式。
  • 多級觸發時的效率更是讓人擔憂,大家在設計時注意考慮。

使用場景

  • 關聯行為場景。需要注意的是,關聯行為是可拆分的,而不是“組合”關係。
  • 事件多級觸發場景。
  • 跨系統的訊息交換場景,如訊息佇列的處理機制。

注意事項

使用觀察者模式也有以下兩個重點問題要解決。

  • 廣播鏈的問題

如果你做過資料庫的觸發器,你應該知道有一個觸發器鏈的問題,比如表A上寫了一個觸發器,內容是一個欄位更新後更新表B的一條資料,而表B上也有個觸發器,要更新表C,表C也有觸發器......完蛋了,這個資料庫基本上就毀掉了!我們的觀察者模式也是一樣的問題,一個觀察者可以有雙重身份,即是觀察者,也是被觀察者,這沒什麼問題,但是鏈一旦建立,這個邏輯就比較複雜,可維護性非常差,根據經驗建議,在一個觀察者模式中最多出現一個物件既是觀察者也是被觀察者,也就是說訊息最多轉發一次(傳遞兩次),這還是比較好控制的。

注意:他和責任鏈模式的最大區別就是觀察者廣播鏈在傳播的過程中訊息是隨時更改的,他是由相鄰的兩個節點協商的訊息結構;而責任鏈模式在訊息傳遞過程中基本上保持訊息不可變,如果要改變,也只是在原有的訊息上進行修正。

  • 非同步處理問題

這個EJB是一個非常好的例子,被觀察者發生動作了,管擦或者要做出迴應,如果觀察者比較多,而且處理時間比較長怎麼辦?那就用非同步唄,非同步處理就要考慮執行緒安全和佇列問題,這個有時間看看Message Queue,就會有更深的瞭解。

擴充套件

Java世界中的觀察者模式

JDK中提供了:java.util.Observable實現類和java.util.Observer介面。

專案中真實的觀察者模式

為什麼要說“真實”呢?因為我們剛剛講的那些是太標準的模式了,在系統設計中會對觀察者模式進行改造或改裝,主要在以下三個方面。

  • 觀察者和被觀察者之間的訊息溝通

被觀察者狀態改變會觸發觀察者的一個行為,同時會傳遞一個訊息給觀察者,這是正確的,在實際中一般的做法是:觀察者的update方法接受兩個引數,一個是被觀察者,一個是DTO(Data Transfer Object,據傳輸物件),DTO一般是一個純潔的JavaBean,由被觀察者生成,由觀察者消費。

當然如果考慮到遠端傳輸,一般訊息是以XML格式傳遞。

  • 觀察者響應方式

我們這樣來想一個問題,觀察者是一個比較複雜的邏輯,他要接受被觀察者傳遞過來的資訊,同時還要對他們進行邏輯處理,在一個觀察者多個被觀察者的情況下,效能就需要提到日程上來考慮了,為什麼呢?如果觀察者來不及響應,被觀察者的執行時間是不是也會被拉長?那現在的問題就是:觀察者如何快速響應?有兩個辦法:一是採用多執行緒技術,甭管是被觀察者啟動執行緒還是觀察者啟動執行緒,都可以明顯的提高系統性能,這也就是大家通常所說的非同步架構;二是快取技術,甭管你誰來,我已經準備了足夠的資源給你了,我保證快速響應,這當然也是一種比較好的方案,代價就是開發難度很大,而且壓力測試要做到足夠充分,這種方案也就是大家說的同步架構。

  • 被觀察者儘量自己做主

這是什麼意思呢?被觀察者的狀態改變是否一定要通知觀察者呢?不一定吧,在設計的時候要靈活考慮,否則會加重觀察者的處理邏輯,一般是這樣做的,對被觀察者的業務邏輯doSomething方法實現過載,如增加一個doSometing(boolean isNotifyObs)方法,決定是否通知觀察者,而不是在訊息到達觀察者時才判斷是否要消費。

訂閱釋出模型

觀察者模式也叫做釋出/訂閱模型(Publish/Subscribe),如果你做過EJB(Enterprise JavaBean)的開發,這個你絕對不會陌生。EJB2是個折騰死人不償命的玩意兒,寫個Bean要實現,還要繼承,再加上那一堆的配置檔案,小專案還湊合,你要知道用EJB開發的基本上都不是小專案,到最後是每個專案成員都在罵EJB這個忽悠人的東西;但是EJB3是個非常優秀的框架,還是算比較輕量級,寫個Bean只要加個Annotation就成了,配置檔案減少了,而且也引入了依賴注入的概念,雖然只是EJB2的翻版,但是畢竟還是前進了一步。在EJB中有3個型別的Bean:Session Bean、Entity Bean和MessageDriven Bean,我們這裡來說一下MessageDriver Bean(一般簡稱為MDB),訊息驅動Bean,訊息的釋出者(Provider)釋出一個訊息,也就是一個訊息驅動Bean,通過EJB容器(一般是Message Queue訊息佇列)通知訂閱者做出迴應,從原理上看很簡單,就是觀察者模式的升級版,或者說是觀察者模式的BOSS版。

最佳實踐

觀察者模式在實際專案和生活中非常常見,我們舉幾個經常發生的例子來說明。

  • 檔案系統

比如,在一個目錄下新建立一個檔案,這個動作會同時通知目錄管理器增加該目錄,並通知磁碟管理器減少1KB的空間,也就說“檔案”是一個被觀察者,“目錄管理器”和“磁碟管理器”則是觀察者。

  • 貓鼠遊戲

夜裡貓叫一聲,家裡的老鼠撒腿就跑,同時也吵醒了熟睡的主人,這個場景中,“貓”就是被觀察者,老鼠和人則是觀察者。

  • ATM取錢

比如你到ATM機器上取錢,多次輸錯密碼,卡就會被ATM吞掉,吞卡動作發生的時候,會觸發哪些事件呢?第一,攝像頭連續快拍,第二,通知監控系統,吞卡發生;第三,初始化ATM機螢幕,返回最初狀態。一般前兩個動作都是通過觀察者模式來完成的,後一個動作是異常來完成。

  • 廣播收音機

電臺在廣播,你可以開啟一個收音機,或者兩個收音機來收聽,電臺就是被觀察者,收音機就是觀察者。