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

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

編輯:關於JAVA

在上一部分中,介紹了兩個結構型的模式:Bridge和Decorator。這一部分的內容,將會 接著上面的講解,繼續我們的設計模式之旅。

這一部分,除了還會介紹一個結構型的Composite模式之外,還會有兩個行為模式登場。 實際上在前面的內容中,我們已經接觸到行為模式了:Observer和Command就是兩個典型的行 為模式。行為模式更多的注重於算法和對象建間職責的分配,也就是說,它會更多的關注於 這個模式系統之類的各對象協作間的語義,以及在對象間進行通訊的流控制。

Composite模式

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

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

圖十一:Composite模式的類圖

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

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

圖十二:Composite模式的一種變體

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

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

public abstract class Component{
   public abstract void operation();
   public void add(Component component){};
   public void remove(Component component){};
}
import java.util.*;
public class Composite extends Component{
   String name;
   ArrayList children = new ArrayList();
   public Composite(String name){
     this.name = name;
   }
   public void add(Component component){
     children.add(component);
   }
   public void remove(Component component){
     children.remove(component);
   }
   public void operation(){
     System.out.println(name);
     Iterator iterator = children.iterator();
     while(iterator.hasNext()){
       Component child = (Component)iterator.next();
       child.operation();
     }
   }
}
public class Leaf extends Component{
   String name;
   public Leaf(String name){
     this.name = name;
   }
   public void operation(){
     System.out.println(name);
   }
}

Strategy模式

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

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

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

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

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

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

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

圖十四:Strategy模式的類圖

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

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

Iterator模式

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()方法:

public boolean contains(Object o) {
   Iterator e = iterator();
   if (o==null) {
     while (e.hasNext())
     if (e.next()==null)
       return true;
   } else {
     while (e.hasNext())
     if (o.equals(e.next()))
       return true;
   }
   return false;
   }

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

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

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

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

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

int nextIndex():

int previousIndex():

void set(Object o):

void add(Object o):

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

public Iterator iterator() {
   return new Itr(); //Itr是一個內部類
   }
   private class Itr implements Iterator {
   int cursor = 0;//Iterator的計數器,指示當前調用next()方法時會被返回的元素 的位置
   int lastRet = -1;//指示剛剛通過next()或者previous()方法被返回的元素的位置 ,-1
//表示剛剛調用的是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 == -1)
     throw new IllegalStateException();
       checkForComodification();
     try {
//同樣remove(int)也依賴於具體的子類實現
     AbstractList.this.remove(lastRet);
     if (lastRet < cursor)
       cursor--;
     lastRet = -1;
     expectedModCount = modCount;
     } catch(IndexOutOfBoundsException e) {
     throw new ConcurrentModificationException();
     }
   }
   final void checkForComodification() {
     if (modCount != expectedModCount)
     throw new ConcurrentModificationException();
   }
   }

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

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

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

小結:

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

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