Jungleford's Home BlogJava分舵

          Java技術(shù)研究,兼探討歷史話題

          BlogJava 首頁 新隨筆 聯(lián)系 聚合 管理
            24 Posts :: 0 Stories :: 53 Comments :: 0 Trackbacks
          jungleford如是說

          以前做一個界面的時候常常會遇到這樣的尷尬情況:希望保留各個獨立的組件(類),但又希望它們之間能夠相互通信。譬如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):
           o_observer.gif
          其中各元素的含義如下:
          • Subject:被觀察的目標的抽象接口,它提供對觀察者(Observer)的注冊、注銷服務(wù),Notify方法通知Observer目標發(fā)生改變;
          • Object:觀察者的抽象接口,Update方法是當?shù)玫絊ubject狀態(tài)變化的通知后所要采取的動作;
          • ConcreteSubject:Subject的具體實現(xiàn);
          • ConcreteObserver:Observer的具體實現(xiàn)
          Observer模式在實現(xiàn)MVC結(jié)構(gòu)時非常有用,為數(shù)據(jù)和數(shù)據(jù)表示解耦合。

          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ù);
          Observable的方法:
          • addObserver(Observer observer) observer向該subject注冊自己
          • hasChanged() 檢查該subject狀態(tài)是否發(fā)生變化
          • setChanged() 設(shè)置該subject的狀態(tài)為“已變化”
          • notifyObservers() 通知observer該subject狀態(tài)發(fā)生變化
          3. Observer模式在Java GUI事件模型中應(yīng)用
          其實在AWT/Swing事件模型中用到了好幾種設(shè)計模式,以前的JDK 1.0 AWT使用的是“基于繼承的事件模型”,在該模型
          Component類中定義了一系列事件處理方法,如:handleEventmouseDownmouseUp等等,我們對事件的響應(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;
          import javax.swing.*;
          import javax.swing.event.
          TreeSelectionListener;
          import javax.swing.event.
          TreeSelectionEvent;
          import javax.swing.tree.
          DefaultMutableTreeNode;
          import java.util.
          Observable;
          import java.util.
          Observer;

          public final class LeftPanel extends JPanel
          {// 把樹型選擇視圖布局在左邊
            private
          JTree tree;// 樹型選擇視圖
            private
          JScrollPane scroll;// 讓視圖可滾動
            private DefaultMutableTreeNode root, node1, node2;
          // 根節(jié)點及兩個葉子
            private Sensor sensor;
          // sensor是一個Observable,由于只能單根繼承,所以作為組合成員
            private
          String file;// 圖片文件名,與RightPanel通信的內(nèi)容

            public LeftPanel(Observer observer)
            {
              file = "";
              sensor = new Sensor();
              sensor.
          addObserver(observer);// 向Observable注冊O(shè)bserver
              root = new DefaultMutableTreeNode("Images");
              tree = new JTree(root);
              node1 = new DefaultMutableTreeNode("Rabbit");
              node2 = new DefaultMutableTreeNode("Devastator");
              root.
          add(node1);
              root.add(node2);
              tree.
          addTreeSelectionListener(new TreeSelectionListener()
              {
          // 樹節(jié)點選擇動作
                public void
          valueChanged(TreeSelectionEvent e)
                {
                  
          Object obj = e.getPath().getLastPathComponent();
                  if (obj instanceof DefaultMutableTreeNode)
                  {
                    DefaultMutableTreeNode node = (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 = new JScrollPane(tree);
              
          add(scroll, BorderLayout.CENTER);
            }

            public Observable getSensor()
            {
          // 返回Observable對象,使Observer可以獲取
              return sensor;
            }
          }

          class Sensor extends Observable
          {
          // 定義自己的Observable
            private Object data;

            public void setData(Object newData)
            {
              data = newData;
              
          setChanged();// 改變Observable
              
          System.out.println("Data changed!");
            }

            public Object getData()
            {
              return data;
            }
          }


          //RightPanel.java
          package com.jungleford.test;

          import java.awt.*;
          import javax.swing.
          JPanel;
          import java.util.
          Observer;
          import java.util.
          Observable;

          public class RightPanel extends JPanel implements Observer
          {
          // 把圖片瀏覽視圖布局在右邊
            private
          Image image;

            public void
          update(Observable subject, Object obj)
            {
          // 定義接收到Observable變化后的響應(yīng)動作
              
          String file = (String)((Sensor)subject).getData();
              if (!file.
          equals(""))
              {
                image =
          Toolkit.getDefaultToolkit().getImage(file);
                
          MediaTracker tracker = new MediaTracker(this);// 定義圖像跟蹤
                tracker.
          addImage(image, 0);
                
          try
                {
                  tracker.
          waitForID(0);// 等待圖像的完全加載
                }
                catch (
          InterruptedException e)
                {
                  e.
          printStackTrace();
                }
              }
              
          else
                image = null;
              
          repaint();// 重繪組件
            }

            public void
          paintComponent(Graphics g)
            {
              g.
          setColor(Color.LIGHT_GRAY);
              g.
          fillRect(0, 0, getWidth() - 1, getHeight() - 1);// 先將組件上的畫面清除
              if (image != null)
                g.
          drawImage(image, 0, 0, this);// 繪制新的圖像
            }
          }


          //MainFrame.java
          package com.jungleford.test;

          import java.awt.*;
          import javax.swing.
          JFrame;

          public class MainFrame extends JFrame
          {
          // 演示窗口
            public static void main(
          String[] args)
            {
              MainFrame frame = new MainFrame();
              RightPanel right = new RightPanel();
              LeftPanel left = new LeftPanel(right);
          // 注冊O(shè)bserver
              frame.
          getContentPane().add(left, BorderLayout.WEST);
              frame.getContentPane().add(right, BorderLayout.
          CENTER);
              frame.
          setTitle("Observer Test");
              frame.
          setSize(400, 300);
              frame.
          setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              frame.
          setVisible(true);
            }
          }


          程序運行截圖如下:
          o_mainframe.gif
          啟動界面

          o_rabbit.gif
          點擊Rabbit顯示的圖像

          o_devestator.gif
          點擊Devestator顯示的圖像

          附錄:Observer模式概覽

          摘自
          設(shè)計模式

          意圖 定義對象間的一種一對多的依賴關(guān)系,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新。
          動機 將一個系統(tǒng)分割成一系列相互協(xié)作的類有一個常見的副作用:需要維護相關(guān)對象間的移植性。我們不希望為了維持一致性而使各類緊密耦合,因為這樣將降低它們的可重用性。
          適用性
          • 當一個抽象模型有兩個方面,其中一個依賴于另一個,將這二者封裝在獨立的對象中使它們可以各自獨立地改變和復(fù)用
          • 當對一個對象的改變需要同時改變其它對象,但不知道具體有多少對象有待改變
          • 當一個對象必須通知其它對象,但它又不能假定其它對象是什么,亦即不希望這些對象是緊密耦合的
          結(jié)構(gòu)圖 o_observer.gif
          參與者
          • Subject(目標)
          • Observer(觀察者)
          • ConcreteSubject(具體目標)
          • ConcreteObserver(具體觀察者)
          協(xié)作圖 o_observer2.gif
          效果 允許你獨立的改變目標和觀察者。你可以單獨復(fù)用目標對象而無需同時復(fù)用其觀察者,反之亦然。你也可以在不改動目標和其它觀察者的前提下增加觀察者
          應(yīng)用 MVC模式
          相關(guān)模式
          • Mediator模式
          • Singleton模式

          參考資料:
          posted on 2005-04-02 21:44 jungleford 閱讀(320) 評論(0)  編輯  收藏 所屬分類: 咖啡屋 - Java 技術(shù)研究
          主站蜘蛛池模板: 平和县| 剑川县| 秭归县| 侯马市| 托里县| 阿克苏市| 古田县| 宝应县| 夏河县| 宁安市| 瓮安县| 秦皇岛市| 江口县| 江孜县| 香河县| 高青县| 敦化市| 夏邑县| 长汀县| 石家庄市| 游戏| 黄冈市| 翁牛特旗| 宁海县| 赤城县| 长泰县| 沐川县| 岑溪市| 辽宁省| 巢湖市| 合作市| 上高县| 炉霍县| 翁源县| 铁岭县| 聊城市| 仪陇县| 宝兴县| 哈巴河县| 寿宁县| 潜山县|