1. 程式人生 > >JAVA設計模式什麼鬼(組合)——作者:凸凹裡歐

JAVA設計模式什麼鬼(組合)——作者:凸凹裡歐

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

當然,二叉樹算是最簡單的樹了,其實大自然中更多的是多叉樹結構,我們來觀察一些蕨類植物,巨集觀上看貌似這只是一片簡單的葉子。

 

然而,一花一世界,一葉一菩提。仔細觀察我們會發現葉子上又有小的枝葉,一個小的枝葉上又有更小的枝葉。我們不管從巨集觀還是微觀維度上看都是類似的結構,這正取決於植物的DNA,無論在哪個維度上都是相同的生長方式。冥冥之中,好似存在著某種大自然的規律,類似的結構總是在重複、迭代地顯現出某種自似性。

這其實牽扯到一個數學概念:分形理論。不管是連綿的山川、飄浮的雲朵、岩石的斷裂口、樹冠、花菜、還是人類的大腦皮層……把這些部分與整體以某種方式相似的形體呈現出來就稱為分形。

從簡單到複雜,或是複雜到簡單,我們抽出任意一個“部分”,其與“整體”的結構是類似的。所以,上面提到的“樹”結構,無論是根、枝、還是葉子,我們都統統把他們抽象地稱為“節點”,模糊他們的行為差異,這樣我們便可以達到模糊簡單元素與複雜元素的目的。好了,開始程式碼部分,這裡我們就拿類似樹結構的檔案系統目錄結構來舉例吧。

我們可以看到,從根目錄開始分支,下面可以包含資料夾或者檔案,資料夾下面可以繼續存放子資料夾或檔案,而檔案則屬於“葉子”節點,下面不再有延續分支。不管三七二十一,我們籠統地把他們都抽象成”節點“。

 1 public 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行新增子節點方法我們做成抽象的,模糊其新增行為並留給子類去實現。下面新增資料夾類並繼承自抽象節點。

 1 public 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行的新增子節點方法,作為資料夾類當然是需要實現的。反之作為葉子節點的檔案類,是不具備新增子節點功能的,看程式碼。

 1 public 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行我們在這裡實現了新增子節點方法並列印輸出一句錯誤資訊告知使用者“不能新增子節點”,其實更合適的做法是在此處丟擲異常資訊。一切就緒,我們可以構建目錄並新增檔案了。

 1 public 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方法。

 1 public 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方法即可。而資料夾類則比較特殊了,不但要列出自己的名字,還要列出子節點的名字。

 1 public 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)即可。

 1 public 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,再到龐雜的軟體系統,再從“道生一,一生二”的陰陽哲學,再到“三生萬物的簡明玄妙”,分形無不揭示著世界的本質,其部分與整體結構或其行為總是以類似的形式湧現,分形之道如此,組合模式亦是如此。