《Head First設計模式》讀書筆記
相關代碼:Vks-Feng/HeadFirstDesignPatternNotes: Head First設計模式讀書筆記及相關代碼
- 讓你的對象知悉現狀,不會錯過對象感興趣的事
- 對象甚至在運行時可決定是否要繼續被通知
- JDK中使用最多的模式之一
本節例子
系統三部分:
- 氣象站:獲取實際氣象數據的物理裝置
- 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);
}
}
認識觀察者模式
報紙類比:
- 報社的業務就是出版報紙
- 向報社訂閲報紙,當有新報紙出版時,就會送來。只要是訂户,就會一直收到報紙
- 不再想看時,取消訂閲,就不會再送
- 只要報社還運行,就會一直有人向他們訂閲/取消訂閲
出版者+訂閲者=觀察者模式
HeadFirst設計模式2-觀察者模式
出版者↔️主題(subject)
訂閲者↔️觀察者(Observer)
觀察者模式使用
- 對象告知主題,想要成為觀察者 —— 註冊(訂閲)
- 對象成為觀察者,可以接收到通知
- 對象要求從觀察者把自己除名(取消訂閲),主題接收到請求,將其除名
定義觀察者模式
觀察者模式定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知並自動更新。
- 定義了一系列對象之間的一對多關係
- 狀態改變→依賴者都會接到通知
類圖
Q&A
Q:這和一對多的關係有何關聯?
A:利用觀察者模式,主題是具有狀態的對象,並且可以控制這些狀態,也就是説,有“一個”具有狀態的主題。另一方面,觀察者使用這些狀態,雖然這些狀態並不屬於他們,有許多的觀察者,以來主題來告訴他們狀態何時改變了。這就產生了一個關係:“一個”主題對“多個”觀察者的關係。
Q:其間的依賴是如何產生的?
A:主題是真正擁有數據的人,觀察者是主題的依賴者,在數據變化時更新,這樣比起讓許多對象控制同一份數據來,可以得到更乾淨的OO設計。
鬆耦合的威力
當兩個對象之間鬆耦合,它們依然可以交互,但是不太清楚彼此的細節。
觀察者模式提供了一種對象設計,讓主題和觀察者之間鬆耦合。
- 主題唯一依賴的東西就是一個實現Observer接口的對象列表,所以我們怎麼操作其中的觀察者對象(增刪改),不會影響到主題。
- 當有新類型的觀察者出現時,不需要修改主題的代碼,只需要該類去實現觀察者接口,然後註冊為觀察者即可。主題不在乎別的,只會發送給所有實現了觀察者接口的對象。
- 可以獨立地複用主題或觀察者,其他地方需要使用時也可以輕易地複用,因為二者並非緊耦合
- 因為主題和觀察者二者是鬆耦合的,所以只要它們之間的接口仍被遵守,就可以自由地改變它們
設計原則4 :為了交互對象之間的鬆耦合設計而努力
鬆耦合的設計之所以能讓我們建立有彈性的OO系統,能夠應對變化,是因為對象之間的相互依賴降到了最低
氣象站設計
“拉”與“推”之辯
拉:指觀察者主動向主題獲取數據
推:指主題將信息發給觀察者
“拉”:
- 拉的好處:
- 主題不可能事先料到各種觀察者的需求,拉可以讓各種觀察者去獲取目標信息,而非被動接收到一大坨信息(其中可能包含不需要的內容)
- 當主題需要擴展功能時,不用修改和更新對每位觀察者的調用,只需要改變自己的getter、setter即可
- 拉的缺點:
- 主題必須門户大開,封裝性被打破,面臨被大肆挖掘數據的風險
- 觀察者可能會多次調用才能拼湊其所需信息
“推”:與“拉”的優缺相對應,略
Java內置的觀察者模式
( Java 9 及更高版本中,Observable 類被標記為 @Deprecated,意味着它仍然可以使用,但不推薦使用,未來可能會被移除。)
當然,我們還是可以從中學到一些東西的😛
使用
java.util包中包含最基本的Observer接口和Observer類,具備許多功能,使用上更方便,甚至可以使用“推”或“拉”的方式傳送數據
圖中重點信息:
- “主題”(Subject)也可稱為“可觀察者”(Observable)
- Observable是一個“類”,而不是一個“接口”
如何運作
- 把對象變成觀察者
- 實現觀察者接口(java.util.Observer),然後調用其
addObserver()方法
- 實現觀察者接口(java.util.Observer),然後調用其
- 可觀察者送出通知
- 產生可觀察類
- 先調用
setChange()方法,標記狀態已經改變的事實 - 調用兩種
notifyObservers()方法中的一個(notifyObserver()或notifyObservers(Object arg))
- 如何接收通知
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)數據(然而,推的方式被認為更“正確”)
- 有多個觀察者時,不可以依賴特定的通知次序(即通知順序不應該影響程序的正確性)
- 一旦觀察者/可觀察者的實現有所改變,通知次序就會改變,很可能就會產生錯誤的結果,這違背了鬆耦合