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

從Java類庫看設計模式

2022-06-13   來源: Java高級技術 

  很多時候對於一個設計來說(軟件上的建築上的或者它他工業上的)經驗是至關重要的好的經驗給我們以指導並節約我們的時間;壞的經驗則給我們以借鑒可以減少失敗的風險然而從知識層面上來講經驗只是作為一種工作的積累而存在於個人的大腦中的很難被傳授或者記錄為了解決這樣的問題人們提出了所謂的模式的概念所謂模式是指在一個特定背景下反復出現的問題解決方案模式是經驗的文檔化

  軟件模式的概念現在比較的廣泛涉及到分析設計體系結構編碼測試重構等軟件構造生命期中的各個部分這兒主要討論的是設計模式指的是在軟件設計過程中反復出現的一些問題的解決方法了不過我們一般在提到設計模式的時候一般都是指GOF的經典書《Design PatternElements of Reusable ObjectOriented Software》出現的個模式因而它是具體的針對於面向對象軟件設計過程的

  從全局上看來模式代表了一種語言一種被文檔化的經驗甚至是一種文化往往很多不方便描敘或者描敘起來很復雜的問題用模式語言來敘說會讓聽者產生心領神會的感覺當然這需要交流雙方都能夠很好地把握模式語言的含義然而這並不是一件容易的事情模式在各個人的理解上往往存在差異這篇文章旨在從一個具體的應用角度Java類庫來闡敘設計模式並結合具體的例子希望能夠加深大家對設計模式的理解

  這兒說的Java類庫其實並沒有局限於JDK本身還包括了一些其他的類庫中的例子比如JAXP等(當然下一個版本的JDK中也會包含JAXP了)其實設計模式的思想現在應用的如此廣泛無論在什麼樣的設計中只要稍微大一點的設計都可以找到很多很多設計模式的蹤跡或者說都不可避免的用到設計模式下面所講的設計模式大部分都是GOF的那部經典中出現過的個模式然而還有一些比如MVC並不屬於那裡一般的來講我們認為GOF的個模式是一些中級的模式在它下面還可以抽象出一些更為一般的低層的模式在其上也可以通過組合來得到一些高級的模式當然這兒的低中高的區別如同區別不同的語言一樣並沒有優劣之分僅僅是在應用層面上的區別

  Observer模式

  Observer模式的功用是希望兩個(或多個)對象我們稱之為Subject和Observer當一方的狀態發生改變的時候另一方能夠得到通知也就是說作為Observer的一方能夠監視到Subject的某個特定的狀態變化並為之做出反應一個簡單的例子就是當一個用戶視圖中的數據被用戶改變後後端的數據庫能夠得到更新而當數據庫被其他方式更新後用戶視圖中的數據顯示也會隨之改變

  圖一Obverser模式的類圖

  

  在JDK中實際上有一個對Observer模式的簡單的實現就是類javautilObserverable和接口javautilObserverjavautilObserverable類對應於Subject而javautilObserver就是觀察者了JDK中並沒有把這兩個部分都設計為接口而是讓類javautilObserverable提供了部分的實現簡化了許多編程的工作當然這也減少了一定的靈活性

  下面列出了Observer和Observeral的函數列表及其簡單的功能說明

  javautilObserver

  public void update(Observable obs Object obj)

  javautilObserver 接口很簡單只定義了這一個方法狹義的按照Observer模式的說法Observer應該在這個方法中調用Subject的getXXX()方法來取得最新的狀態而實際上你可以只是在其中對Subject的某些事件進行響應這便是Java中的代理事件模型的一個雛形對事件進行響應只不過在Observer模式中將事件特定化為某個狀態/數據的改變了

  javautilObservable

  public void addObserver(Observer obs)

  向Subject注冊一個Observer也就是把這個Observer對象添加到了一個javautilObservable內部的列表中在JDK中對於這個列表是簡單的通過一個javautilVector類來實現的而實際上在一些復雜的Observer模式的應用中需要把這個部分單另出來形成一個Manager類來管理Subject和Observer之間的映射這樣Subject和Observer進一步的被解藕程序也會具有更大的靈活性

  public void deleteObserver(Observer obs)

  從Subject中刪除一個已注冊了Observer的引用

  public void deleteObservers()

  從Subjec中刪除所有注冊的Observer的引用

  public int countObservers()

  返回注冊在Subject中的Observer個數

  protected void setChanged()

  設置一個內部的標志以指明這個Ovserver的狀態已經發生改變注意這是一個protected方法也就是說只能在Observer類和其子類中被調用而在其它的類中是看不到這個方法的

  protected void clearChanged()

  清除上敘的內部標志它在notifyObservers()方法內部被自動的調用以指明Subject的狀態的改變已經傳遞到Ovserver中了

  public boolean hasChanged()

  確定Subject的狀態是否發生了改變

  public void notifyObservers(Object obj)

  它首先檢查那個內部的標志以判斷狀態是否改變如果是的話它會調用注冊在Subject中的每個Observer的update()方法在JDK中這個方法內部是作為synchronized來實現的也就是如果發生多個線程同時爭用一個javautilObserverable的notifyObservers()方法的話他們必須按調度的等待著順序執行在某些特殊的情況下這會有一些潛在的問題可能在等待的過程中一個剛剛被加入的Observer會被遺漏沒有被通知到而一個剛剛被刪除了的Observer會仍然收到它已經不想要了的通知

  public void notifyObservers()

  等價於調用了notifyObservers(null)

  因而在Java中應用Observer就很簡單了需要做的是讓需要被觀察的Subject對象繼承javautilObserverable讓需要觀察的對象實現javautilObserver接口然後用javautilObserverable的addObserver(Observer obj)方法把Observer注冊到Subject對象中這已經完成了大部分的工作了然後調用javautilObserverable的notifyObservers(Object arg)等方法就可以實現Observer模式的機理我們來看一個簡單使用了這個模式的例子這個例子有三個類FrameSubjectDateSubjectFrameObject和EntryClassFrameSubject中用戶可以設置被觀察的值然後自動的會在FrameObject中顯示出來DateSubject封裝被觀察的值並且充當Observer模式中的Subject

      public class FrameSubject extends JFrame {
  
     …………
  
     //因為無法使用多重繼承這兒就只能使用對象組合的方式來引入一個
  
     //javautilObserverable對象了
  
     DateSubject subject=new DateSubject();

  //這個方法轉發添加Observer消息到DateSubject

  public void registerObserver(javautilObserver o){

  subjectaddObserver(o);

  }

  //數據改變事件被觸發後調用notifyObservers()來通知Observer

  void jButton_actionPerformed(ActionEvent e) {

  subjectsetWidthInfo(IntegerparseInt(jTextFieldgetText()));

  subjectsetHeightInfo(IntegerparseInt(jTextFieldgetText()));

  subjectnotifyObservers();

  }

  ……………

  }

  public class DateSubject extends Observable {

  //封裝被觀察的數據

  private int widthInfo;

  private int heightInfo;

  public int getWidthInfo() {

  return widthInfo;

  }

  public void setWidthInfo(int widthInfo) {

  thiswidthInfo = widthInfo;

  //數據改變後setChanged()必須被調用否則notifyObservers()方法會不起作用

  thissetChanged();

  }

  public void setHeightInfo(int heightInfo) {

  thisheightInfo = heightInfo;

  thissetChanged();

  }

  public int getHeightInfo() {

  return heightInfo;

  }

  }

  public class FrameObserver extends JFrame implements javautilObserver {

  …………

  //觀察的數據

  int widthInfo=;

  int heightInfo=;

  //在update()方法中實現對數據的更新和其它必要的反應

  public void update(Observable o Object arg) {

  DateSubject subject=(DateSubject) o;

  widthInfo=subjectgetWidthInfo();

  heightInfo=subjectgetHeightInfo();

  jLabelsetText(The heightInfo from subject is: );

  jLabelsetText(StringvalueOf(heightInfo));

  jLabelsetText(The widthInfo from subject is: );

  jLabelsetText(StringvalueOf(widthInfo));

  }

  ……………

  }

  public class EntryClass {

  public static void main(String[] args) {

  ……………

  FrameSubject frame = new FrameSubject();

  FrameObserver frame=new FrameObserver();

  //在Subject中注冊Observer將兩者聯系在一起

  frameregisterObserver(frame);

  …………

  framesetVisible(true);

  framesetVisible(true);

  ……………

  }

  }

  我認為在JDK中這個Observer模式的實現對於一般的Observer模式的應用已經是非常的足夠了的但是一方面它用一個類來實現了Subject另一方面它使用Vector來保存Subject對於Observer的引用這雖然簡化了編程的過程但會限制它在一些需要更為靈活復雜的設計中的應用有時候(雖然這種情況不多)我們還不得不重新編寫新的Subject對象和額外的Manager對象來實現更為復雜的Observer模式的應用

  隨著現代軟件工業的不斷進步軟件系統的規模的日益擴大越來越需要對某些個不斷出現的問題進行模式化思維以成功的經驗或者失敗的教訓來減少軟件開發失敗的風險模式代表了一種文檔化的經驗它為某一類的問題提供了最好(或者說很好)的解決方案使得即使不是經驗豐富的軟件工程師也能夠根據模式來構建相對成功的系統本節給出的一個Obverser模式的示例比較好的說明了這一點Obverser模式主要解決在對象間的狀態映射或者鏡像的問題

  在設計一般用途的軟件的時候在C或者C++語言中用的很多的一個技巧就是回調函數(Callback)所謂的回調函數意指先在系統的某個地方對函數進行注冊讓系統知道這個函數的存在然後在以後當某個事件發生時再調用這個函數對事件進行響應在C或者C++中實現的回調函數方法是使用函數指針但是在Java中並不支持指針因而就有了Command模式這一回調機制的面向對象版本

  Command模式用來封裝一個命令/請求簡單的說一個Command對象中包含了待執行的一個動作(語句)序列以執行特定的任務當然並不是隨便怎麼樣的語句序列都可以構成一個Command對象的按照Command模式的設計Command對象和它的調用者Incvoker之間應該具有接口約定的也就是說Invoker得到Command對象的引用並調用其中定義好的方法而當Command對象改變(或者是對象本身代碼改變或者干脆完全另外的一個Command對象)之後Invoker中的代碼可以不用更改這樣通過封裝請求可以把任務和任務的實現加以分離

   圖二Command模式的類圖

  

  而對於請求的處理又有兩種不同的方法一種是Command只充當代理將請求轉發給某個接受者對象還有一種是Command對象自己處理完所有的請求操作當然這只是兩個極端更多的情況是Command完成一部分的工作而另外的一部分這則交給接受者對象來處理

  在新的JDK的代理事件模型中就可以看作是這樣的一個Command模式在那個模型中一個事件監聽者類EventListener監聽某個事件並根據接口定義實現特定的操作比如當用Document對象的addDocumentListener(DocumentListener listener) 方法注冊了一個DocumentListener後以後如果在Document對象中發生文本插入的事件DocumentListener中實現的insertUpdate(DocumentEvent e)方法就會被調用如果發生文本刪除事件removeUpdate(DocumentEvent e)方法就會被調用怎麼樣想想看這是不是一個Command模式的應用呢?

  然而最經典的Command模式的應用莫過於Swing中的Action接口Action實際上繼承的是ActionListener也就是說它也是一個事件監聽者(EventListener)但是Action作為一種ActionListener的擴展機制提供了更多的功能它可以在其中包含對這個Action動作的一個或者多個文字的或圖標的描敘它提供了Enable/Disable的功能許可性標志並且一個Action對象可以被多個Invoker比如實現相同功能的按鈕菜單快捷方式所共享而這些Invoker都知道如何加入一個Action並充分利用它所提供的擴展機制可以說在這兒Action更像一個對象了因為它不僅僅提供了對方法的實現更提供了對方法的描敘和控制可以方便的描敘任何的事務這更是面向對象方法的威力所在

  下面我們看一個Command模式的應用的例子假設要實現這樣的一個任務Task Schedule也就是說我想對多個任務進行安排比如掃描磁盤我希望它每個小時進行一次而備份數據我希望它半個小時進行一次等等等等但是我並不希望作為TaskSchedule的類知道各個任務的細節內容TaskSchedule應該只是知道Task本身而對具體的實現任務的細節並不理會因而在這兒我們就需要對TaskSchedule和Task進行解耦將任務和具體的實現分離出來這不正是Command模式的用武之地嗎?

  圖三Command模式的應用例子

  

  程序清單

   //抽象的Task接口作為回調的Command模式的主體
