熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java高級技術 >> 正文

利用Observer模式解決組件間通信問題

2013-11-23 19:43:50  來源: Java高級技術 

   問題的提出
  以前做一個界面的時候常常會遇到這樣的尴尬情況希望保留各個獨立的組件(類)但又希望它們之間能夠相互通信譬如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模式來解決這個問題
  
   Observer模式
  設計模式分為創建型結構型和行為型其中行為型模式專門處理對象間通信指定交互方式等Observer模式就是屬於行為型的一種設計模式按照四人幫(Gang of Four)在Design Patterns裡的定義Observer模式定義對象間的一種一對多的依賴關系當一個對象的狀態發生改變時 所有依賴於它的對象都得到通知並被自動更新這個描述正好符合我們對組件通信問題的需求讓我們先看看Observer模式的結構
  
  其中各元素的含義如下
  Subject被觀察的目標的抽象接口它提供對觀察者(Observer)的注冊注銷服務Notify方法通知Observer目標發生改變
  Object觀察者的抽象接口Update方法是當得到Subject狀態變化的通知後所要采取的動作
  ConcreteSubjectSubject的具體實現
  ConcreteObserverObserver的具體實現
  Observer模式在實現MVC結構時非常有用為數據和數據表示解耦合
  
   Java中的Observer模式Observer和Observable
  在大致了解了Observer模式的描述之後現在我們更為關心的是它在Java中是如何應用的幸運的是自從JDK 就有了專門處理這種應用的API這就是Observer接口和Observable類它們是屬於javautil包的一部分看來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狀態發生變化
  
   Observer模式在Java GUI事件模型中應用
  其實在AWT/Swing事件模型中用到了好幾種設計模式以前的JDK AWT使用的是基於繼承的事件模型在該模型Component類中定義了一系列事件處理方法handleEventmouseDownmouseUp等等我們對事件的響應是通過對組件類繼承並覆蓋相應的事件處理方法的手段來實現這種模型有很多缺點事件的處理不應當由事件產生者負責而且根據設計模式一書中的原則繼承通常被認為是對封裝性的破壞父子類之間的緊密耦合關系降低了靈活性同時繼承容易導致家族樹規模的龐大這些都不利於組件可重用
  JDK 以後新的事件模型是被成為基於授權的事件模型也就是我們現在所熟悉的Listener模型事件的處理不再由產生事件的對象負責而由Listener負責尤其在Swing組件中設計MVC結構時用到了Observer模式眾所周知MVC表示模型-視圖-控制器數據-表示邏輯-操作其中數據可以對應多種表示這樣視圖就處在了observer的地位而model則是subject
  
   簡單的例子
  回到本文一開始的那個Explorer的例子我們考慮做一個簡單的圖片浏覽器使樹型選擇組件和圖片浏覽面板在兩個不同的類中其中圖片浏覽面板根據所選擇的樹的節點顯示相應的圖片所以圖片浏覽面板是一個observer樹是subject由於Java單根繼承的原因我們不能同時繼承JPanel和Observable但可以用對象的組合把一個subject放到我們的類當中並通過TreeSelectionListener觸發subject的setChanged方法並通過notifyObservers方法通知observer
  例子代碼如下
  //LeftPaneljava
  package comjunglefordtest;
  import javaawtBorderLayout;
  import javaxswing*;
  import javaxswingeventTreeSelectionListener;
  import javaxswingeventTreeSelectionEvent;
  import javaxswingtreeDefaultMutableTreeNode;
  import javautilObservable;
  import javautilObserver;
  
  public final class LeftPanel extends JPanel
  {// 把樹型選擇視圖布局在左邊
  private JTree tree;// 樹型選擇視圖
  private JScrollPane scroll;// 讓視圖可滾動
  private DefaultMutableTreeNode root node node;// 根節點及兩個葉子
  private Sensor sensor;// sensor是一個Observable由於只能單根繼承所以作為組合成員
  private String file;// 圖片文件名與RightPanel通信的內容
  
  public LeftPanel(Observer observer)
  {
  file = ;
  sensor = new Sensor();
  sensoraddObserver(observer);// 向Observable注冊Observer
  root = new DefaultMutableTreeNode(Images);
  tree = new JTree(root);
  node = new DefaultMutableTreeNode(Rabbit);
  node = new DefaultMutableTreeNode(Devastator);
  rootadd(node);
  rootadd(node);
  treeaddTreeSelectionListener(new TreeSelectionListener()
  {// 樹節點選擇動作
  public void valueChanged(TreeSelectionEvent e)
  {
  Object obj = egetPath()getLastPathComponent();
  if (obj instanceof DefaultMutableTreeNode)
  {
  DefaultMutableTreeNode node = (DefaultMutableTreeNode)obj;
  if (node == root)
  file = ;// 選擇根
  if (node == node)
  file = rabbitjpg;// 選擇node
  if (node == node)
  file = devastatorgif;// 選擇node
  sensorsetData(file);// 改變Observable
  sensornotifyObservers();// 通知observer對象已改變
  }
  }
  });
  scroll = new JScrollPane(tree);
  add(scroll BorderLayoutCENTER);
  }
  
  public Observable getSensor()
  {// 返回Observable對象使Observer可以獲取
  return sensor;
  }
  }
  
  class Sensor extends Observable
  {// 定義自己的Observable
  private Object data;
  
  public void setData(Object newData)
  {
  data = newData;
  setChanged();// 改變Observable
  Systemoutprintln(Data changed!);
  }
  
  public Object getData()
  {
  return data;
  }
  }
  
  //RightPaneljava
  package comjunglefordtest;
  import javaawt*;
  import javaxswingJPanel;
  import javautilObserver;
  import javautilObservable;
  
  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 (!fileequals())
  {
  image = ToolkitgetDefaultToolkit()getImage(file);
  MediaTracker tracker = new MediaTracker(this);// 定義圖像跟蹤
  trackeraddImage(image );
  try
  {
  trackerwaitForID();// 等待圖像的完全加載
  }
  catch (InterruptedException e)
  {
  eprintStackTrace();
  }
  }
  else
  image = null;
  repaint();// 重繪組件
  }
  
  public void paintComponent(Graphic
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27368.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.