設計模式介紹文章層出不窮,但深度研究型文章仍顯稀缺,實屬憾事。本文旨在拋磚引玉,具體分析 Java JDK 內建 Observer 設計模式(下文若無特別說明,Observer 設計模式即指 JDK 內建實作)的實作機制。
在 GOF《設計模式》中,Observer 屬於行為型模式。JDK 提供的實作由 java.util.Observable 類別與 java.util.Observer 介面組成。從名稱即可看出兩者扮演的角色:Observer 為觀察者角色,Observable 為被觀察目標角色。
Observable 封裝了 Subject 基本功能:註冊觀察者、註銷觀察者等。這些功能是任何扮演 Observable 角色的類別都必須實作的,JDK 將這些通用功能專門封裝在一個類別中,設計合理。通常我們的類別只要繼承 Observable 即可成為 Observable 角色類別,使用非常簡單。
專案實際開發往往更複雜。Java 不支援多重繼承,在很多時候成為使用 Observer 模式的絆腳石。例如,我們設計的一個類別已經繼承自某個類別,此時想同時讓它扮演 Observable 角色就變得棘手。如何達成「多重繼承」效果是擺在面前的難題。先分析 Observable 類別。
Observable 必須「有變化」才能觸發通知觀察者,這是其本質體現。查看原始碼可知一二:
//……省略……
private boolean changed = false;
//……省略……
public void notifyObservers(Object arg) {
//……省略……
Object[] arrLocal;
synchronized (this) {
//……省略……
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
//……省略……
}
protected synchronized void setChanged() {
changed = true;
}
protected synchronized void clearChanged() {
changed = false;
}
關鍵在於 notifyObservers(Object arg) 方法中的 if (!changed) return;。若 changed 屬性為 false,將直接返回,根本不會觸發通知。且 changed 初始化為 false,意味著若不主動設為 true,不會有任何變化,也就起不到「通知」作用。因此,設定 changed 屬性值是應用 JDK Observer 模式的關鍵。唯一入口是 setChanged()。下面分析 changed 屬性及相關方法 setChanged() 和 clearChanged()。
Observable#changed 初始值為 false 很好理解。細心讀者會注意到 setChanged() 和 clearChanged() 修飾符都是 protected,而非 public。這是否必要且合理?答案是肯定的。
前文已提到,setChanged() 是設定 changed 的唯一入口,定義為 protected 意味著透過建立 Observable 物件再設定 changed 將變得不可能。從這意義上說,要應用 Observer 模式,必須繼承 Observable 類別。為什麼不能定義成 public?似乎定義成 public 更方便設定值?看 Observable 相關程式碼:
//……省略……
public void notifyObservers(Object arg) {
//……省略……
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
這段程式碼表達:找出所有已註冊的 Observer,逐個進行「通知」,透過呼叫 Observer#update(Observable, Object) 方法。注意 update 第一個參數是 this,且此程式碼在 Observable 類別內。這反覆強調:**發出「通知」的,必須是 observable 自己(Observable 類別或其子類別),其他任何類別都不行。**
這意味著我們的 observable 類別繼承 Observable 類別是必要的。若不繼承而採用組合方式,將無法保證正確傳遞 this。換句話說,組合方式使用 Observable 幾乎毫無意義。同時,修飾符定義為 protected,確保觸發通知只在 Observable 內部進行,不會在其他地方觸發,內聚性極強。若 setChanged() 定義為 public,將無法保證正確「傳遞 this」的硬性要求,不符合「只有 observable 能直接或間接通知 observer」這一 Observer 設計模式的硬性要求。由此可見 JDK 設計思想之深度。
借助 Adapter 設計模式(詳見筆者發表的 Adapter 相關文章)與 Java 支援多介面特性,基本可解決「多重繼承」問題。核心思想是結合繼承/實作與組合達到效果。前文分析已知 Observable 必須繼承使用,不能組合使用,因此只需將需扮演 Observable 角色的類別裝扮成 Adapter 角色,將該類別原繼承的類別裝扮成 Adaptee 角色。示例程式碼:
// 欲充當 observable 角色的類別原始碼:
public class MyObject extends BaseObject {
public MyObject() {}
public void method1() {}
}
// 充當 observable 角色後的程式碼:
public class MyObject extends Observable {
private BaseObject baseObject = null;
public MyObject(BaseObject baseObject) {
this.baseObject = baseObject;
}
}
若上例中 BaseObject 也用到需傳遞「this」的方法,上述組合方式可能失效。這是最糟情況。此時可考慮在 BaseObject 類別這些「瓶頸」處盡量採用介面取代類別(包含抽象類別)來解決。