public interface Task {
  public void taskPerform();
}
//具體的實現了Task接口的子類實現特定的操作
public class BackupTask implements Task{
  public void taskPerform(){
    Systemoutprintln(Backup Task has been performed);
  }
}
//具體的實現了Task接口的子類實現特定的操作
public class ScanDiskTask implements Task{
  public void taskPerform(){
    Systemoutprintln(ScanDisk Task has been performed);
  }
}
//一個封裝了Task的一個封裝類提供了一些與Task相關的內容也可以把這些內容
//這兒不過為了突出Command模式而把它單另出來實際上可以和Task合並
public class TaskEntry {
  private Task task;
  private long timeInterval;
  private long timeLastDone;
  public Task getTask() {
    return task;
  }
  public void setTask(Task task) {
    thistask = task;
  }
  public void setTimeInterval(long timeInterval) {
    thistimeInterval = timeInterval;
  }
  public long getTimeInterval() {
    return timeInterval;
  }
  public long getTimeLastDone() {
    return timeLastDone;
  }
  public void setTimeLastDone(long timeLastDone) {
    thistimeLastDone = timeLastDone;
  }
  public TaskEntry(Task tasklong timeInteral){
    thistask=task;
    thistimeInterval =timeInteral;
  }
}
//調度管理Task的類繼承Thread只是為了調用其sleep()方法
//實際上如果真的作Task調度的話每個Task顯然應該用單獨的Thread來實現
public class TaskSchedule extends javalangThread {
  private javautilVector taskList=new javautilVector();
  private long sleeptime=l;//最短睡眠時間
  public void addTask(TaskEntry taskEntry){
    taskListadd(taskEntry);
    taskEntrysetTimeLastDone(SystemcurrentTimeMillis());
    if (sleeptime>taskEntrygetTimeInterval())
    sleeptime=taskEntrygetTimeInterval();
  }
  //執行任務調度
  public void schedulePermorm(){
    try{
      sleep(sleeptime);
      Enumeration e = taskListelements();
      while (ehasMoreElements()) {
        TaskEntry te = (TaskEntry) enextElement();
        if (tegetTimeInterval() + tegetTimeLastDone() <
                SystemcurrentTimeMillis()) {
          tegetTask()taskPerform();
          tesetTimeLastDone(SystemcurrentTimeMillis());
          }
      }
    }catch (Exception e){
      eprintStackTrace();
    }
  }
  public static void main (String args[]){
    TaskSchedule schedule=new TaskSchedule();
    TaskEntry taks=new TaskEntry(new ScanDiskTask());
    TaskEntry taks=new TaskEntry(new BackupTask());
    scheduleaddTask(taks);
    scheduleaddTask(taks);
    while (true){
        scheduleschedulePermorm();
      }
  }
}

  程序本身其實沒有多大的意義因而程序在編碼的時候也只是用的最簡單的方法來實現的如果要做一個真正的TaskSchedule的話這個程序除了結構上的其它沒有什麼好值得參考的了

  基本上來說AbstractFacotry模式和FactoryMethod模式所作的事情是一樣的都是用來創建與具體程序代碼無關的對象只是面對的對象層次不一樣AbstractFactory創建一系列的對象組這些對象彼此相關而FactoryMethod往往只是創建單個的對象

  再開始這兩個模式之前有必要先陳敘一個在設計模式或者說在整個面向對象設計領域所遵循的一個設計原則針對接口編程而不是針對具體的實現這個思想可以說是設計模式的基石之一現在的很多對象模型比如EJBCOM+等等無不是遵照這個基本原則來設計的針對接口編程的好處有很多通過接口來定義對象的抽象功能方便實現多態和繼承;通過接口來指定對象調用之間的契約有助於協調對象之間的關系;通過接口來劃分對象的職責有助於尋找對象等等

  AbstractFactory和FactoryMethod還有其他的一些創建型的設計模式都是為了實現這個目的而設計出來的它們創建一個個符合接口規范的對象/對象組使得用同一個Factory創建出來的對象/對象組可以相互替換這種可替換性就稱為多態是面向對象的核心思想之一而多態是通過動態綁定來實現的

  圖四AbstractFactory模式的類圖

  

  客戶程序使用具體的AbstractFacotry對象(ConcreteFactoryX)調用CreateProductX()方法生成具體的ConcreteProductX每個AbstractFactory所能生成的對象組成一個系列的對象組他們可能是相互相關的緊耦合的應為各個AbstractFactory對象所能夠生成的對象組都遵循一組相同的接口(AbstractProductX)因而當程序是針對接口進行編程的時候這些實現方法各不相同的對象組卻可以相互的替換

  實際上客戶程序本身並不關心也不知道具體使用的是那些產品對象它甚至能夠不理會到底是哪個AbstractFactory對象被創建在這種情況下你可能會問那麼一個AbstractFactory又該如何生成呢?這時候就該用該FactoryMethod模式了

  前面有說過AbstractFactory著重於創建一系列相關的對象而這些對象與具體的AbstractFactory相關而FactoryMethod則著重於創建單個的對象這個對象決定於一個參數或者一個外部的環境變量的值;或者在一個抽象類中定義一個抽象的工廠方法(也成為虛擬構造器)然後再實現的子類中返回具體的產品對象

  FactoryMethod可以借助一個參數或者一個外部的標志來判斷該具體生成的哪一個子類的實例比如對於不同的具體情況需要有不同的AbstractFactory來生成相應的對象組這時候FactoryMethod通常作為一個AbstractFactory對象的靜態方法出現使得其能夠在具體的對象被創建之前就能夠被調用

  在JAVA中應用這兩個模式的地方實在太多下面我們來看一個在JAXP中這兩個模式的應用JAXP是用來處理XML文檔的一個API我們都知道XML文件的一個特點就是其平台無關流通性能好因而往往也需要處理他們的程序具有更好的平台無關性Java語言是一個比較好的平台無關語言可以作為一個選擇但是對XML進行解析的解析器確有很多有時候需要在不同的解析器之間進行切換這時候JAXP的良好設計就能夠體現出來了它能夠允許在不同解析器之間竟進行切換的時候不用更改程序的代碼

  我們就拿JAXP中的DOM解析器來作為例子來例示AbstractFactory和FactoryMethod的用法

  圖五DOM中工廠模式的應用

  

  上圖中為了方便起見只畫出了抽象類和接口DocumentBuilderFactory和DocumentBuilder都是抽象類

  DocumentBuilderFactory的靜態方法newInstance()方法根據一個外部的環境變量javaxxmlparsersDocumentBuilderFactory的值來確定具體生成DocumentBuilderFactory的哪一個子類這兒的newInstance()是一個工廠方法當DocumentBuilderFactory被創建後可以調用其newDocumentBuilder()來創建具體一個DocumentBuilder的子類然後再由DocumentBuilder來生成Document等DOM對象

  下面是創建一個DOM對象的代碼片段

   //第一步創建一個DocumentBuilderFactory
        DocumentBuilderFactory dbf = DocumentBuilderFactorynewInstance();
        //第二步創建一個DocumentBuilder
        DocumentBuilder db = dbfnewDocumentBuilder();
        //第三步解析XML文件得到一個Document對象
        Document doc = dbparse(new File(filename));

  在這兒DocumentBuilderDocumentNode等等對象所組成的一個產品組是和具體的DocumentBuilderFactory相關的這也就是AbstractFactory模式的含義所在

  當然FactoryMethod模式應用的很廣這是一個具體的例子但他不應該限制我們的思路FactoryMethod和AbstractFactory是解決面向對象設計中一個基本原則面向接口編程的主要方法

    Singleton模式

  Singleton模式要解決的是對象的唯一性問題由Singleton模式創建的對象在整個的應用程序的范圍內只允許有一個對象的實例存在這樣的情況在Java程序設計的過程中其實並不少見比如處理JDBC請求的連接池(Connection Pool)再比如一個全局的注冊表(Register)等等這都需要使用到Singleton單件模式

  在Java中最簡單的實現Singleton模式的方法是使用static修飾符static可以用在內部類上也可以用在方法和屬性上當一個類需要被創建成Singleton時可以把它所有的成員都定義成static然後再用final和private來修飾其構造函數使其不能夠被創建和重載這在程序語法上保證了只會有一個對象實例被創建比如javautilMath就是這樣的一個類

  而Singleton模式所作的顯然要比上面介紹的解決方法要復雜一些也更為安全 target=_blank>安全一些它基本的思路也還是使用static變量但是它用一個類來封裝這個static變量並攔截對象創建方法保證只有一個對象實例被創建這兒的關鍵在於使用一個private或者protected的構造函數而且你必須提供這樣的一個構造函數否則編譯器會自動的為你創建一個public的構造函數這就達不到我們想要的目的了

   cellPadding= width=% align=center border=> ff> public class Singleton {

  //保存唯一實例的static變量

  static private Singleton _instance = null;

  /*為了防止對象被創建可以為構造函數加上private修飾符但是這同樣也防止了子類的對象被創建因而可以選用protected修飾符來替代private*/

  protected Singleton() {

  //

  }

  //static方法用來創建/訪問唯一的對象實例這兒可以對對象的創建進行控制使得可//以很容易的實現只允許指定個數的對象存在的泛化的Singleton模式

  static public Singleton instance() {

  if(null == _instance) {

  _instance = new Singleton();

  }

  return _instance;

  }

  //

  }

  對象創建的方法除了使用構造函數之外還可以使用Object對象的clone()方法因而在Singleton中也要注意這一點如果Singleton類直接繼承於Object因為繼承於Object的clone()方法仍保留有其protected修飾因而不能夠被其他外部類所調用所以可以不用管它但是如果Singleton繼承於一個其他的類而這個類又有重載clone()方法這時就需要在Singleton中再重載clone()方法並在其中拋出CloneNotSupportedException這樣就可以避免多個Singleton的實例被創建了

  在JDK以前的版本中使用Singleton模式的時候有一些需要額外注意的地方因為Singleton類並沒有被任何其他的對象所引用所以這個類在創建後一段時間會被unloadSingleton類的靜態方法就會出現問題這是由於Java中垃圾收集機制造成的解決的方法也很容易只需要為其創建一個引用就行了而在JDK以後的版本中Sun重新定義了Java規范改正了其垃圾收集機制中的一些問題這個問題也就不復存在了這兒指出只是為了提起大家的主意

  Command模式用來封裝請求也描敘了一致性的發送請求的接口允許你配置客戶端以處理不同的請求為程序增添了更大的靈活性Singleton模式為提供對象的單一入口提供了幫助AbstractFactory和FactoryMethod模式在功能上比較類似都是用來處理對象的創建的但應用在不同的層面上在創建型模式中還有Builder模式和Prototype模式這兒不打算詳細的討論了簡單的說Builder模式用來處理對象創建的細節在兩個工廠模式中都沒有涉及到對象創建的具體細節都是通過接口來返回一個給定類型的對象而Builder模式則需要對創建一個給定類型對象的過程進行建模這對創建復雜對象時很有用使得創建對象的算法獨立於對象各個組成部分的創建而Prototype模式使用原型機制通過創建簡單原型的拷貝來創建對象

  當初Java剛剛推出來的時候AWT可是一個比較熱的話題雖然現在有被Swing取代的趨勢但是我一直都覺得AWT也有其優勢至少它使用的本地代碼就要比Swing快上許多而且可以為用戶提供熟悉的本地操作系統界面如果在Windows XP中運行基於AWT的程序的話XP中絢爛多變的界面Theme可以輕易應用到AWT程序中而Swing就不行了因為AWT所調用的是本帶代碼使用的是本地的窗體控件當然Swing也有其好處不可一概而論

  簡單來講AWT提供對程序員的是對窗體界面系統的抽象而在內部實現中針對每一種操作系統分別有不同實現這就是同位體(Peer)的概念當程序員調用AWT對象時調用被轉發到對象所對應的一個Peer上在由Peer調用本地對象方法完成對象的顯示例如如果你使用AWT創建了一個Menu類的實例那麼在程序運行時會創建一個菜單同位體的實例而由創建的同位體的來實際執行菜單的現實和管理不同的系統有不同的同位體實現Solaris JDK將產生一個Motif菜單的同位體Windows下的JDK將產生一個Windows的菜單的同位體等等同位體的使用使得交叉平台窗口工具的開發變得極為迅速因為同位體的使用可以避免重新實現本地窗口控件中已經包含的方法

  圖六AWT中的組件和其對等體

  <fp src=http://imgeducitycn/img_///jpg border= twffan=done>

  實際上從設計的角度來看這是一個抽象和實現分離的過程AWT是抽象同位體是實現抽象和實現各自成為一個對象體系它們由一個橋連接起來可以各自發展各自的對象層次而不必顧慮另一方面這就是Bridge模式所提供的思想Bridge模式更可以提供在各個不同的實現中動態的進行切換而不必從新編譯程序

  通常Bridge模式和AbstractFactory模式一起工作由AbstractFactory來創建一個具體實現的對象體系特殊的當只有一個實現的時候可以將Implementor抽象類去掉這樣在抽象和實現之間建立起了一一對應的關系但這並不損害Bridge模式的內涵這被稱為退化了的Bridge模式

  很多時候Abstraction層次和Implementor層次之間的方法都不是一一對應的也就是說在Abstraction和Implementor之不是簡單的的消息轉發通常我們會將Abstraction作為一個抽象類(而不是接口)來實現在Implementor層次中定義底層的或者稱之為原子方法而在Abstraction層次中定義一些中高層的基於原子方法的抽象方法這樣就能更為清晰的劃分Abstraction和Implementor類的結構也更為清晰

  圖七Bridge模式對系統的劃分

    <fp src=http://imgeducitycn/img_///jpg border= twffan=done>

  下面我們來看一個Bridge模式的具體應用考慮這樣的一個問題需要生成一份報告但是報告的格式並沒有確定可能是HTML文件也可能是純ASCII文本報告本身也可能分為很多種財務報表貨物報表等等問題很簡單用繼承也較容易實現因為相互之間的組合關系並不是很多但是我們現在需要用Bridge的觀點來看問題

  在Bridge模式中使用一個Report類來描敘一個報告的抽象用一個Reporter類來描敘Report的實現它的子類有HTMLReporter和ASCIIReporter用來分別實現HTML格式和ASCII格式的報告在Report層次下面有具體的一個StockListReport子類用來表示貨物清單報告

   cellPadding= width=% align=center border=> ff>    public abstract class Report
  
     {
  
     Reporter reporter;
  
     public Report(Reporter reporter) {
  
     thisreporter = reporter;

  }

  //抽象類使用橋接對象的方法來實現一個任務

  public void addReportItem(Object item){

  reporteraddLine(itemtoString());

  }

  public void addReportItems(List items){

  Iterator iterator = erator();

  while ( iteratorhasNext() )

  {

  reporteraddLine(iteratornext()toString());

  }

  }

  public String report(){

  return reportergetReport();

  }

  }

  public class StockListReport extends Report{

  ArrayList stock=new ArrayList();

  public StockListReport(Reporter reporter){

  super(reporter);

  }

  public void addStockItem(StockItem stockItem){

  stockadd(stockItem);

  addReportItem(stockItem);

  }

  }

  //實現層次的抽象父類定義原子方法供抽象層次的類調用

  public abstract class Reporter{

  String header = ;

  String trailer = ;

  String report = ;

  public abstract void addLine(String line);

  public void setHeader(String header){

  thisheader = header;

  }

  public void setTrailer(String trailer){

  thistrailer = trailer;

  }

  public String getReport(){

  return header+report+trailer;

  }

  }

  public class HTMLReporter extends Reporter{

  public HTMLReporter(){

  setHeader(\n\n\n);

  setTrailer(\n);

  }

  public void addLine(String line){

  report += line +

  \n;

  }

  }

  public class ASCIIReporter extends Reporter{

  public void addLine(String line) {

  report += line + \n;

  }

  }

  實際上Bridge模式是一個很強大的模式可以應用在很多方面其基本思想分離抽象和實現是設計模式的基礎之一正如GOF所提到的找到變化的部分並將其封裝起來;更多的考慮用對象組合機制而不是用對象繼承機制Bridge模式很好的體現了這幾點

  在使用Java中的IO類庫的時候是不是快要被它那些功能相似卻又絕對可稱得上龐雜的類搞得要發瘋了?或許你很不明白為什麼要做這麼多功能相似的幾十個類出來這就是Decorator模式將要告訴你的了

  在IO處理中Java將數據抽象為流(Stream)在IO庫中最基本的是InputStream和OutputStream兩個分別處理輸出和輸入的對象(為了敘述簡便起見這兒只涉及字節流字符流和其完全相似)但是在InputStream和OutputStream中之提供了最簡單的流處理方法只能讀入/寫出字符沒有緩沖處理無法處理文件等等它們只是提供了最純粹的抽象最簡單的功能

  如何來添加功能以處理更為復雜的事情呢?你可能會想到用繼承不錯繼承確實可以解決問題但是繼承也帶來更大的問題它對每一個功能都需要一個子類來實現比如我先實現了三個子類分別用來處理文件緩沖和讀入/寫出數據但是如果我需要一個既能處理文件又具有緩沖功能的類呢?這時候又必須在進行一次繼承重寫代碼實際上僅僅這三種功能的組合就已經是一個很大的數字如果再加上其它的功能組合起來的IO類庫如果只用繼承來實現的話恐怕你真的是要被它折磨瘋了

  圖八JDK中IO流的類層次

  <fp src=http://imgeducitycn/img_///jpg border= twffan=done>

  Decorator模式可以解決這個問題Decorator字面的意思是裝飾的意思在原有的基礎上每添加一個裝飾就可以增加一種功能這就是Decorator的本意比如對於上面的那個問題只需要三個Decorator類分別代表文件處理緩沖和數據讀寫三個功能在此基礎上所衍生的功能都可以通過添加裝飾來完成而不必需要繁雜的子類繼承了更為重要的是比較繼機制承而言Decorator是動態的可以在運行時添加或者去除附加的功能因而也就具有比繼承機制更大的靈活性

  上面就是Decorator的基本思想下面的是Decorator模式的靜態結構圖

   圖九Decorator模式的類圖

  <fp src=http://imgeducitycn/img_///jpg border= twffan=done>

  可以看到一個Decorator與裝飾的Subject對象有相同的接口並且除了接口中給出的方法外每個Decorator均有自己添加的方法來添加對象功能每個Decorator均有一個指向Subject對象的引用附加的功能被添加在這個Subject對象上而Decorator對象本身也是一個Subject對象因而它也能夠被其他的Decorator所修飾提供組合的功能

  在Java IO操作中經常可以看到諸如如下的語句

   cellPadding= width=% align=center border=> ff> myStringBuffer=new StringBuffer(This is a sample string to be read);

  FilterInputStream myStream=new LineNumberInputStream

  ( new BufferInputStream( new StringBufferInputStream( myStringBuffer)));

  myStreamread();

  myStreamline();

  多個的Decorator被層疊在一起最後得到一個功能強大的流既能夠被緩沖又能夠得到行數這就是Decorator的威力!

  不僅僅如此Java中的IO還允許你引入自定義的Decorator來實現自己想要的功能在良好的設計背景下這做起並不復雜只需要

  創建兩個分別繼承了FilterInputStream和 FilterOutputStream的子類

  重載read()和write()方法來實現自己想要的功能

  可以定義或者重載其它方法來提供附加功能

  確定這兩個類會被一起使用因為它們在功能上是對稱的

  就這樣你就可以無限的擴展IO的功能了

  在了解了IO中的Decorator後我們再來看一個Decorator模式應用的具體的例子這個例子原本是出現在GOF書中的這兒稍作改動引來示例

  在一個圖形用戶界面(GUI)中一個組件有時候需要用到邊框或者滾動條而有時候又不需要有時候可能兩者都要用到當需要動態的去處或者添加職能的時候就可以考慮使用Decorator模式了這兒對於一個VisualComponent組件對象我們引入了兩個Decorator類BoderDecorator和ScrollDecorator分別用來為組件添加邊框和處理滾動程序類圖如下

  圖十Decorator模式的應用例子

  <fp src=http://imgeducitycn/img_///jpg border= twffan=done>

  程序寫得很簡單沒有包括具體的代碼只是有一個可以運行的框架以供參考代碼如下

   cellPadding= width=% align=center border=> ff>    //Client類用來創建窗體和組件對象這兒可以看到Decorator是如何組合和應用的
  
     class Client{
  
     public static void main (String[] args ){
  
     Window window = new Window ();
  
     TextView textView = new TextView ();

  windowsetContents (

  new BorderDecorator (

  new ScrollDecorator (textView ) ));

  }

  }

  //Windows類用來容納組件對象

  class Window{

  VisualComponent contents;

  public Window () {}

  public void setContents (VisualComponent vc){

  contents = vc;

  }

  }

  //VisualComponent類定義了組件的接口

  class VisualComponent{

  public VisualComponent (){}

  public void draw (){}

  public void resize (){}

  }

  //TextView類是一個顯示文本的具體的組件

  class TextView extends VisualComponent{

  public TextView (){}

  public void draw (){

  …

  }

  public void resize (){

  …

  }

  }

  //Decorator類繼承於VisualComponent定義所有Decorator的缺省方法實現

  class Decorator extends VisualComponent{

  private VisualComponent component;

  public Decorator (VisualComponent vc) {

  ponent=vc;

  }

  public void draw () {

  componentdraw ();

  }

  public void resize () {

  componentresize ();

  }

  }

  //BorderDecorator類為組件提供邊框

  class BorderDecorator extends Decorator{

  private int width;

  public BorderDecorator (VisualComponent vc int borderWidth){

  super (vc);

  width = borderWidth;

  }

  public void draw (){

  superdraw ();

  drawBorder (width);

  }

  private void drawBorder (int width){

  …

  }

  }

  //ScrollDecorator類為組件提供滾動條

  class ScrollDecorator extends Decorator{

  private int scrollSize;

  public ScrollDecorator (VisualComponent vc int scrSize){

  super (vc);

  scrollSize = scrSize;

  }

  public void draw (){

  scroll();

  superdraw ();

  }

  private void scroll (){

  …

  }

  }

  Decorator確實能夠很好的緩解當功能組合過多時子類繼承所能夠帶來的問題但是在得到很大的靈活性的同時Decorator在使用時也表現得較為復雜看看僅僅為了得到一個IO流除了要創建核心的流外還要為其加上各種各樣的裝飾類這使得代碼變得復雜而難懂有幾個人一開始時沒有被Java的IO庫嚇一跳呢?

  Bridge模式用來分離抽象和實現使得這兩個部分能夠分別的演化而不必修改另外一部分的內容通常的可以在實現部分定義一些基本的原子方法而在抽象部分則通過組合定義在實現層次中的原子方法來實現系統的功能Decorator模式通過聚合機制來為對象動態的添加職責解決了在子類繼承中容易引起的子類爆炸的問題


  毫無疑問的AWT中的ComponentContainer體系就是一個很好的Composite模式的例子Container繼承於Component而Container中有可以包含有多個Component因為Container實際上也是Component因而Container也可以包含Container這樣通過ComponentContainer結構的對象組合形成一個樹狀的層次結構這也就是Composite模式所要做的

  Composite模式是為了簡化編程而提出的一般的在編程的時候如果嚴格的區分Component和Container的話有時候會帶來許多不便而且這些往往是沒有必要的比如我要在一個Container中放置一個Component我並不需要知道這個Component到底是一個Container或者就是一個一般的Component在父級容器中所要做的只是記錄一個Component的引用在需要的時候調用Component的繪制方法來顯示這個Component當這個Component確實是一個Container的時候它可以通過Container重載後的繪制方法完成對這個容器的顯示並把繪制消息傳遞給到它的子對象去也就是說對一個父級容器而言它並不不關心其子對象到底是一個Component還是一個Container它需要將Component和Container統一對待

  圖十一Composite模式的類圖

  <fp src=http://imgeducitycn/img_///jpg border= twffan=done>

  Composite模式比較簡單實現起來也不復雜但是有一定的局限性比如在處理樹的時候我們往往需要處理三類對象子樹頁節點和非頁節點而在Composite模式中對於子樹和非葉節點的區分並不明顯而是把他們合成為一個Composite對象了而且在GOF給出的Composite的模式中對於添加刪除子節點等屬於Composite對象的的方法是放在了Component對象中的這雖然在實現的時候可以區分開來但容易造成一些概念上的誤解

  由上所敘我們可以提出一個改進了的Composite模式引入子樹對象從而將子樹和非葉節點分開如下圖所示

  圖十二Composite模式的一種變體

  <fp src=http://imgeducitycn/img_///jpg border= twffan=done>

  雖然將Composite從Component類層次中分離出來但並沒有損害Composite模式的內涵這樣做不一定就會比上面的那個要好各有不同的應用不過有時候用這樣的方法來處理子樹要容易些概念上也更為清晰

  下面的代碼給出了一個Composite模式簡單的Java實現

   cellPadding= width=% align=center border=> ff>  public abstract class Component{

  public abstract void operation();

  public void add(Component component){};

  public void remove(Component component){};

  }

  import javautil*;

  public class Composite extends Component{

  String name;

  ArrayList children = new ArrayList();

  public Composite(String name){

  thisname = name;

  }

  public void add(Component component){

  childrenadd(component);

  }

  public void remove(Component component){

  childrenremove(component);

  }

  public void operation(){

  Systemoutprintln(name);

  Iterator iterator = erator();

  while(iteratorhasNext()){

  Component child = (Component)iteratornext();

  childoperation();

  }

  }

  }

  public class Leaf extends Component{

  String name;

  public Leaf(String name){

  thisname = name;

  }

  public void operation(){

  Systemoutprintln(name);

  }

  }

