設計模式學習筆記(十三)組合模式及其在樹形結構中的應用

歸斯君發表於2022-04-03

組合(Composite)模式,又叫做樹形模式,主要用來處理樹形結構資料。是將一組物件組織成樹形結構,以表示一種“部分-整體”的層次結構。讓客戶端可以統一單個物件和組合物件的處理邏輯。

組織架構圖

一、組合模式介紹

組合模式通過以樹形結構來表示“部分-整體”,使得使用者對葉物件和組合物件的使用具有一致性。也就是說在組合模式中,整個樹形結構的物件都屬於同一種型別,使用者可以對葉物件和組合物件統一處理。

1.1 組合模式分類

組合模式主要有透明式和安全式兩種分類,下面來分別說明

1.1.1 透明式組合模式

在該方式中,抽象構件宣告瞭所有子類中的全部方法,這樣實現抽象構件介面的所有子類都具備了全部方法,這樣的好處是葉節點和枝節點對於外界沒有任何區別,它們具備了完全一致的介面。但是對於葉節點有些本身不具備的方法,就可能會有安全隱患(空指標異常等)。其結構類圖如下所示:

image-20220403141237169

  • Component:抽象構件,為葉節點和樹枝節點宣告公共介面,以及訪問和管理子類的介面
  • Composite:樹枝構件,組合中的分支節點物件,作用是儲存和管理子部件
  • Leaf:樹葉構件,組合中的葉節點物件,用於繼承和實現抽象構件
  • Client:客戶端

1.1.2 安全式組合模式

前面提到透明式組合模式中,因為抽象構件宣告所有子類方法,有可能會造成安全問題。所以在安全式中,將管理葉節點的方法轉移到樹枝構件中,抽象構件和樹葉構件沒有對子物件的管理方法,這樣就避免了透明式組合模式中的安全問題。但是由於樹葉和樹枝構件有不同的介面,因此在使用時,就不能將兩種構件一概而論,對於客戶端呼叫方而言,就失去了透明性。其結構類圖如下所示:

image-20220403141317008

  • Component:抽象構件,為葉節點和樹枝節點宣告公共介面,沒有訪問和管理子類的介面
  • Composite:樹枝構件,組合中的分支節點物件,作用是儲存和管理子部件
  • Leaf:樹葉構件,組合中的葉節點物件,沒有對子類的管理方法
  • Client:客戶端

1.2 組合模式實現

根據上面的類圖,可以實現如下程式碼:

1.2.1 透明式組合模式實現

/**
 * @description: 透明式抽象構件
 * @author: wjw
 * @date: 2022/4/3
 */
public interface Component {

    /**公共操作方法**/
   void operation();

    /**
     * 新增構件
     * @param c 組合模式中的構件
     */
    void add(Component c);

    /**
     * 移除構件
     * @param c 組合模式中的構件
     */
    void remove(Component c);

    /**
     * 獲得子物件
     * @param t 子物件序號
     * @return  子物件
     */
    Component getChild(int t);

}

/**
 * @description: 樹枝節點
 * @author: wjw
 * @date: 2022/4/3
 */
public class Composite implements Component{

    private ArrayList<Component> children = new ArrayList<>();

    @Override
    public void operation() {
        for (Component child : children) {
            child.operation();
        }
    }

    @Override
    public void add(Component c) {
        children.add(c);
    }

    @Override
    public void remove(Component c) {
        children.remove(c);
    }

    @Override
    public Component getChild(int t) {
        return children.get(t);
    }
}

/**
 * @description: 樹葉節點
 * @author: wjw
 * @date: 2022/4/3
 */
public class Leaf implements Component{
    private String name;

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

    @Override
    public void operation() {
        System.out.println("我是樹葉節點:" + name);
    }

    @Override
    public void add(Component c) {

    }

    @Override
    public void remove(Component c) {

    }

    @Override
    public Component getChild(int t) {
        return null;
    }
}
/**
 * @description: 客戶端類
 * @author: wjw
 * @date: 2022/4/3
 */
public class Client {
    public static void main(String[] args) {
        Component component = new Composite();
        Component leaf1 = new Leaf("1");
        Component leaf2 = new Leaf("2");
        component.add(leaf1);
        component.add(leaf2);
        component.operation();
        component.getChild(1).operation();
        //這裡樹葉構件能呼叫add方法就會造成安全隱患
        leaf1.add(leaf1);
    }
}

客戶端執行結果:

我是樹葉節點:1
我是樹葉節點:2
我是樹葉節點:2

1.2.2 安全式組合模式實現

/**
 * @description: 安全式抽象構件
 * @author: wjw
 * @date: 2022/4/3
 */
public interface Component {

    /**公共操作方法**/
   void operation();
}

/**
 * @description: 樹枝節點
 * @author: wjw
 * @date: 2022/4/3
 */
public class Composite implements Component{

    private ArrayList<Component> children = new ArrayList<>();

    @Override
    public void operation() {
        for (Component child : children) {
            child.operation();
        }
    }


    public void add(Component c) {
        children.add(c);
    }


    public void remove(Component c) {
        children.remove(c);
    }


    public Component getChild(int t) {
        return children.get(t);
    }
}

/**
 * @description: 樹葉節點
 * @author: wjw
 * @date: 2022/4/3
 */
public class Leaf implements Component{
    private String name;

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

    @Override
    public void operation() {
        System.out.println("我是樹葉節點:" + name);
    }

}

/**
 * @description: 客戶端類
 * @author: wjw
 * @date: 2022/4/3
 */
public class Client {
    public static void main(String[] args) {
        Composite composite = new Composite();
        Leaf leaf1 = new Leaf("1");
        Leaf leaf2 = new Leaf("2");
        composite.add(leaf1);
        composite.add(leaf2);
        composite.operation();
    }
}

客戶端測試結果:

我是樹葉節點:1
我是樹葉節點:2

二、組合模式應用場景

組合模式常見的應用場景主要是出現樹形結構的地方,比如檔案目錄,公司人員架構圖等等

2.1 公司人員架構

比如按照部門和員工組織成樹形結構,可以統一處理薪資:

image-20220403152352420

/**
 * @description: 人力資源抽象構件
 * @author: wjw
 * @date: 2022/4/3
 */
public abstract class HumanResource {
    protected long id;
    protected double salary;

    public HumanResource(long id) {
        this.id = id;
    }

    public long getId() {
        return id;
    }

    /**
     * 計算工資
     * @return 工資結果
     */
    public abstract double calculateSalary();
}

/**
 * @description: 部門樹枝構件
 * @author: wjw
 * @date: 2022/4/3
 */
public class Department extends HumanResource{

    private List<HumanResource> humanResources = new ArrayList<>();

    public Department(long id) {
        super(id);
    }

    @Override
    public double calculateSalary() {
        double totalSalary = 0;
        for (HumanResource humanResource : humanResources) {
            totalSalary += humanResource.calculateSalary();
        }
        this.salary = totalSalary;
        return totalSalary;
    }

    public void addHumanResource(HumanResource humanResource) {
        humanResources.add(humanResource);
    }
}
/**
 * @description: 員工樹葉構件
 * @author: wjw
 * @date: 2022/4/3
 */
public class Employee extends HumanResource{

    public Employee(long id, double salary) {
        super(id);
        this.salary = salary;
    }

    @Override
    public double calculateSalary() {
        return salary;
    }
}

參考資料

《設計模式之美》

http://c.biancheng.net/view/1373.html

《Java 設計模式》

《設計模式:可複用物件導向軟體的基礎》

相關文章