設計模式——組合模式

一、引子

在大學的數據結構這門課上,樹是最重要的章節之一。還記得樹是怎麼定義的嗎?樹(Tree)是n(n≥0)個結點的有限集T,T爲空時稱爲空樹,否則它滿足如下兩個條件:
(1) 有且僅有一個特定的稱爲根(Root)的結點;
(2) 其餘的結點可分爲m(m≥0)個互不相交的子集Tl,T2,…,Tm,其中每個子集本身又是一棵樹,並稱其爲根的子樹(SubTree)。
上面給出的遞歸定義刻畫了樹的固有特性:一棵非空樹是由若干棵子樹構成的,而子樹又可由若干棵更小的子樹構成。而這裏的子樹可以是葉子也可以是分支。
今天要學習的組合模式就是和樹型結構以及遞歸有關係。

二、定義與結構

組合(Composite)模式的其它翻譯名稱也很多,比如合成模式、樹模式等等。在《設計模式》一書中給出的定義是:將對象以樹形結構組織起來,以達成“部分-整體”的層次結構,使得客戶端對單個對象和組合對象的使用具有一致性。
從定義中可以得到使用組合模式的環境爲:在設計中想表示對象的“部分-整體”層次結構;希望用戶忽略組合對象與單個對象的不同,統一地使用組合結構中的所有對象。
看下組合模式的組成。
1) 抽象構件角色Component:它爲組合中的對象聲明接口,也可以爲共有接口實現缺省行爲。
2) 樹葉構件角色Leaf:在組合中表示葉節點對象——沒有子節點,實現抽象構件角色聲明的接口。
3) 樹枝構件角色Composite:在組合中表示分支節點對象——有子節點,實現抽象構件角色聲明的接口;存儲子部件。
下圖爲組合模式的類圖表示。

這裏寫圖片描述

如圖所示:一個Composite實例可以像一個簡單的Leaf實例一樣,可以把它傳遞給任何使用Component的方法或者對象,並且它表現的就像是一個Leaf一樣。
可以看出來,使用組合模式使得這個設計結構非常靈活,在下面的例子中會得到進一步的印證。

三、安全性與透明性

組合模式中必須提供對子對象的管理方法,不然無法完成對子對象的添加刪除等等操作,也就失去了靈活性和擴展性。但是管理方法是在Component中就聲明還是在Composite中聲明呢?
一種方式是在Component裏面聲明所有的用來管理子類對象的方法,以達到Component接口的最大化(如下圖所示)。目的就是爲了使客戶看來在接口層次上樹葉和分支沒有區別——透明性。但樹葉是不存在子類的,因此Component聲明的一些方法對於樹葉來說是不適用的。這樣也就帶來了一些安全性問題。

這裏寫圖片描述

另一種方式就是隻在Composite裏面聲明所有的用來管理子類對象的方法(如下圖所示)。這樣就避免了上一種方式的安全性問題,但是由於葉子和分支有不同的接口,所以又失去了透明性。

這裏寫圖片描述

《設計模式》一書認爲:在這一模式中,相對於安全性,我們比較強調透明性。對於第一種方式中葉子節點內不需要的方法可以使用空處理或者異常報告的方式來解決。

四、舉例

這裏以JUnit中的組合模式的應用爲例(JUnit入門)。
JUnit是一個單元測試框架,按照此框架下的規範來編寫測試代碼,就可以使單元測試自動化。爲了達到“自動化”的目的,JUnit中定義了兩個概念:TestCase和TestSuite。TestCase是對一個類或者jsp等等編寫的測試類;而TestSuite是一個不同TestCase的集合,當然這個集合裏面也可以包含TestSuite元素,這樣運行一個TestSuite會將其包含的TestCase全部運行。
然而在真實運行測試程序的時候,是不需要關心這個類是TestCase還是TestSuite,我們只關心測試運行結果如何。這就是爲什麼JUnit使用組合模式的原因。
JUnit爲了採用組合模式將TestCase和TestSuite統一起來,創建了一個Test接口來扮演抽象構件角色,這樣原來的TestCase扮演組合模式中樹葉構件角色,而TestSuite扮演組合模式中的樹枝構件角色。下面將這三個類的有關代碼分析如下:

//Test接口——抽象構件角色
public interface Test {
       /**
        * Counts the number of test cases that will be run by this test.
        */
       public abstract int countTestCases();
       /**
        * Runs a test and collects its result in a TestResult instance.
        */
       public abstract void run(TestResult result);
}

//TestSuite類的部分有關源碼——Composite角色,它實現了接口Test
public class TestSuite implements Test {
//用了較老的Vector來保存添加的test
       private Vector fTests= new Vector(10);
       private String fName;
       …… 
/**
        * Adds a test to the suite.
        */
       public void addTest(Test test) {          
//注意這裏的參數是Test類型的。這就意味着TestCase和TestSuite以及以後實現Test接口的任何類都可以被添加進來
              fTests.addElement(test);
       }
       ……
       /**
        * Counts the number of test cases that will be run by this test.
        */
       public int countTestCases() {
              int count= 0;
              for (Enumeration e= tests(); e.hasMoreElements(); ) {
                     Test test= (Test)e.nextElement();
                     count= count + test.countTestCases();
              }
              return count;
       }
       /**
        * Runs the tests and collects their result in a TestResult.
        */
       public void run(TestResult result) {
              for (Enumeration e= tests(); e.hasMoreElements(); ) {
                    if (result.shouldStop() )
                           break;
                     Test test= (Test)e.nextElement();
                           //關鍵在這個方法上面
                     runTest(test, result);
              }
       }
            //這個方法裏面就是遞歸的調用了,至於你的Test到底是什麼類型的只有在運行的時候得知
            public void runTest(Test test, TestResult result) {
                   test.run(result);
            }
……
}

//TestCase的部分有關源碼——Leaf角色,你編寫的測試類就是繼承自它
public abstract class TestCase extends Assert implements Test {
       ……
       /**
        * Counts the number of test cases executed by run(TestResult result).
        */
       public int countTestCases() {
              return 1;
       }
/**
        * Runs the test case and collects the results in TestResult.
        */
       public void run(TestResult result) {
              result.run(this);
       }
……
}

可以看出這是一個偏重安全性的組合模式。因此在使用TestCase和TestSuite時,不能使用Test來代替。

五、優缺點

從上面的舉例中可以看到,組合模式有以下優點:
1) 使客戶端調用簡單,客戶端可以一致的使用組合結構或其中單個對象,用戶就不必關心自己處理的是單個對象還是整個組合結構,這就簡化了客戶端代碼。
2) 更容易在組合體內加入對象部件. 客戶端不必因爲加入了新的對象部件而更改代碼。這一點符合開閉原則的要求,對系統的二次開發和功能擴展很有利!
當然組合模式也少不了缺點:組合模式不容易限制組合中的構件。

六、總結

組合模式是一個應用非常廣泛的設計模式,在前面已經介紹過的解釋器模式、享元模式中都是用到了組合模式。它本身比較簡單但是很有內涵,掌握了它對你的開發設計有很大的幫助。
這裏寫下了我學習組合模式的總結,希望能給你帶來幫助,也希望您能給與指正。

原文地址:http://blog.csdn.net/ai92/article/details/298336

發佈了80 篇原創文章 · 獲贊 141 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章