Jungleford's Home BlogJava分舵

          Java技術研究,兼探討歷史話題

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

          以前做一個界面的時候常常會遇到這樣的尷尬情況:希望保留各個獨立的組件(類),但又希望它們之間能夠相互通信。譬如Windows中的Explorer,我們希望鼠標點擊左邊是樹型目錄的一個節點,右邊的文件瀏覽能及時列出該節點目錄下的文件和子目錄,類似這樣一個簡單的應用,如果只有一個類繼承JFrame,而樹型組件和瀏覽文件的面板作為成員,就像:
          public class MainFrame extends JFrame
          {
            JPanel treePanel;
            JTree tree;
            JPanel filePanel;
            ...
          }

          這樣當然容易在兩者之間傳遞消息,但是可擴展性較差。通常容易想到的是兩種辦法:在一個組件里保留另一個組件類型的成員,初始化時作為參數傳入引用,比如:
          class TreePanel extends JPanel
          {
            JTree tree;
            ...
          }
          
          class FilePanel extends JPanel
          {
            public FilePanel(JTree tree){...}
            ...
          }

          或者將一個組件線程化,不停地監聽另一個組件的變化,然后作出相應的反映,比如:
          class TreePanel extends JPanel
          {
            JTree tree;
            ...
          }
          
          class FilePanel extends JPanel implements Runnable
          {
            public void run()
            {
              while (true)
              {
                //監聽tree的變化
              }
              ...
            }
            ...
          }

          這樣確實可以達到我們的目的,但是第一種方案顯然不利于松散耦合,第二種方案比較占用系統資源。通過學習設計模式,我們發現可以用Observer模式來解決這個問題。

          1. Observer模式
          設計模式分為創建型、結構型和行為型,其中行為型模式專門處理對象間通信,指定交互方式等,Observer模式就是屬于行為型的一種設計模式。按照“四人幫”(Gang of Four)在“Design Patterns”里的定義,Observer模式“定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時, 所有依賴于它的對象都得到通知并被自動更新”,這個描述正好符合我們對“組件通信”問題的需求。讓我們先看看Observer模式的結構:
           o_observer.gif
          其中各元素的含義如下:
          • Subject:被觀察的目標的抽象接口,它提供對觀察者(Observer)的注冊、注銷服務,Notify方法通知Observer目標發生改變;
          • Object:觀察者的抽象接口,Update方法是當得到Subject狀態變化的通知后所要采取的動作;
          • ConcreteSubject:Subject的具體實現;
          • ConcreteObserver:Observer的具體實現
          Observer模式在實現MVC結構時非常有用,為數據和數據表示解耦合。

          2. Java中的Observer模式:Observer和Observable
          在大致了解了Observer模式的描述之后,現在我們更為關心的是它在Java中是如何應用的。幸運的是,自從JDK 1.0起,就有了專門處理這種應用的API,這就是
          Observer接口和Observable類,它們是屬于java.util包的一部分。看來Java的開發者們真是深諳設計模式的精髓,而Java的確是為了真正的面向對象而生的,呵呵!
          這里的Observer和Observable分別對應設計模式中的Observer和Subject,對比一下它們定義的方法,痕跡還是相當明顯的:
          Observer的方法:
          • update(Observable subject, Object arg) 監控subject,當subject對象狀態發生變化時Observer會有什么響應,arg是傳遞給Observable的notifyObservers方法的參數;
          Observable的方法:
          • addObserver(Observer observer) observer向該subject注冊自己
          • hasChanged() 檢查該subject狀態是否發生變化
          • setChanged() 設置該subject的狀態為“已變化”
          • notifyObservers() 通知observer該subject狀態發生變化
          3. Observer模式在Java GUI事件模型中應用
          其實在AWT/Swing事件模型中用到了好幾種設計模式,以前的JDK 1.0 AWT使用的是“基于繼承的事件模型”,在該模型
          Component類中定義了一系列事件處理方法,如:handleEventmouseDownmouseUp等等,我們對事件的響應是通過對組件類繼承并覆蓋相應的事件處理方法的手段來實現,組件接收到事件向所在容器廣播,沿著容器鏈直到發現事件被某個容器的handle方法所處理。這種模型有很多缺點,事件的處理不應當由事件產生者負責,而且根據“設計模式”一書中的原則,“繼承”通常被認為是“對封裝性的破壞”,父子類之間的緊密耦合關系降低了靈活性,同時繼承容易導致家族樹規模的龐大,這些都不利于組件可重用。
          JDK 1.1以后新的事件模型是被成為“基于授權的事件模型”,也就是我們現在所熟悉的Listener模型,事件的處理不再由產生事件的對象負責,而由Listener負責,只有被注冊過的Listener才能向組件傳遞事件動作。尤其在Swing組件中設計MVC結構時用到了Observer模式,眾所周知,MVC表示“模型-視圖-控制器”,即“數據-表示邏輯-操作”,其中數據可以對應多種表示,這樣視圖就處在了observer的地位,而model則是subject。大家所熟悉的JTree和JTable就是這種MVC結構:
          -----------------------------------------------------
          Model                View         Controller
          -----------------------------------------------------
          TreeModel        JTree        TreeModelListener
          TableModel      JTable      TableModelListener
          -----------------------------------------------------

          4. 簡單的例子
          回到本文一開始的那個Explorer的例子,我們考慮做一個簡單的圖片瀏覽器,使樹型選擇組件和圖片瀏覽面板在兩個不同的類中,其中圖片瀏覽面板根據所選擇的樹的節點顯示相應的圖片,所以圖片瀏覽面板是一個observer,樹是subject。由于Java單根繼承的原因,我們不能同時繼承JPanel和Observable,但可以用對象的組合把一個subject放到我們的類當中,并通過TreeSelectionListener觸發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;
          // 根節點及兩個葉子
            private Sensor sensor;
          // sensor是一個Observable,由于只能單根繼承,所以作為組合成員
            private
          String file;// 圖片文件名,與RightPanel通信的內容

            public LeftPanel(Observer observer)
            {
              file = "";
              sensor = new Sensor();
              sensor.
          addObserver(observer);// 向Observable注冊Observer
              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()
              {
          // 樹節點選擇動作
                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變化后的響應動作
              
          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);
          // 注冊Observer
              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模式概覽

          摘自
          設計模式

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

          參考資料:
          posted on 2005-04-02 21:44 jungleford 閱讀(320) 評論(0)  編輯  收藏 所屬分類: 咖啡屋 - Java 技術研究
          主站蜘蛛池模板: 海林市| 大化| 宣威市| 阿合奇县| 苍梧县| 丰顺县| 米脂县| 抚远县| 石城县| 太和县| 宝山区| 固阳县| 浑源县| 会泽县| 平利县| 江门市| 安泽县| 嵩明县| 灵宝市| 五峰| 武宣县| 迁安市| 家居| 湘阴县| 商洛市| 乌拉特后旗| 尉犁县| 四平市| 漳平市| 龙山县| 台南县| 营口市| 巴南区| 天水市| 吉林省| 林口县| 杨浦区| 视频| 塘沽区| 馆陶县| 黄大仙区|