意圖
定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新
別名:發佈-訂閱模式
觀察者模式的誕生
將一個系統分割成一系列相互協作的類有一個常見的副作用:需要維護相關對象間的致性,我們不希望爲了維持一致性而使各類緊密耦合,因爲這樣降低了它們的可重用性。
說人話就是:
【產品】:開發小哥,我需要你設計一個天氣預報顯示大屏,氣象站會給你發送數據,你需要把它展示到大屏裏,OK嗎?
【開發】:OJBK!秒秒鐘搞定一切!代碼立馬出來!
void getTemperature () {
// 從氣象站獲取發送過來的溫度數據
// getData();
// ................................
// 顯示到大屏裏面去
// showDataToScreen();
// ................................
}
void getMisture () {
// 從氣象站獲取發送過來的溼度數據
// getData();
// ................................
// 顯示到大屏裏面去
// showDataToScreen();
// ................................
}
void getAirindex () {
// 從氣象站獲取發送過來的空氣指數數據
// getData();
// ................................
// 顯示到大屏裏面去
// showDataToScreen();
// ................................
}
【BOSS】:磕大頭!寧是準備每獲取一次數據就把代碼CV一遍嗎?你不累嗎?
【開發】:老大,我一點都不累!就是複製粘貼一下呀!
【BOSS】:如果我現在不需要同步更新天氣指數呢?刪代碼嗎?
【開發】:對啊!一秒鐘就能刪掉!( •̀ ω •́ )✧
【BOSS】:重寫😃
HeadFirst 核心代碼
於是乎,我們開啓了關於設計模式的經典書籍閱讀之旅
/**
* 觀察主題接口
*/
public interface Observable{
public void addObserver(Observer observer); // 添加觀察者
public void removeObserver(Observer observer); // 移除觀察者
public void notifyObservers(WeatherData data); // 通知所有觀察者
}
/**
* 觀察者
*/
public interface Observer {
public abstract void update(WeatherData data);
}
/**
* 天氣主題
*
*/
public class Weather implements Observable {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers(WeatherData data) {
for (Observer observer : observers)
observer.update(data);
}
}
觀察者模式的設計思路:
- Subject 目標(容器)提供註冊和刪除觀察者的接口以及更新接口
- Observer(觀察者)爲那些在目標發生改變時需獲得通知的對象定義一個更新接口
- ConcreteSubject(具體目標)狀態發生改變時,向各個觀察者發出通知
- ConcreteObserver(具體觀察者)實現Observer的更新接口
簡單來說,
- 我們需要一個接口來定義註冊,刪除和更新接口
- 然後由具體的目標(類)實現該接口,並且在類中創建一個容器,存儲需要被通知的對象
- 需要被通知的對象,需要實現Observer接口中的update更新方法
- 將觀察者對象註冊進容器中,當具體目標更新時,調用所有容器類對象的update方法
如果看着有點模棱兩可,就看完本文後,訪問專題設計模式開源項目,裏面有具體的代碼示例,鏈接在最下面
JDK中的觀察者模式
JDK中已經對觀察者模式有具體的實現,代碼非常簡單,如下所示:
具體目標:
public class ObservableApp extends Observable {
private long curr;
public ObservableApp(long curr) {
this.curr = curr;
}
public void change(long newStr) {
this.curr = newStr;
// 更改狀態,發送通知
setChanged();
notifyObservers(newStr);
}
@Override
protected synchronized void setChanged() {
super.setChanged();
}
}
具體觀察者:
public class ObserverA implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println(MessageFormat.format("ObserverA -> {0} changed, Begin to Work. agr is:{1}", o.getClass().getSimpleName(), arg));
}
}
Main方法:
public class App {
public static void main(String[] args) throws InterruptedException {
ObservableApp app = new ObservableApp(System.currentTimeMillis());
System.out.println(app.getCurr());
app.addObserver(new ObserverA());
app.addObserver(new ObserverB());
Thread.sleep(1000);
long curr = System.currentTimeMillis();
app.change(curr);
System.out.println(app.getCurr());
}
}
// 輸出如下:
// 1589688733464
// ObserverB -> ObservableApp changed, Begin to Work. agr is:1,589,688,734,469
// ObserverA -> ObservableApp changed, Begin to Work. agr is:1,589,688,734,469
// 1589688734469
推模式
通知發給觀察者,通知攜帶參數,這就是推,對應JDK方法中的:notifyObservers(Object arg)
拉模式
通知發給觀察者,通知不攜帶參數,需要觀察者自己去主動調用get方法獲取數據,這就是拉
對應JDK方法中的:notifyObservers(),僅告知觀察者數據發生了變化,至於數據的詳情需要觀察者主動到主題中pull數據
拉模型強調的是目標不知道它的觀察者,而推模型假定目標知道一些觀察者的需要的信息。推模型可能使得觀察者相對難以複用,因爲目標對觀察者的假定可能並不總是正確的。另一方面。拉模型可能效率較差,因爲觀察者對象需在沒有目標對象幫助的情況下確定什麼改變了。
遵循的設計原則
- 封裝變化
- 在觀察者模式中會經常改變的是主題的狀態,以及觀察者的數目和類型
- 我們可以改變依賴於主題狀態的對象,但是不必改變主題本身,這便是提前規劃
- 針對接口編程
- 主題和觀察者都使用了接口
- 觀察者利用主題的接口向主題註冊
- 主題利用觀察者接口通知觀察者,可以使兩者之間正常交互,同時又具有鬆耦合的特性
- 多使用組合
- 觀察者模式利用組合將許多觀察者組合進主題中
- 它們之間的關係並不是通過繼承得到,而是在運行時動態改變
什麼場景適合使用
當對象間存在一對多關係時,則使用觀察者模式(Observer Pattern),比如,當一個對象被修改時,則會自動通知它的依賴對象。觀察者模式屬於行爲型模式
Code/生活中的實際應用
- 比如微信公衆號中的訂閱關注,訂閱後,公衆號發佈文章會實時分發給各個賬號
- 又如,我們使用Keep跑步時,如果你跑的足夠激情,它會提示你,恭喜你,你已經打破了五公里的最好記錄!這樣的語音提醒一定是觸發式,而不是實時去檢測吧?(實時檢測沒有意義,浪費性能)這裏就可以利用觀察者模式進行設計和解耦
最後
附上GOF一書中對於觀察者模式的UML圖:
相關代碼鏈接
- 兼顧了《HeadFirst》以及《GOF》兩本經典數據中的案例
- 提供了友好的閱讀指導