相關rss> 相關keyword >

  Strategy模式主要用來將算法實現從類中分離出來並封裝在一個單獨的類中更簡單的說對象與其行為(behaviour)這本來緊密聯系的兩部分被解耦分別放在了兩個不同的類中這使得對同一個行為可以方便的在任何時候切換不同的實現算法而通過對策略的封裝為其提供統一的接口也可以很容易的引入新的策略

  AWT的LayoutManager是Strategy模式的一個例子對於GUI而言每個組件(Component)在容器中(Container)的排放是需要遵循一定的算法的通常的方法是使用絕對坐標就像VBDelphi之類的工具所作的那樣記錄每個組件在容器中的位置這當然會帶來一些問題比如在窗體縮放的時候就需要手工編碼改變組件的大小和位置以使得原來的比例得以保存而在AWT中引入了布局管理器(LayoutManager)的概念使得布局的方法大大豐富編碼過程也變得簡單

  一個容器比如AppletPanel等僅僅記錄其包含的組件而布局管理器中封裝了對容器中組件進行布局的算法具體地說就是指明容器中組件的位置和尺寸的大小通過布局管理器你只需要確定想放置的組件間的相對位置即可這一方面簡化編碼另一方面也有助於實現軟件的平台無關性

  圖十三AWT中的容器和布局管理器的關系

  <fp src=http://imgeducitycn/img_///jpg border= twffan=done>

  每一個容器均有一個布局管理器當容器需要布置它的組件時它調用布局管理器的方法布置容器內的組件LayoutManager繼承於LayoutManager提供更為細致的布局功能它可以讓布局管理器為組件加上約束條件已確定組件如何被布置例如為了確定組件被擺放在邊框內的位置BorderLayout在它的組件上加上方向指示

  特別的通過實現LayoutManager或者LayoutManager接口可以很容易實現自定義的布局策略

  回到模式的話題上來如果有幾個很相似的類其區別僅僅是在個別行為上的動作不同這時候就可以考慮使用Strategy模式這樣通過策略組合將原來的多個類精簡為一個帶有多個策略的類這很符合OO設計的原則找到變化的部分並將其封裝起來!Strategy模式同樣的為子類繼承提供了一個好的替代方案當使用繼承機制的時候行為的改變是靜態的你指能夠改變一次而策略是動態的可以在任何時候切換任何次數更為重要的是策略對象可以在不同的環境中被不同的對象所共享以布局管理器為例雖然每一個容器只有一個布局管理器但是一個布局管理器可以為多個容器工作

  圖十四Strategy模式的類圖

  <fp src=http://imgeducitycn/img_///jpg border= twffan=done>

  Strategy模式也有一些缺點比如應用程序必須知道所有的策略對象並從中選者其一而且在策略對象被使用的時候它和Context對象之間通常是緊耦合的Context對象必須為策略對象提供與具體算法相關的數據或者其它的東西而這些數據的傳遞可能並不能夠風裝載抽象地策略類中因為並不是所有的算法都會需要這些數據的另外因為策略對象通常由應用程序所創建Context對象並不能夠控制Strategy的生命期而在概念上這個策略應該從屬於Context對象其生命期不應該超出Context的范圍對象

  通常的Strategy很容易和Bridge模式相混淆確實他們有著很相近的結構但是他們卻是為解決不同的問題而設計的Strategy模式注重於算法的封裝而Bridge模式注重於分離抽象和實現為一個抽象體系提供不同的實現

  Iterator模式用來規格化對某一數據結構的遍歷接口

  JDK中在Collection Framework中引入了Iterator接口提供對一個Collection的遍歷每一個Collection類中都定義有從Collection接口中繼承而來的iterator()方法來得到一個Iterator對象我們稱之為遍歷器Iterator接口很簡單

  hasNext()用來判斷在遍歷器中是否還有下一個元素

  next()返回遍歷器中的下一個元素

  remove()在被遍歷的Collection類中刪除最後被返回的那個對象

  我們就以最為常用的Vector為例看看在Collection Framework中Iterator模式是如何被實現的在此之前我們需要先了解一些Vector和Collection Framework的結構

  Collection接口作為這個Framework的基礎被所有其它的集合類所繼承或者實現對Collection接口有一個基本的實現是抽象類AbstractCollection它實現了大部分與具體數據結構無關的操作比如判斷一個對象是否存在於這個集合類中的contains()方法

   cellPadding= width=% align=center border=> ff>  public boolean contains(Object o) {

  Iterator e = iterator();

  if (o==null) {

  while (ehasNext())

  if (enext()==null)

  return true;

  } else {

  while (ehasNext())

  if (oequals(enext()))

  return true;

  }

  return false;

  }

  而這其中調用的iterator()方法是一個抽象方法有賴於具體的數據結構的實現但是對於這個containers()方法而言並不需要知道具體的Iterator實現而只需要知道它所提供的接口能夠完成某類任務既可這就是抽象類中抽象方法的作用其它的在AbstractCollection中實現的非抽象方法大部分都是依賴於抽象方法iterator()方法所提供的Iterator接口來實現的這種設計方法是引入抽象類的一個關鍵所在值得仔細領悟

  List接口繼承Collection接口提供對列表集合類的抽象;對應的AbstractList類繼承AbstractCollection並實現了List接口作為List的一個抽象基類它對其中非抽象方法的實現也大抵上與AbstractCollection相同這兒不再贅敘

  而對應於Collection的IteratorList有其自己的ListIteratorListIterator繼承於Iterator並添加了一些專用於List遍歷的方法

  boolean hasPrevious()判斷在列表中當前元素之前是否存在有元素

  Object previous()返回列表中當前元素之前的元素

  int nextIndex()

  int previousIndex()

  void set(Object o)

  void add(Object o)

  ListIterator針對List提供了更為強勁的功能接口在AbstractList中實現了具體的iterator()方法和listIterator()方法我們來看看這兩個方法是如何實現的

   cellPadding= width=% align=center border=> ff>  public Iterator iterator() {

  return new Itr(); //Itr是一個內部類

  }

  private class Itr implements Iterator {

  int cursor = ;//Iterator的計數器指示當前調用next()方法時會被返回的元素的位置

  int lastRet = ;//指示剛剛通過next()或者previous()方法被返回的元素的位置

  //表示剛剛調用的是remove()方法刪除了一個元素

  //modCount是定義在AbstractList中的字段指示列表被修改的次數Iterator用//這個值來檢查其包裝的列表是否被其他方法所非法修改

  int expectedModCount = modCount;

  public boolean hasNext() {

  return cursor != size();

  }

  public Object next() {

  try {

  //get方法仍然是一個抽象方法依賴於具體的子類實現

  Object next = get(cursor);

  //檢查列表是否被不正確的修改

  checkForComodification();

  lastRet = cursor++;

  return next;

  } catch(IndexOutOfBoundsException e) {

  checkForComodification();

  throw new NoSuchElementException();

  }

  }

  public void remove() {

  if (lastRet == )

  throw new IllegalStateException();

  checkForComodification();

  try {

  //同樣remove(int)也依賴於具體的子類實現

  AbstractListthisremove(lastRet);

  if (lastRet < cursor)

  cursor;

  lastRet = ;

  expectedModCount = modCount;

  } catch(IndexOutOfBoundsException e) {

  throw new ConcurrentModificationException();

  }

  }

  final void checkForComodification() {

  if (modCount != expectedModCount)

  throw new ConcurrentModificationException();

  }

  }

  這兒的設計技巧和上面一樣都是使用抽象方法來實現一個具體的操作抽象方法作為最後被實現的內容依賴於具體的子類抽象類看起來很像是一個介於接口和子類之間的一個東西

  從設計上來講有人建議所有的類都應該定義成接口的形式這當然有其道理但多少有些極端當你需要最大的靈活性的時候應該使用接口而抽象類卻能夠提供一些缺省的操作最大限度的統一子類抽象類在許多應用框架(Application Framework)中有著很重要的作用例如在一個框架中可以用抽象類來實現一些缺省的服務比如消息處理等等這些抽象類能夠讓你很容易並且自然的把自己的應用嵌入到框架中去而對於依賴於每個應用具體實現的方法可以通過定義抽象方法來引入到框架中

  其實在老版本的JDK中也有類似的概念被稱為EnumerationIterator其實與Enmeration功能上很相似只是多了刪除的功能用Iterator不過是在名字上變得更為貼切一些模式的另外一個很重要的功用就是能夠形成一種交流的語言(或者說文化)有時候你說Enumeration大家都不明白說Iterator就都明白了

  CompositeStrategy和IteratorComposite是一個結構性的模式用來協調整體和局部的關系使之能夠被統一的安排在一個樹形的結構中並簡化了編程Strategy模式與Bridge模式在結構上很相似但是與Bridge不同在於它是一個行為模式更側重於結構的語義以及算法的實現它使得程序能夠在不同的算法之間自由方便的作出選擇並能夠在運行時切換到其他的算法很大程度上增加了程序的靈活性Iterator模式提供統一的接口操作來實現對一個數據結構的遍歷使得當數據結構的內部算法發生改變時客戶代碼不需要任何的變化只需要改變相應的Iterator實現就可以無縫的集成在原來的程序中

  有了前面諸多設計模式的基礎這兒可以提出一個比較特殊的模式MVCMVC並不屬於GOF的個設計模式之列但是它在GOF的書中作為一個重要的例子被提出來並給予了很高的評價一般的來講我們認為GOF的個模式是一些中級的模式在它下面還可以抽象出一些更為一般的低層的模式在其上也可以通過組合來得到一些高級的模式MVC就可以看作是一些模式進行組合之後的結果(實際上MVC的出現要早於設計模式的提出這而只是對它在設計模式的基礎上進行在分析)如果沒有前面的基礎理解MVC或許會有一些困難

  MVC模式

  MVC模式比較的特別它含義比較的廣涉及的層面也不僅僅是設計這一塊不好簡單的把它歸為設計模式當然它主要還是作為一個設計的概念被提到的而且在Java體系中MVC有著至關重要的作用這兒提的是Java中的設計模式當然不好拉了它不講了

  關於MVC的來龍去脈這兒就不再講了這裡主s要講兩個方面的作為設計模式的MVC和作為體系結構模式的MVC

  所謂MVC指的是一種劃分系統功能的方法它將一個系統劃分為三個部分

  模型(Model)封裝的是數據源和所有基於對這些數據的操作在一個組件中Model往往表示組件的狀態和操作狀態的方法

  視圖(View)封裝的是對數據源Model的一種顯示一個模型可以由多個視圖而一個視圖理論上也可以同不同的模型關聯起來

  控制器(Control)封裝的是外界作用於模型的操作通常這些操作會轉發到模型上並調用模型中相應的一個或者多個方法一般Controller在Model和View之間起到了溝通的作用處理用戶在View上的輸入並轉發給Model這樣Model和View兩者之間可以做到松散耦合甚至可以彼此不知道對方而由Controller連接起這兩個部分

  有了前面介紹的諸多模式之後就可以很容易的通過模式來解釋MVC的內在行為了前面說過在設計模式中MVC實際上是一個比較高層的模式它由多個更基本的設計模式組合而成ModelView的關系實際上是Observer模式模型的狀態和試圖的顯示相互響應而ViewController則是由Strategy模式所描敘的View用一個特定的Controller的實例來實現一個特定的響應策略更換不同的Controller可以改變View對用戶輸入的響應而其它的一些設計模式也很容易組合到這個體系中比如通過Composite模式可以將多個View嵌套組合起來;通過FactoryMethod模式來指定View的Controller等等

  使用MVC的好處一方面分離數據和其表示使得添加或者刪除一個用戶視圖變得很容易甚至可以在程序執行時動態的進行Model和View能夠單獨的開發增加了程序了可維護性可擴展性並使測試變得更為容易另一方面將控制邏輯和表現界面分離允許程序能夠在運行時根據工作流用戶習慣或者模型狀態來動態選擇不同的用戶界面

  Swing號稱是完全按照MVC的思路來進行設計的在設計開始前Swing的希望能夠達到的目標就包括

  模型驅動(ModelDriven)的編程方式

  提供一套單一的API但是能夠支持多種視感(lookandfeel)為用戶提供不同的界面

  很自然的可以發現使用MVC模式能夠有助於實現上面的這兩個目標

  嚴格的說Swing中的MVC實際上是MVC的一個變體MVC Swing中只顯示的定義了Model接口而在一個UI對象中集成了視圖和控制器的部分機制View和Control比較松散的交叉組合在一起而更多的控制邏輯是在事件監聽者部分引入的

  但是這並沒有妨礙在Swing中體現MVC的精髓事實上在Swing的開發初期Swing確實是按照標准的MVC模式來設計的但是很快的問題就出現了View和Controller實際上是緊密耦合的很難作出一個能夠適應不同View的一般化的Controller來而且一般也沒有很大的必要

  在Swing中基本上每一個組件都會有對應的Model對象但其並不是一一對應的一個Model接口可以為多個Swing對向服務例如JProgressBarJScrollBarJSlider這三個組件使用的都是BoundedRangeModel接口這種模型的共享更能夠從分的體現MVC的內涵除了Model接口外為了實現多個視感間的自由切換每個Swing組件還包含一個UI接口也就是ViewController負責對組件的繪制和接受用戶輸入

  ModelView是Subject和Obverser的關系因而模型的改變必須要在UI對象中體現出來Swing使用了JavaBeans的事件模型來實現這種通知機制具體而言有兩種實現辦法一是僅僅通知事件監聽者狀態改變了然後由事件監聽者向模型提取必要的狀態信息這種機制對於事件頻繁的組件很有效另外的一種辦法是模型向監聽者發送包含了已改變的狀態信息的通知給UI這兩種方法根據其優劣被分別是現在不同的組件中比如在JScollBar中使用的是第一種方法在JTable中使用的是第二種方法而對Model而言為了能夠支持多個View它並不知道具體的每一個View它維護一個對其數據感興趣的Obverser的列表使得當數據改變的時候能夠通知到每一個Swing組件對象

  上面講到的是作為設計模式的MVC而在JEE中Sun更是將MVC提升到了一個體系結構模式的高度這兒的MVC的含義就更為廣泛了與Swing中不同的是在這兒MVC的各個部件不再是單純的類或者接口而是應用程序的一個組成部分!

  在JEE Blueprint中Sun推薦了一種基於MVC的JEE程序的模式對於企業級的分布式應用程序而言它更需要支持多種形式的用戶接口比如網上商店需要一個HTML的界面來同網上的客戶打交道WML的界面可以提供給無線用戶管理者可能需要傳統的基於Swing的應用程序來進行管理而對對商業伙伴基於XML的Web服務可能對他們更為方便

  MVC無疑是這樣一個問題的有效的解決方法通過在控制和顯示邏輯分離出核心的數據存取功能形成一個Model模塊能夠讓多種視圖來共享這個Model

  在JEE中有幾個核心的技術JSPJavaBeanServletEJBSessionBeanEntityBean構成了JEE構架的基石JSP能夠生成HTMLWML甚至XML它對應於Web應用程序中的View部分EJB作為數據庫與應用程序的中介提供了對數據的封裝一般EntityBean封裝的是數據SessionBean是封裝的是對數據的操作這兩個部分合起來對應於Web應用程序的Model部分在技術上JSP能夠直接對EJB進行存取但這並不是好辦法那樣會混淆程序中的顯示邏輯和控制邏輯使得JSP的重用性能降低這時候有兩種解決方法通過JavaBean或者Servlet作為中介的控制邏輯對EJB所封裝的數據進行存取這時JavaBean或者Servlet對應於Web引用程序中的Controller部分兩種類型的Controller各有其優缺點JSP同Servlet的交互不容易規范化使得交互的過程變得復雜但是Servlet可以單獨同用戶交互實際上JSP的運行時狀態就是Servlet;而由於JavaBean的規范性JSP同JavaBean的交互很容易利用JavaBean的get/set方法JSP不需要過多的語句就可以完成數據的存取這能夠讓JSP最大限度的集中在其視圖功能上而且在桌面應用程序中使用JavaBean也很容易而用Servlet就相對麻煩許多根據不同的問題背景可以選取不同的Controller有時候也可以兩者混合使用或者直接在Servlet中調用JavaBean

  在JEE中MVC是一個大的框架這時我們往往把它不再看作為設計模式而是作為體系結構模式的一個應用了

  總結

  在這篇文章中從設計的角度對Java的類庫進行了一些分析並著重於設計模式在其中的使用問題相信大家看了之後不論對Java類庫本身還是設計模式都應該有了一個更深層次的了解當然Java類庫是一個非常龐大的東西還有著許多設計精良的結構因而對Java源代碼的研究不論對於編碼還是設計都是很有裨益的本人接觸設計模式的時間並不很長對其的理解可能會有一些偏差如有謬誤的地方還請能夠提出大家能夠共同的探討

  需要說明的是對模式的描敘實際上是有一套完整的規格(或者語言)來進行的涉及到模式的意圖(Intent)問題描敘(Problem)背景(Context)約束(Force)解決方案(Solution)結果(Resulting Context)等等但這兒為了敘述的方便並沒有將它們一一列舉如果需要對模式有詳細系統的研究就應該對這些規格敘述有更深入的了解


From:http://tw.wingwit.com/Article/program/Java/gj/201311/27366.html
  • 上一篇文章:

  • 下一篇文章:
  • 推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.