以前做一個界面的時候常常會遇到這樣的尷尬情況:希望保留各個獨立的組件(類),但又希望它們之間能夠相互通信。譬如Windows中的Explorer,我們希望鼠標點擊左邊是樹型目錄的一個節(jié)點,右邊的文件瀏覽能及時列出該節(jié)點目錄下的文件和子目錄,類似這樣一個簡單的應(yīng)用,如果只有一個類繼承JFrame,而樹型組件和瀏覽文件的面板作為成員,就像:
public class MainFrame extends JFrame { JPanel treePanel; JTree tree; JPanel filePanel; ... } |
這樣當然容易在兩者之間傳遞消息,但是可擴展性較差。通常容易想到的是兩種辦法:在一個組件里保留另一個組件類型的成員,初始化時作為參數(shù)傳入引用,比如:
class TreePanel extends JPanel { JTree tree; ... } class FilePanel extends JPanel { public FilePanel(JTree tree){...} ... } |
或者將一個組件線程化,不停地監(jiān)聽另一個組件的變化,然后作出相應(yīng)的反映,比如:
class TreePanel extends JPanel { JTree tree; ... } class FilePanel extends JPanel implements Runnable { public void run() { while (true) { //監(jiān)聽tree的變化 } ... } ... } |
這樣確實可以達到我們的目的,但是第一種方案顯然不利于松散耦合,第二種方案比較占用系統(tǒng)資源。通過學(xué)習(xí)設(shè)計模式,我們發(fā)現(xiàn)可以用Observer模式來解決這個問題。
1. Observer模式
設(shè)計模式分為創(chuàng)建型、結(jié)構(gòu)型和行為型,其中行為型模式專門處理對象間通信,指定交互方式等,Observer模式就是屬于行為型的一種設(shè)計模式。按照“四人幫”(Gang of Four)在“Design Patterns”里的定義,Observer模式“定義對象間的一種一對多的依賴關(guān)系,當一個對象的狀態(tài)發(fā)生改變時, 所有依賴于它的對象都得到通知并被自動更新”,這個描述正好符合我們對“組件通信”問題的需求。讓我們先看看Observer模式的結(jié)構(gòu):

其中各元素的含義如下:
- Subject:被觀察的目標的抽象接口,它提供對觀察者(Observer)的注冊、注銷服務(wù),Notify方法通知Observer目標發(fā)生改變;
- Object:觀察者的抽象接口,Update方法是當?shù)玫絊ubject狀態(tài)變化的通知后所要采取的動作;
- ConcreteSubject:Subject的具體實現(xiàn);
- ConcreteObserver:Observer的具體實現(xiàn)
2. Java中的Observer模式:Observer和Observable
在大致了解了Observer模式的描述之后,現(xiàn)在我們更為關(guān)心的是它在Java中是如何應(yīng)用的。幸運的是,自從JDK 1.0起,就有了專門處理這種應(yīng)用的API,這就是Observer接口和Observable類,它們是屬于java.util包的一部分。看來Java的開發(fā)者們真是深諳設(shè)計模式的精髓,而Java的確是為了真正的面向?qū)ο蠖模呛牵?BR>這里的Observer和Observable分別對應(yīng)設(shè)計模式中的Observer和Subject,對比一下它們定義的方法,痕跡還是相當明顯的:
Observer的方法:
- update(Observable subject, Object arg) 監(jiān)控subject,當subject對象狀態(tài)發(fā)生變化時Observer會有什么響應(yīng),arg是傳遞給Observable的notifyObservers方法的參數(shù);
- addObserver(Observer observer) observer向該subject注冊自己
- hasChanged() 檢查該subject狀態(tài)是否發(fā)生變化
- setChanged() 設(shè)置該subject的狀態(tài)為“已變化”
- notifyObservers() 通知observer該subject狀態(tài)發(fā)生變化
其實在AWT/Swing事件模型中用到了好幾種設(shè)計模式,以前的JDK 1.0 AWT使用的是“基于繼承的事件模型”,在該模型Component類中定義了一系列事件處理方法,如:handleEvent,mouseDown,mouseUp等等,我們對事件的響應(yīng)是通過對組件類繼承并覆蓋相應(yīng)的事件處理方法的手段來實現(xiàn),組件接收到事件向所在容器廣播,沿著容器鏈直到發(fā)現(xiàn)事件被某個容器的handle方法所處理。這種模型有很多缺點,事件的處理不應(yīng)當由事件產(chǎn)生者負責(zé),而且根據(jù)“設(shè)計模式”一書中的原則,“繼承”通常被認為是“對封裝性的破壞”,父子類之間的緊密耦合關(guān)系降低了靈活性,同時繼承容易導(dǎo)致家族樹規(guī)模的龐大,這些都不利于組件可重用。
JDK 1.1以后新的事件模型是被成為“基于授權(quán)的事件模型”,也就是我們現(xiàn)在所熟悉的Listener模型,事件的處理不再由產(chǎn)生事件的對象負責(zé),而由Listener負責(zé),只有被注冊過的Listener才能向組件傳遞事件動作。尤其在Swing組件中設(shè)計MVC結(jié)構(gòu)時用到了Observer模式,眾所周知,MVC表示“模型-視圖-控制器”,即“數(shù)據(jù)-表示邏輯-操作”,其中數(shù)據(jù)可以對應(yīng)多種表示,這樣視圖就處在了observer的地位,而model則是subject。大家所熟悉的JTree和JTable就是這種MVC結(jié)構(gòu):
-----------------------------------------------------
Model View Controller
-----------------------------------------------------
TreeModel JTree TreeModelListener
TableModel JTable TableModelListener
-----------------------------------------------------
4. 簡單的例子
回到本文一開始的那個Explorer的例子,我們考慮做一個簡單的圖片瀏覽器,使樹型選擇組件和圖片瀏覽面板在兩個不同的類中,其中圖片瀏覽面板根據(jù)所選擇的樹的節(jié)點顯示相應(yīng)的圖片,所以圖片瀏覽面板是一個observer,樹是subject。由于Java單根繼承的原因,我們不能同時繼承JPanel和Observable,但可以用對象的組合把一個subject放到我們的類當中,并通過TreeSelectionListener觸發(fā)subject的setChanged方法,并通過notifyObservers方法通知observer。
例子代碼如下:
//LeftPanel.java package com.jungleford.test; import java.awt.BorderLayout; public final class LeftPanel extends JPanel class Sensor extends Observable |
//RightPanel.java package com.jungleford.test; import java.awt.*; public class RightPanel extends JPanel implements Observer |
//MainFrame.java package com.jungleford.test; import java.awt.*; public class MainFrame extends JFrame |
程序運行截圖如下:

啟動界面

點擊Rabbit顯示的圖像

點擊Devestator顯示的圖像
附錄:Observer模式概覽
摘自 設(shè)計模式
意圖 | 定義對象間的一種一對多的依賴關(guān)系,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新。 |
動機 | 將一個系統(tǒng)分割成一系列相互協(xié)作的類有一個常見的副作用:需要維護相關(guān)對象間的移植性。我們不希望為了維持一致性而使各類緊密耦合,因為這樣將降低它們的可重用性。 |
適用性 |
|
結(jié)構(gòu)圖 | ![]() |
參與者 |
|
協(xié)作圖 | ![]() |
效果 | 允許你獨立的改變目標和觀察者。你可以單獨復(fù)用目標對象而無需同時復(fù)用其觀察者,反之亦然。你也可以在不改動目標和其它觀察者的前提下增加觀察者 |
應(yīng)用 | MVC模式 |
相關(guān)模式 |
|
參考資料:
- Design Patterns: Elements of Reusable Object-Oriented Software, by E. Gamma, R. Helm, R. Johnson, J. Vlissides
- IBM developerWorks教程:Java設(shè)計模式101
- Graphic Java 2, Mastering the JFC Volumn I: AWT, by David M. Geary