程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 從Java類庫看設計模式(2)

從Java類庫看設計模式(2)

編輯:關於JAVA

在上一部分的內容中,我們講到什麼是模式,什麼是設計模式,以及對一個設計模式 Observer的詳細闡敘。相信大家對於模式的概念應該是比較的理解了。這部分及以後的內容 ,將會步入正題,從Java類庫的分析入手,來闡敘設計模式是如何應用到一個完美的設計中 的。實際上,Java類庫非常的龐雜,這兒不可能把所有能夠找到的設計模式的例子一一列舉 ,只是找了一些容易發現的例子。實際上也沒有必要,因為只要對一個設計模式有足夠的理 解,對於它的具體應用而言,倒是一件不是很困難的事情。

Command模式

在設計一般用途的軟件的時候,在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 。也就是說,我想對多個任務進行安排,比如掃描磁盤,我希望它每1個小時進行一次,而備 份數據,我希望它半個小時進行一次,等等等等。但是,我並不希望作為TaskSchedule的類 知道各個任務的細節內容,TaskSchedule應該只是知道Task本身,而對具體的實現任務的細 節並不理會。因而在這兒,我們就需要對TaskSchedule和Task進行解耦,將任務和具體的實 現分離出來,這不正是Command模式的用武之地嗎?

圖三:Command模式的應用例子

程序清單:

//抽象的Task接口,作為回調的Command模式的主體
public interface Task {
  public void taskPerform();
}
//具體的實現了Task接口的子類,實現特定的操作。
public class BackupTask implements Task{
  public void taskPerform(){
   System.out.println("Backup Task has been performed");
  }
}
//具體的實現了Task接口的子類,實現特定的操作。
public class ScanDiskTask implements Task{
  public void taskPerform(){
   System.out.println("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) {
   this.task = task;
  }
  public void setTimeInterval(long timeInterval) {
   this.timeInterval = timeInterval;
  }
  public long getTimeInterval() {
   return timeInterval;
  }
  public long getTimeLastDone() {
   return timeLastDone;
  }
  public void setTimeLastDone(long timeLastDone) {
   this.timeLastDone = timeLastDone;
  }
  public TaskEntry(Task task,long timeInteral){
   this.task=task;
   this.timeInterval =timeInteral;
  }
}
//調度管理Task的類,繼承Thread只是為了調用其sleep()方法,
//實際上,如果真的作Task調度的話,每個Task顯然應該用單獨的Thread來實現。
public class TaskSchedule extends java.lang.Thread {
  private java.util.Vector taskList=new java.util.Vector();
  private long sleeptime=10000000000l;//最短睡眠時間
  public void addTask(TaskEntry taskEntry){
   taskList.add(taskEntry);
   taskEntry.setTimeLastDone(System.currentTimeMillis());
   if (sleeptime>taskEntry.getTimeInterval())
   sleeptime=taskEntry.getTimeInterval();
  }
  //執行任務調度
  public void schedulePermorm(){
   try{
    sleep(sleeptime);
    Enumeration e = taskList.elements();
    while (e.hasMoreElements()) {
     TaskEntry te = (TaskEntry) e.nextElement();
     if (te.getTimeInterval() + te.getTimeLastDone() <
         System.currentTimeMillis()) {
      te.getTask().taskPerform();
      te.setTimeLastDone(System.currentTimeMillis());
      }
    }
   }catch (Exception e1){
    e1.printStackTrace();
   }
  }
  public static void main (String args[]){
   TaskSchedule schedule=new TaskSchedule();
   TaskEntry taks1=new TaskEntry(new ScanDiskTask(),10000);
   TaskEntry taks2=new TaskEntry(new BackupTask(),3000);
   schedule.addTask(taks1);
   schedule.addTask(taks2);
   while (true){
     schedule.schedulePermorm();
    }
  }
}

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

AbstractFactory 和 FactoryMethod

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

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

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()方法根據一個外部的環境變量 javax.xml.parsers.DocumentBuilderFactory的值來確定具體生成DocumentBuilderFactory 的哪一個子類。這兒的newInstance()是一個工廠方法。當DocumentBuilderFactory被創建後 ,可以調用其newDocumentBuilder()來創建具體一個DocumentBuilder的子類。然後再由 DocumentBuilder來生成Document等DOM對象。

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

//第一步:創建一個DocumentBuilderFactory。
     DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance ();
     //第二步:創建一個DocumentBuilder
     DocumentBuilder db = dbf.newDocumentBuilder();
     //第三步:解析XML文件得到一個Document對象
     Document doc = db.parse(new File(filename));

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

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

Singleton模式

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

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

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

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的實例被創建了。

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

小結:

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved