动态

详情 返回 返回

《Head First設計模式》讀書筆記 —— 觀察者模式 - 动态 详情

《Head First設計模式》讀書筆記
相關代碼:Vks-Feng/HeadFirstDesignPatternNotes: Head First設計模式讀書筆記及相關代碼

  • 讓你的對象知悉現狀,不會錯過對象感興趣的事
  • 對象甚至在運行時可決定是否要繼續被通知
  • JDK中使用最多的模式之一

本節例子

HF觀察者模式用例.png

系統三部分:

  • 氣象站:獲取實際氣象數據的物理裝置
  • WeatherData對象(追蹤來自氣象站的數據,並更新佈告板)
  • 佈告板:顯示目前天氣狀況給用户看

物理氣象站→WeatherData對象(已完成)
WeatherData對象→及時更新佈告板

我們的工作:建立一個應用,利用WeatherData對象取得數據,並更新三個佈告板(目前狀況、氣象統計、天氣預報)

|-----------------------|
|      WeatherData      |
|-----------------------|
|   getTemperature()    |
|   getHumidity()       |
|   getPressure()       |
|   measure-            |
|   mentsChanged()      |
|   //其他方法           |
|-----------------------|

一旦氣象測量更新,mentsChanged會被調用

錯誤示範

public class WeatherData {
	public void measurementsChanged() {
		float temp = getTemperature();
		float humidity = getHumidity();
		float pressure = getPressure();
		currentConditionDisplay.update(temp, humidity, pressure);
		statisticsDisplay.update(temp, humidity, pressure);
		forecastDisplay.update(temp, humidity, pressure);
	}
}

認識觀察者模式

報紙類比:

  1. 報社的業務就是出版報紙
  2. 向報社訂閲報紙,當有新報紙出版時,就會送來。只要是訂户,就會一直收到報紙
  3. 不再想看時,取消訂閲,就不會再送
  4. 只要報社還運行,就會一直有人向他們訂閲/取消訂閲

出版者+訂閲者=觀察者模式

HeadFirst設計模式2-觀察者模式

出版者↔️主題(subject)
訂閲者↔️觀察者(Observer)

觀察者模式

觀察者模式使用

  1. 對象告知主題,想要成為觀察者 —— 註冊(訂閲)
  2. 對象成為觀察者,可以接收到通知
  3. 對象要求從觀察者把自己除名(取消訂閲),主題接收到請求,將其除名

定義觀察者模式

觀察者模式定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知並自動更新。

  • 定義了一系列對象之間的一對多關係
  • 狀態改變→依賴者都會接到通知

類圖

觀察者模式類圖

Q&A

Q:這和一對多的關係有何關聯?
A:利用觀察者模式,主題是具有狀態的對象,並且可以控制這些狀態,也就是説,有“一個”具有狀態的主題。另一方面,觀察者使用這些狀態,雖然這些狀態並不屬於他們,有許多的觀察者,以來主題來告訴他們狀態何時改變了。這就產生了一個關係:“一個”主題對“多個”觀察者的關係。

Q:其間的依賴是如何產生的?
A:主題是真正擁有數據的人,觀察者是主題的依賴者,在數據變化時更新,這樣比起讓許多對象控制同一份數據來,可以得到更乾淨的OO設計。

鬆耦合的威力

當兩個對象之間鬆耦合,它們依然可以交互,但是不太清楚彼此的細節。
觀察者模式提供了一種對象設計,讓主題和觀察者之間鬆耦合。

  • 主題唯一依賴的東西就是一個實現Observer接口的對象列表,所以我們怎麼操作其中的觀察者對象(增刪改),不會影響到主題。
  • 當有新類型的觀察者出現時,不需要修改主題的代碼,只需要該類去實現觀察者接口,然後註冊為觀察者即可。主題不在乎別的,只會發送給所有實現了觀察者接口的對象。
  • 可以獨立地複用主題或觀察者,其他地方需要使用時也可以輕易地複用,因為二者並非緊耦合
  • 因為主題和觀察者二者是鬆耦合的,所以只要它們之間的接口仍被遵守,就可以自由地改變它們

設計原則4 :為了交互對象之間的鬆耦合設計而努力

鬆耦合的設計之所以能讓我們建立有彈性的OO系統,能夠應對變化,是因為對象之間的相互依賴降到了最低

氣象站設計

氣象站設計

“拉”與“推”之辯

拉:指觀察者主動向主題獲取數據
推:指主題將信息發給觀察者

