天天看點

設計模式是什麼鬼(組合)

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

設計模式是什麼鬼(組合)
當然,二叉樹算是最簡單的樹了,其實大自然中更多的是多叉樹結構,我們來觀察一些蕨類植物,宏觀上看貌似這隻是一片簡單的葉子。
設計模式是什麼鬼(組合)
然而,一花一世界,一葉一菩提。仔細觀察我們會發現葉子上又有小的枝葉,一個小的枝葉上又有更小的枝葉。我們不管從宏觀還是微觀次元上看都是類似的結構,這正取決于植物的DNA,無論在哪個次元上都是相同的生長方式。冥冥之中,好似存在着某種大自然的規律,類似的結構總是在重複、疊代地顯現出某種自似性。
設計模式是什麼鬼(組合)
這其實牽扯到一個數學概念:分形理論。不管是連綿的山川、飄浮的雲朵、岩石的斷裂口、樹冠、花菜、還是人類的大腦皮層……把這些部分與整體以某種方式相似的形體呈現出來就稱為分形。
設計模式是什麼鬼(組合)
從簡單到複雜,或是複雜到簡單,我們抽出任意一個“部分”,其與“整體”的結構是類似的。是以,上面提到的“樹”結構,無論是根、枝、還是葉子,我們都統統把他們抽象地稱為“節點”,模糊他們的行為差異,這樣我們便可以達到模糊簡單元素與複雜元素的目的。好了,開始代碼部分,這裡我們就拿類似樹結構的檔案系統目錄結構來舉例吧。
設計模式是什麼鬼(組合)
我們可以看到,從根目錄開始分支,下面可以包含檔案夾或者檔案,檔案夾下面可以繼續存放子檔案夾或檔案,而檔案則屬于“葉子”節點,下面不再有延續分支。不管三七二十一,我們籠統地把他們都抽象成”節點“。

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