一、
引子
在大學的數據結構這門課上,樹是最重要的章節之一。還記得樹是怎么定義的嗎?樹
(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)?????? 更容易在組合體內加入對象部件 . 客戶端不必因為加入了新的對象部件而更改代碼。這一點符合開閉原則的要求,對系統的二次開發和功能擴展很有利!
當然組合模式也少不了缺點:組合模式不容易限制組合中的構件。
六、
總結
組合模式是一個應用非常廣泛的設計模式,在前面已經介紹過的解釋器模式、享元模式中都是用到了組合模式。它本身比較簡單但是很有內涵,掌握了它對你的開發設計有很大的幫助。
這里寫下了我學習組合模式的總結,希望能給你帶來幫助,也希望您能給與指正。