設計模式【11】-- 搞定組合模式

第十六封發表於2022-01-19

設計模式

開局還是那種圖,各位客官往下看...

組合模式是什麼?

組合模式,將物件組合成樹形結構以表示“部分-整體”的層次結構。(百度百科)

其實,組合模式,又稱為部分整體模式,用於把一組相似的物件當作一個單一的物件。組合模式依據樹形結構來組合物件,用來表示部分以及整體層次。這種型別的設計模式屬於結構型模式,它建立了物件組的樹形結構。

關鍵字:一致性,整體部分

比如公司的組織架構,就是樹形的結構:

公司下面有部門與人,人是屬於部門,部門可以擁有子部門,如果我們將上面的節點,不管是組織,還是人,統一抽象成為一個node,那麼,我們並不需要關心當前節點到底是人,還是部門,統計人數的時候或者遍歷的時候,一視同仁。

還有就是Java Swing程式設計中,一般也會容器的說法:Container,我們在Container裡面可以放子的容器,也可以放具體的元件,比如Button或者Checkbox,其實這也是一種部分-整體的思維。

除此之外,最經典的是資料夾與檔案的表示,一個資料夾(容器物件)既可以存放資料夾(容器物件),也可以存放檔案(葉子物件)。如果把樹的每個節點攤平,那就是List。而樹結構,則是更能直觀的體現每個節點與整體的關係。

為什麼需要這個模式呢?它的目的是什麼?

主要是想要對外提供一致性的使用方式,即使容器物件與葉子物件之間屬性差別可能非常大,我們希望抽象出相同的地方,一致的處理。

組合模式的角色

組合模式中一般有以下三種角色:

  • 抽象構件(Component):一般是介面或者抽象類,是葉子構件和容器構件物件宣告介面,抽象出訪問以及管理子構件的方法。
  • 葉子節點(Leaf):在組合中表示葉子節點物件,葉子節點沒有子節點,也就沒有子構件。
  • 容器構件(Composite):容器節點可以包含子節點,子節點可以是葉子節點,也可以是容器節點。

注意:關鍵點就是抽象構件,所有節點都統一,不再需要呼叫者關心葉子節點與非葉子節點的差異。

組合模式的兩種實現

組合模式有兩種不同的實現,分別是透明模式安全模式

兩者的區別在於透明模式將組合使用的方法放到抽象類中,而安全模式則是放到具體實現類中

透明模式

透明模式是把組合的方法抽象到抽象類中,不管是葉子節點,還是組合節點,都有一樣的方法,這樣對外處理的時候是一致的,不過實際上有些方法對葉子節點而言,是沒有用的,有些累贅。

下面是程式碼實現:

抽象類,要求實現三個方法,增加,刪除,展示:

package designpattern.composite;

public abstract class Component {
    String name;

    public Component(String name) {
        this.name = name;
    }

    public abstract void add(Component component);

    public abstract void remove(Component component);

    public abstract void show(int depth);
}

組合類:

import java.util.ArrayList;
import java.util.List;

public class Composite extends Component {
    List<Component> childs = new ArrayList<>();

    public Composite(String name) {
        super(name);
    }

    @Override
    public void add(Component component) {
        this.childs.add(component);
    }

    @Override
    public void remove(Component component) {
        this.childs.remove(component);
    }

    @Override
    public void show(int depth) {
        for (int i = 0; i < depth; i++) {
            System.out.print("    ");
        }
        System.out.println(name + ": ");
        for (Component component : childs) {
            component.show(depth + 1);
        }
    }
}

葉子類:


public class Leaf extends Component {
    public Leaf(String name) {
        super(name);
    }

    @Override
    public void add(Component component) {

    }

    @Override
    public void remove(Component component) {

    }

    @Override
    public void show(int depth) {
        for (int i = 0; i < depth; i++) {
            System.out.print("    ");
        }
        System.out.println(name);
    }
}

測試類:

public class Test {
    public static void main(String[] args) {
        Composite folderRoot = new Composite("備忘錄資料夾");
        folderRoot.add(new Leaf("word 檔案"));
        folderRoot.add(new Leaf("ppt 檔案"));

        Composite folderLevel1 = new Composite("週報資料夾");
        folderLevel1.add(new Leaf("20210101週報"));
        folderRoot.add(folderLevel1);


        Composite folderLevel2 = new Composite("筆記資料夾");
        folderLevel2.add(new Leaf("jvm.ppt"));
        folderLevel2.add(new Leaf("redis.txt"));
        folderLevel1.add(folderLevel2);


        folderRoot.add(new Leaf("需求.txt"));


        Leaf leaf = new Leaf("bug單.txt");
        folderRoot.add(leaf);
        folderRoot.remove(leaf);

        folderRoot.show(0);
    }
}

執行結果如下:

備忘錄資料夾: 
    word 檔案
    ppt 檔案
    週報資料夾: 
        20210101週報
        筆記資料夾: 
            jvm.ppt
            redis.txt
    需求.txt

可以看到以上是一棵樹的結果,不管是葉子節點,還是組合節點,都是一樣的操作。

安全模式

安全模式,就是葉子節點和組合節點的特性分開,只有組合節點才有增加和刪除操作,而兩者都會擁有展示操作。但是如果同時對外暴露葉子節點和組合節點的話,使用起來還需要做特殊的判斷。

抽象元件:

public abstract class Component {
    String name;

    public Component(String name) {
        this.name = name;
    }

    public abstract void show(int depth);
}

元件構件:

public class Composite extends Component {
    List<Component> childs = new ArrayList<>();

    public Composite(String name) {
        super(name);
    }

    public void add(Component component) {
        this.childs.add(component);
    }

    public void remove(Component component) {
        this.childs.remove(component);
    }

    @Override
    public void show(int depth) {
        for (int i = 0; i < depth; i++) {
            System.out.print("    ");
        }
        System.out.println(name + ": ");
        for (Component component : childs) {
            component.show(depth + 1);
        }
    }
}

葉子節點:

public class Leaf extends Component {
    public Leaf(String name) {
        super(name);
    }

    @Override
    public void show(int depth) {
        for (int i = 0; i < depth; i++) {
            System.out.print("    ");
        }
        System.out.println(name);
    }
}

測試類不變,測試結果也一樣:

備忘錄資料夾: 
    word 檔案
    ppt 檔案
    週報資料夾: 
        20210101週報
        筆記資料夾: 
            jvm.ppt
            redis.txt
    需求.txt

安全模式中,葉子節點沒有多餘的方法,沒有空的方法,外面呼叫的時候,不會呼叫到空方法。但是需要對節點進行判斷,才能知道哪一個方法能調,哪一個方法不能調。

小結一下

組合模式的優點:

  • 可以分層次定義複雜物件,表示區域性和全部,客戶端可以忽略不同的節點的差異。
  • 從高層次呼叫,可以很順暢的呼叫到每一個區域性,一致性比較強。
  • 節點自由搭配,靈活度比較高。

缺點:

  • 在使用組合模式時,其葉子和組合節點的宣告都是實現類,而不是介面,違反了依賴倒置原則。

使用場景:

  • 希望忽略每個部分的差異,客戶端一致使用

  • 需要表現為樹形結構,以表示“整體-部分”的結構層次。

以一句網友的話結尾:

“張無忌學太極拳,忘記了所有招式,打倒了"玄冥二老",所謂"心中無招"。設計模式可謂招數,如果先學通了各種模式,又忘掉了所有模式而隨心所欲,可謂OO之最高境界。”

【作者簡介】
秦懷,公眾號【秦懷雜貨店】作者,個人網站:http://aphysia.cn,技術之路不在一時,山高水長,縱使緩慢,馳而不息。

劍指Offer全部題解PDF

開源程式設計筆記

設計模式系列:

相關文章