利用Observer模式實現組件間通信
1.問題的提出
以前做一個界面的時候常常會遇到這樣的尷尬情況:希望保留各個獨立的組件(類),但又希望它們之間能夠相互通信。譬如Windows中的Explorer,我們希望鼠標點擊左邊是樹型目錄的一個節點,右邊的文件瀏覽能及時列出該節點目錄下的文件和子目錄,類似這樣一個簡單的應用,如果只有一個類繼承JFrame,而樹型組件和瀏覽文件的面板作為成員,就像:
這樣當然容易在兩者之間傳遞消息,但是可擴展性較差。通常容易想到的是兩種辦法:在一個組件里保留另一個組件類型的成員,初始化時作為參數傳入引用,比如:
或者將一個組件線程化,不停地監聽另一個組件的變化,然后作出相應的反映,比如:
這樣確實可以達到我們的目的,但是第一種方案顯然不利于松散耦合,第二種方案比較占用系統資源。通過學習設計模式,我們發現可以用Observer模式來解決這個問題。
2.Observer模式
設計模式分為創建型、結構型和行為型,其中行為型模式專門處理對象間通信,指定交互方式等,Observer模式就是屬于行為型的一種設計模式。按照“四人幫”(GangofFour)在“DesignPatterns”里的定義,Observer模式“定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都得到通知并被自動更新”,這個描述正好符合我們對“組件通信”問題的需求。讓我們先看看Observer模式的結構:
其中各元素的含義如下:
Subject:被觀察的目標的抽象接口,它提供對觀察者(Observer)的注冊、注銷服務,Notify方法通知Observer目標發生改變;
Object:觀察者的抽象接口,Update方法是當得到Subject狀態變化的通知后所要采取的動作;
ConcreteSubject:Subject的具體實現;
ConcreteObserver:Observer的具體實現
Observer模式在實現MVC結構時非常有用,為數據和數據表示解耦合。
3.Java中的Observer模式:Observer和Observable
在大致了解了Observer模式的描述之后,現在我們更為關心的是它在Java中是如何應用的。幸運的是,自從JDK1.0起,就有了專門處理這種應用的API,這就是Observer接口和Observable類,它們是屬于java.util包的一部分。看來Java的開發者們真是深諳設計模式的精髓,而Java的確是為了真正的面向對象而生的,呵呵!
這里的Observer和Observable分別對應設計模式中的Observer和Subject,對比一下它們定義的方法,痕跡還是相當明顯的:
Observer的方法:
update(Observablesubject,Objectarg)監控subject,當subject對象狀態發生變化時Observer會有什么響應,arg是傳遞給Observable的notifyObservers方法的參數;
Observable的方法:
addObserver(Observerobserver)observer向該subject注冊自己
hasChanged()檢查該subject狀態是否發生變化
setChanged()設置該subject的狀態為“已變化”
notifyObservers()通知observer該subject狀態發生變化
4.Observer模式在JavaGUI事件模型中應用
其實在AWT/Swing事件模型中用到了好幾種設計模式,以前的JDK1.0AWT使用的是“基于繼承的事件模型”,在該模型Component類中定義了一系列事件處理方法,如:handleEvent,mouseDown,mouseUp等等,我們對事件的響應是通過對組件類繼承并覆蓋相應的事件處理方法的手段來實現,這種模型有很多缺點,事件的處理不應當由事件產生者負責,而且根據“設計模式”一書中的原則,“繼承”通常被認為是“對封裝性的破壞”,父子類之間的緊密耦合關系降低了靈活性,同時繼承容易導致家族樹規模的龐大,這些都不利于組件可重用。
JDK1.1以后新的事件模型是被成為“基于授權的事件模型”,也就是我們現在所熟悉的Listener模型,事件的處理不再由產生事件的對象負責,而由Listener負責。尤其在Swing組件中設計MVC結構時用到了Observer模式,眾所周知,MVC表示“模型-視圖-控制器”,即“數據-表示邏輯-操作”,其中數據可以對應多種表示,這樣視圖就處在了observer的地位,而model則是subject。
5.簡單的例子
回到本文一開始的那個Explorer的例子,我們考慮做一個簡單的圖片瀏覽器,使樹型選擇組件和圖片瀏覽面板在兩個不同的類中,其中圖片瀏覽面板根據所選擇的樹的節點顯示相應的圖片,所以圖片瀏覽面板是一個observer,樹是subject。由于Java單根繼承的原因,我們不能同時繼承JPanel和Observable,但可以用對象的組合把一個subject放到我們的類當中,并通過TreeSelectionListener觸發subject的setChanged方法,并通過notifyObservers方法通知observer。
例子代碼如下:
程序運行截圖如下:
啟動界面
點擊Rabbit顯示的圖像
點擊Devestator顯示的圖像
以前做一個界面的時候常常會遇到這樣的尷尬情況:希望保留各個獨立的組件(類),但又希望它們之間能夠相互通信。譬如Windows中的Explorer,我們希望鼠標點擊左邊是樹型目錄的一個節點,右邊的文件瀏覽能及時列出該節點目錄下的文件和子目錄,類似這樣一個簡單的應用,如果只有一個類繼承JFrame,而樹型組件和瀏覽文件的面板作為成員,就像:
| publicclassMainFrameextendsJFrame { JPaneltreePanel; JTreetree; JPanelfilePanel; ... } |
這樣當然容易在兩者之間傳遞消息,但是可擴展性較差。通常容易想到的是兩種辦法:在一個組件里保留另一個組件類型的成員,初始化時作為參數傳入引用,比如:
| classTreePanelextendsJPanel { JTreetree; ... } classFilePanelextendsJPanel { publicFilePanel(JTreetree){...} ... } |
或者將一個組件線程化,不停地監聽另一個組件的變化,然后作出相應的反映,比如:
| classTreePanelextendsJPanel { JTreetree; ... } classFilePanelextendsJPanelimplementsRunnable { publicvoidrun() { while(true) { //監聽tree的變化 } ... } ... } |
這樣確實可以達到我們的目的,但是第一種方案顯然不利于松散耦合,第二種方案比較占用系統資源。通過學習設計模式,我們發現可以用Observer模式來解決這個問題。
2.Observer模式
設計模式分為創建型、結構型和行為型,其中行為型模式專門處理對象間通信,指定交互方式等,Observer模式就是屬于行為型的一種設計模式。按照“四人幫”(GangofFour)在“DesignPatterns”里的定義,Observer模式“定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都得到通知并被自動更新”,這個描述正好符合我們對“組件通信”問題的需求。讓我們先看看Observer模式的結構:
![]() |
其中各元素的含義如下:
Subject:被觀察的目標的抽象接口,它提供對觀察者(Observer)的注冊、注銷服務,Notify方法通知Observer目標發生改變;
Object:觀察者的抽象接口,Update方法是當得到Subject狀態變化的通知后所要采取的動作;
ConcreteSubject:Subject的具體實現;
ConcreteObserver:Observer的具體實現
Observer模式在實現MVC結構時非常有用,為數據和數據表示解耦合。
3.Java中的Observer模式:Observer和Observable
在大致了解了Observer模式的描述之后,現在我們更為關心的是它在Java中是如何應用的。幸運的是,自從JDK1.0起,就有了專門處理這種應用的API,這就是Observer接口和Observable類,它們是屬于java.util包的一部分。看來Java的開發者們真是深諳設計模式的精髓,而Java的確是為了真正的面向對象而生的,呵呵!
這里的Observer和Observable分別對應設計模式中的Observer和Subject,對比一下它們定義的方法,痕跡還是相當明顯的:
Observer的方法:
update(Observablesubject,Objectarg)監控subject,當subject對象狀態發生變化時Observer會有什么響應,arg是傳遞給Observable的notifyObservers方法的參數;
Observable的方法:
addObserver(Observerobserver)observer向該subject注冊自己
hasChanged()檢查該subject狀態是否發生變化
setChanged()設置該subject的狀態為“已變化”
notifyObservers()通知observer該subject狀態發生變化
4.Observer模式在JavaGUI事件模型中應用
其實在AWT/Swing事件模型中用到了好幾種設計模式,以前的JDK1.0AWT使用的是“基于繼承的事件模型”,在該模型Component類中定義了一系列事件處理方法,如:handleEvent,mouseDown,mouseUp等等,我們對事件的響應是通過對組件類繼承并覆蓋相應的事件處理方法的手段來實現,這種模型有很多缺點,事件的處理不應當由事件產生者負責,而且根據“設計模式”一書中的原則,“繼承”通常被認為是“對封裝性的破壞”,父子類之間的緊密耦合關系降低了靈活性,同時繼承容易導致家族樹規模的龐大,這些都不利于組件可重用。
JDK1.1以后新的事件模型是被成為“基于授權的事件模型”,也就是我們現在所熟悉的Listener模型,事件的處理不再由產生事件的對象負責,而由Listener負責。尤其在Swing組件中設計MVC結構時用到了Observer模式,眾所周知,MVC表示“模型-視圖-控制器”,即“數據-表示邏輯-操作”,其中數據可以對應多種表示,這樣視圖就處在了observer的地位,而model則是subject。
5.簡單的例子
回到本文一開始的那個Explorer的例子,我們考慮做一個簡單的圖片瀏覽器,使樹型選擇組件和圖片瀏覽面板在兩個不同的類中,其中圖片瀏覽面板根據所選擇的樹的節點顯示相應的圖片,所以圖片瀏覽面板是一個observer,樹是subject。由于Java單根繼承的原因,我們不能同時繼承JPanel和Observable,但可以用對象的組合把一個subject放到我們的類當中,并通過TreeSelectionListener觸發subject的setChanged方法,并通過notifyObservers方法通知observer。
例子代碼如下:
| //LeftPanel.java packagecom.jungleford.test; importjava.awt.BorderLayout; importjavax.swing.*; importjavax.swing.event.TreeSelectionListener; importjavax.swing.event.TreeSelectionEvent; importjavax.swing.tree.DefaultMutableTreeNode; importjava.util.Observable; importjava.util.Observer; publicfinalclassLeftPanelextendsJPanel {//把樹型選擇視圖布局在左邊 privateJTreetree;//樹型選擇視圖 privateJScrollPanescroll;//讓視圖可滾動 privateDefaultMutableTreeNoderoot,node1,node2;//根節點及兩個葉子 privateSensorsensor;//sensor是一個Observable,由于只能單根繼承,所以作為組合成員 privateStringfile;//圖片文件名,與RightPanel通信的內容 publicLeftPanel(Observerobserver) { file=""; sensor=newSensor(); sensor.addObserver(observer);//向Observable注冊Observer root=newDefaultMutableTreeNode("Images"); tree=newJTree(root); node1=newDefaultMutableTreeNode("Rabbit"); node2=newDefaultMutableTreeNode("Devastator"); root.add(node1); root.add(node2); tree.addTreeSelectionListener(newTreeSelectionListener() {//樹節點選擇動作 publicvoidvalueChanged(TreeSelectionEvente) { Objectobj=e.getPath().getLastPathComponent(); if(objinstanceofDefaultMutableTreeNode) { DefaultMutableTreeNodenode=(DefaultMutableTreeNode)obj; if(node==root) file="";//選擇根 if(node==node1) file="rabbit.jpg";//選擇node1 if(node==node2) file="devastator.gif";//選擇node2 sensor.setData(file);//改變Observable sensor.notifyObservers();//通知observer,對象已改變 } } }); scroll=newJScrollPane(tree); add(scroll,BorderLayout.CENTER); } publicObservablegetSensor() {//返回Observable對象,使Observer可以獲取 returnsensor; } } classSensorextendsObservable {//定義自己的Observable privateObjectdata; publicvoidsetData(ObjectnewData) { data=newData; setChanged();//改變Observable System.out.println("Datachanged!"); } publicObjectgetData() { returndata; } } //RightPanel.java packagecom.jungleford.test; importjava.awt.*; importjavax.swing.JPanel; importjava.util.Observer; importjava.util.Observable; publicclassRightPanelextendsJPanelimplementsObserver {//把圖片瀏覽視圖布局在右邊 privateImageimage; publicvoidupdate(Observablesubject,Objectobj) {//定義接收到Observable變化后的響應動作 Stringfile=(String)((Sensor)subject).getData(); if(!file.equals("")) { image=Toolkit.getDefaultToolkit().getImage(file); MediaTrackertracker=newMediaTracker(this);//定義圖像跟蹤 tracker.addImage(image,0); try { tracker.waitForID(0);//等待圖像的完全加載 } catch(InterruptedExceptione) { e.printStackTrace(); } } else image=null; repaint();//重繪組件 } publicvoidpaintComponent(Graphicsg) { g.setColor(Color.LIGHT_GRAY); g.fillRect(0,0,getWidth()-1,getHeight()-1);//先將組件上的畫面清除 if(image!=null) g.drawImage(image,0,0,this);//繪制新的圖像 } } //MainFrame.java packagecom.jungleford.test; importjava.awt.*; importjavax.swing.JFrame; publicclassMainFrameextendsJFrame {//演示窗口 publicstaticvoidmain(String[]args) { MainFrameframe=newMainFrame(); RightPanelright=newRightPanel(); LeftPanelleft=newLeftPanel(right);//注冊Observer frame.getContentPane().add(left,BorderLayout.WEST); frame.getContentPane().add(right,BorderLayout.CENTER); frame.setTitle("ObserverTest"); frame.setSize(400,300); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } } |
程序運行截圖如下:
啟動界面
![]() |
點擊Rabbit顯示的圖像
![]() |
點擊Devestator顯示的圖像
![]() |



