組合,由于事物與事物之間存在某種關系,進而組織起來并形成某種結構并且可以共同發揮作用。組合模式所應用的就是樹形結構以表達“部分/整體”的層次結構。相信我們都知道“二叉樹”結構吧,根部分出來兩個枝杈(左節點,右節點),每個枝杈上又可以繼續分叉,直到末端的葉子為止。

1public abstract class Node {
2 protected String name;//節點命名
3
4 public Node(String name) {//構造節點,傳入節點名。
5 this.name = name;
6 }
7
8 //增加後續子節點方法
9 protected abstract void add(Node child);
10}
每個檔案夾或檔案都應該有一個名字,并且建立時必須聲明,是以在構造的時候必須傳入名字。第9行添加子節點方法我們做成抽象的,模糊其添加行為并留給子類去實作。下面添加檔案夾類并繼承自抽象節點。
1public class Folder extends Node{
2 //檔案夾可以包含子節點(檔案夾或者檔案)。
3 private List<Node> childrenNodes = new ArrayList<>();
4
5 public Folder(String name) {
6 super(name);//調用父類“節點”的構造方法命名。
7 }
8
9 @Override
10 protected void add(Node child) {
11 childrenNodes.add(child);//可以添加子節點。
12 }
13}
作為檔案夾類,我們承載着樹型結構的重任,是以這裡第3行我們的檔案夾類封裝了一個子節點的List,重點在于這裡模糊了其下檔案夾或檔案的概念,也就是說這個檔案夾既可以包含子檔案夾,又可以包含檔案。第5行的構造方法我們則交給父類構造完成,至于第10行的添加子節點方法,作為檔案夾類當然是需要實作的。反之作為葉子節點的檔案類,是不具備添加子節點功能的,看代碼。
1public class File extends Node{
2
3 public File(String name) {
4 super(name);
5 }
6
7 @Override
8 protected void add(Node child) {
9 System.out.println("不能添加子節點。");
10 }
11}
可以看到第9行我們在這裡實作了添加子節點方法并列印輸出一句錯誤資訊告知使用者“不能添加子節點”,其實更合适的做法是在此處抛出異常資訊。一切就緒,我們可以建構目錄并添加檔案了。
1public class Client {
2 public static void main(String[] args) {
3 Node driveD = new Folder("D盤");
4
5 Node doc = new Folder("文檔");
6 doc.add(new File("履歷.doc"));
7 doc.add(new File("項目介紹.ppt"));
8
9 driveD.add(doc);
10
11 Node music = new Folder("音樂");
12
13 Node jay = new Folder("周傑倫");
14 jay.add(new File("雙截棍.mp3"));
15 jay.add(new File("告白氣球.mp3"));
16 jay.add(new File("聽媽媽的話.mp3"));
17
18 Node jack = new Folder("張學友");
19 jack.add(new File("吻别.mp3"));
20 jack.add(new File("一千個傷心的理由.mp3"));
21
22 music.add(jay);
23 music.add(jack);
24
25 driveD.add(music);
26 }
27}
至此,我們已經告一段落了,我們将目錄結構規劃的非常好,以便對各種檔案進行分類管理以便日後查找。不止于此,我們這裡再做一些擴充,比如使用者需要列出目前目錄下的所有子目錄及檔案。
為了實作以上這種顯示方式,我們需要在名稱前加入空格。但需要加入幾個空格呢?這個問題上層目錄肯定知道,就由它主動傳入吧,我們來修改Node節點類并加入ls方法。
1public abstract class Node {
2 protected String name;//節點命名
3
4 public Node(String name) {//構造節點,傳入節點名。
5 this.name = name;
6 }
7
8 //增加後續子節點方法
9 protected abstract void add(Node child);
10
11 protected void ls(int space){
12 for (int i = 0; i < space; i++) {
13 System.out.print(" ");//先循環輸出n個空格;
14 }
15 System.out.println(name);//然後再列印自己的名字。
16 }
17}
這裡從第11行開始加入的ls方法不做抽象,而隻實作出檔案夾與檔案相同的行為片段,至于“不同”的行為片段則在子類中實作。
1public class File extends Node{
2
3 public File(String name) {
4 super(name);
5 }
6
7 @Override
8 protected void add(Node child) {
9 System.out.println("不能添加子節點。");
10 }
11
12 @Override
13 public void ls(int space){
14 super.ls(space);
15 }
16}
檔案類的實作與父類完全一緻,第13行開始直接調用父類繼承下來的ls方法即可。而檔案夾類則比較特殊了,不但要列出自己的名字,還要列出子節點的名字。
1public class Folder extends Node{
2 //檔案夾可以包含子節點(檔案夾或者檔案)。
3 private List<Node> childrenNodes = new ArrayList<>();
4
5 public Folder(String name) {
6 super(name);//調用父類“節點”的構造方法命名。
7 }
8
9 @Override
10 protected void add(Node child) {
11 childrenNodes.add(child);//可以添加子節點。
12 }
13
14 @Override
15 public void ls(int space){
16 super.ls(space);//調用父類共通的ls方法列出自己的名字。
17 space++;//之後列出的子節點前,空格數要增加一個了。
18 for (Node node : childrenNodes) {
19 node.ls(space);//調用子節點的ls方法。
20 }
21 }
22}
自第15行開始,檔案夾的ls方法先調用父類共通的ls方法列出自己的名字,然後再把空格數加1并傳給下一級的所有子節點,循環疊代,直至抵達葉子則傳回調用之初,完美的抽象遞歸。
最後,我們的client在任何一級節點上隻要調用ls(int space),并傳入目前目錄的偏移量(空格數)即可出現之前的樹形清單了,比如挨着左邊框顯示:ls(0)。或者我們幹脆給使用者再增加一個無參數重載方法,内部直接調用ls(0)即可。
1public abstract class Node {
2 protected String name;//節點命名
3
4 public Node(String name) {//構造節點,傳入節點名。
5 this.name = name;
6 }
7
8 //增加後續子節點方法
9 protected abstract void add(Node child);
10
11 protected void ls(int space){
12 for (int i = 0; i < space; i++) {
13 System.out.print(" ");//先循環輸出n個空格;
14 }
15 System.out.println(name);//然後再列印自己的名字。
16 }
17
18 //無參重載方法,預設從第0列開始顯示。
19 protected void ls(){
20 this.ls(0);
21 }
22}
這樣使用者可以抛開煩擾,直接調用ls()便是。
世界雖是紛繁複雜的,然而混沌中有序,從單細胞動物,細胞分裂,到進階動物;從二進制0與1,再到龐雜的軟體系統,再從“道生一,一生二”的陰陽哲學,再到“三生萬物的簡明玄妙”,分形無不揭示着世界的本質,其部分與整體結構或其行為總是以類似的形式湧現,分形之道如此,組合模式亦是如此。