“拉”:

  • 拉的好處:
    • 主題不可能事先料到各種觀察者的需求,拉可以讓各種觀察者去獲取目標信息,而非被動接收到一大坨信息(其中可能包含不需要的內容)
    • 當主題需要擴展功能時,不用修改和更新對每位觀察者的調用,只需要改變自己的getter、setter即可
  • 拉的缺點:
    • 主題必須門户大開,封裝性被打破,面臨被大肆挖掘數據的風險
    • 觀察者可能會多次調用才能拼湊其所需信息

“推”:與“拉”的優缺相對應,略

Java內置的觀察者模式

( Java 9 及更高版本中,Observable 類被標記為 @Deprecated,意味着它仍然可以使用,但不推薦使用,未來可能會被移除。)
當然,我們還是可以從中學到一些東西的😛

使用

java.util包中包含最基本的Observer接口和Observer類,具備許多功能,使用上更方便,甚至可以使用“推”或“拉”的方式傳送數據

Java內置觀察者模式

圖中重點信息:

  • “主題”(Subject)也可稱為“可觀察者”(Observable)
  • Observable是一個“類”,而不是一個“接口”

如何運作

  1. 把對象變成觀察者
    • 實現觀察者接口(java.util.Observer),然後調用其addObserver()方法
  2. 可觀察者送出通知
    • 產生可觀察類
    • 先調用setChange()方法,標記狀態已經改變的事實
    • 調用兩種notifyObservers()方法中的一個(notifyObserver()notifyObservers(Object arg))
  3. 如何接收通知
    • update(Observable o, Object arg)
    • o:主題本身是第一個變量,從而讓觀察者知道是哪個主題發來的通知
    • arg:傳入notifyObservers()的數據對象,如果沒有説明為空

setChange()

setChange()用來標記狀態已經改變的事實,好讓notifyObservers()知道當它被調用時應該更新觀察者。
如果調用notifyObservers()之前沒有調用setChange(),觀察者就不會通知被通知
偽代碼如下:

setChange() {
	changed = true;
}

notifyObservers(Object arg) {
	if (changed) {
		for every observer on the list {
			call updata(this, arg);
		}
		changed = false;
	}
}

notifyObservers() {
	notifyObservers(null);
}

為什麼要設計setChange()
setChange()可以讓你在更新觀察者時,有更多的彈性,可以更適當地通知觀察者

  • 例如:氣象站的測量十分敏鋭,過於微小的變動也會被捕捉,這會造成主題不斷地通知觀察者。但是我們可以設置在温度變化超過半度才通知觀察者(即超過半度再調用setChange()),使得每次通知更有效

黑暗面

  • Observable是一個“類”而非“接口”,其實現有很多問題,限制了他的使用和複用
  • 由於java不支持多繼承,如果某類想具有Observable和另一個超類的行為,會比較麻煩,這限制了Observable的複用能力
    • 而增加複用能力正是使用模式最原始的動機
  • setChange()方法被保護起來了(protect),這意味着除非繼承自Observable,否則無法創建Observable實例並組合到自己的對象中
    • 違反了第二個設計原則:“多用組合,少用繼承”

總結

OO基礎

  • 抽象

OO原則

  • 封裝變化
  • 多用組合,少用繼承
  • 針對接口編程,不針對實現編程
  • 為交互對象之間的鬆耦合設計而努力
    • 鬆耦合設計更有彈性,更能應對變化

OO模式

  • 觀察者模式——在對象之間定義一對多的依賴,這樣一來,當一個對象改變狀態,依賴它的對象都會收到通知,並自動更新
    • 觀察者模式的代表人物——MVC

要點

  • 觀察者模式定義了對象之間一對多的關係
  • 主題(可觀察者)用一個共同的接口來更新觀察者
  • 觀察者和可觀察者之間用鬆耦合方式結合(loosecoupling),可觀察者不知道觀察者的細節,只知道觀察者實現了觀察者接口
  • 使用此模式時,你可從被觀察者處推(push)或拉(pull)數據(然而,推的方式被認為更“正確”)
  • 有多個觀察者時,不可以依賴特定的通知次序(即通知順序不應該影響程序的正確性)
    • 一旦觀察者/可觀察者的實現有所改變,通知次序就會改變,很可能就會產生錯誤的結果,這違背了鬆耦合
user avatar kuailedehuanggua 头像 buguge 头像
点赞 2 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.