天天看點

23種設計模式之_組合模式

前言

23種設計模式組合模式也屬于一種比較常用的模式,類似層級菜單,公司組織架構,資料結構中的樹形結構(root,Branch,Leaf)等等。

Uml通用類圖

23種設計模式之_組合模式

源碼分析

大家在上學的時候應該都學過“資料結構”這門課程吧,還記得其中有一節叫“二叉樹”吧,我們上

學那會兒這一章節是必考内容,左子樹,右子樹,什麼先序周遊後序周遊什麼,重點就是二叉樹的的周遊,

我還記得當時老師就說,考試的時候一定有二叉樹的建構和周遊,現在想起來還是覺的老師是正确的,樹

狀結果在實際項目應用的非常廣泛。

咱就先說個最常見的例子,公司的人事管理就是一個典型的樹狀結構,你想想你公司的結構是不是這樣。

最 高 的老大,往下一層一層的管理,最後到我們這層小兵,很典型的樹狀結構(說明一下,這不是二叉樹,有關二叉樹的定義可以翻翻以前的教科書),我們今天的任務就是要把這個樹狀結構實作出來,并且還要把它遍

曆一遍,你要确認你建立的樹是否有問題呀。

從這個樹狀結構上分析,有兩種節點:有分支的節點(如研發部經理)和無分支的節點(如員工 A、員

工 D 等),我們增加一點學術術語上去,總經理叫做根節點(是不是想到 XML 中的那個根節點 root,那就對

了),類似研發部經理有分支的節點叫做樹枝節點,類似員工 A 的無分支的節點叫做樹葉節點,都很形象,三個類型的的節點,那是不是定義三個類就可以?好,我們按照這個思路走下去,先看我們自己設計的類

圖:

23種設計模式之_組合模式

這個類圖是初學者最容易想到的類圖(如果你已經看明白這個類圖的缺陷了,就可以不看下邊的實作

了,我是循序漸進的講課,呵呵),我那來看這個實作:

先看最進階别的根節點的實作

package interfac;

import java.util.ArrayList;

public interface IRoot {
    // 得到總經理的資訊
    public String getInfo();

    // 總經理下邊要有小兵,那要能增加小兵,比如研發部總經理,這是個樹枝節點
    public void add(IBranch branch);

    // 那要能增加樹葉節點
    public void add(Ileaf leaf);

    // 既然能增加,那要還要能夠周遊,不可能總經理不知道他手下有哪些人
    public ArrayList getSubordinateInfo();
}      

這個根節點就是我們的總經理 CEO,然後看實作類:

package compositemodel;

import java.util.ArrayList;

import interfac.IBranch;
import interfac.IRoot;
import interfac.Ileaf;

public class RootNode implements IRoot {

    String name;// 姓名
    String postion;// 職位
    int pay;// 薪資
    ArrayList  branchs = new ArrayList();



    public RootNode(String name, String postion, int pay) {
        super();
        this.name = name;
        this.postion = postion;
        this.pay = pay;
    }


    public void add(IBranch branch) {

        branchs.add(branch);
    }

    public void add(Ileaf leaf) {

        branchs.add(leaf);
    }

    public ArrayList getSubordinateInfo() {

        return this.branchs;
    }


    public String getInfo() {

        return " 姓名:"+this.name+"  職位:"+ this.postion+"  薪資:"+ this.pay ;
    }

}      

很簡單,通過構造函數傳入參數,然後獲得資訊,還可以增加子樹枝節點(部門經理)和葉子節點(秘

書)。我們再來看 IBranch.java:

package interfac;

import java.util.ArrayList;

public interface IBranch {

    public String getInfo();

    // 總經理下邊要有小兵,那要能增加小兵,比如研發部總經理,這是個樹枝節點
    public void add(IBranch branch);

    // 那要能增加樹葉節點
    public void add(Ileaf leaf);

    // 既然能增加,那要還要能夠周遊,不可能總經理不知道他手下有哪些人
    public ArrayList getSubordinateInfo();
}      

下面看樹枝節點的實作:

package compositemodel;

import java.util.ArrayList;

import interfac.IBranch;
import interfac.Ileaf;

public class BranchNode implements IBranch {

    String name;// 姓名
    String postion;// 職位
    int pay;// 薪資

    ArrayList branchs = new ArrayList();

    public BranchNode(String name, String postion, int pay) {
        super();
        this.name = name;
        this.postion = postion;
        this.pay = pay;
    }

    public void addSubBranch(IBranch branch) {
        branchs.add(branch);
    }

    public ArrayList getSubordinateInfo() {

        return this.branchs;
    }

    public String getInfo() {

        return " 姓名:" + this.name + "  職位:" + this.postion + "  薪資:" + this.pay;
    }

    public void add(IBranch branch) {

        branchs.add(branch);
    }

    public void add(Ileaf leaf) {
        branchs.add(leaf);

    }

}      

最後看葉子節點,也就是員工的接口:

package interfac;

public interface Ileaf {

    // 得到自己資訊
    String getInfo();

}      

以及葉子節點的實作

package compositemodel;

import interfac.Ileaf;

public class LeafNode implements Ileaf {

    String name;// 姓名
    String postion;// 職位
    int pay;// 薪資

    public LeafNode(String name, String postion, int pay) {
        super();
        this.name = name;
        this.postion = postion;
        this.pay = pay;
    }

    public String getInfo() {

        return " 姓名:" + this.name + "  職位:" + this.postion + "  薪資:" + this.pay;
    }

}      

好了,所有的根節點,樹枝節點和葉子節點都已經實作了,從總經理、部門經理到最終的員工都已經

實作了,然後的工作就是組裝成一個樹狀結構和周遊這個樹狀結構,看 Client.java 程式:

package compositemodel;

import java.util.ArrayList;

import interfac.IBranch;
import interfac.IRoot;
import interfac.Ileaf;

/**
 * 組合模式适用屬性結構 層級關系
 * 
 * @author weichyang
 * 
 */
public class Client {

    public static void main(String[] args) {

        // 首先産生了一個根節點
        IRoot ceo = new RootNode("王大麻子", "總經理", 100000);
        // 産生三個部門經理,也就是樹枝節點
        IBranch developDep = new BranchNode("劉大瘸子", "研發部門經理", 10000);
        IBranch salesDep = new BranchNode("馬二拐子", "銷售部門經理", 20000);
        IBranch financeDep = new BranchNode("趙三駝子", "财務部經理", 30000);
        // 再把三個小組長産生出來
        IBranch firstDevGroup = new BranchNode("楊三乜斜", "開發一組組長", 5000);

        IBranch secondDevGroup = new BranchNode("吳大棒槌", "開發二組組長", 6000);
        // 剩下的及時我們這些小兵了,就是路人甲,路人乙
        Ileaf a = new LeafNode("a", "開發人員", 2000);
        Ileaf b = new LeafNode("b", "開發人員", 2000);
        Ileaf c = new LeafNode("c", "開發人員", 2000);
        Ileaf d = new LeafNode("d", "開發人員", 2000);
        Ileaf e = new LeafNode("e", "開發人員", 2000);
        Ileaf f = new LeafNode("f", "開發人員", 2000);
        Ileaf g = new LeafNode("g", "開發人員", 2000);
        Ileaf h = new LeafNode("h", "銷售人員", 5000);
        Ileaf i = new LeafNode("i", "銷售人員", 4000);
        Ileaf j = new LeafNode("j", "财務人員", 5000);
        Ileaf k = new LeafNode("k", "CEO秘書", 8000);
        Ileaf zhengLaoLiu = new LeafNode("鄭老六", "研發部副總", 20000);
        // 該産生的人都産生出來了,然後我們怎麼組裝這棵樹
        // 首先是定義總經理下有三個部門經理
        ceo.add(developDep);
        ceo.add(salesDep);
        ceo.add(financeDep);
        // 總經理下還有一個秘書
        ceo.add(k);
        // 定義研發部門 下的結構
        developDep.add(firstDevGroup);
        developDep.add(secondDevGroup);
        // 研發部經理下還有一個副總
        developDep.add(zhengLaoLiu);
        // 看看開發兩個開發小組下有什麼
        firstDevGroup.add(a);
        firstDevGroup.add(b);
        firstDevGroup.add(c);
        secondDevGroup.add(d);
        secondDevGroup.add(e);
        secondDevGroup.add(f);
        // 再看銷售部下的人員情況
        salesDep.add(h);
        salesDep.add(i);
        // 最後一個财務
        financeDep.add(j);

        // 樹狀結構寫完畢,然後我們列印出來
        System.out.println(ceo.getInfo());
        // 列印出來整個樹形
        getAllSubordinateInfo(ceo.getSubordinateInfo());

    }

    // 周遊所有的樹枝節點,列印出資訊
    private static void getAllSubordinateInfo(ArrayList subordinateList) {
        int length = subordinateList.size();
        for (int m = 0; m < length; m++) { // 定義一個ArrayList長度,不要在for循環中每次計算
            Object s = subordinateList.get(m);
            if (s instanceof LeafNode) { // 是個葉子節點,也就是員工
                Ileaf employee = (Ileaf) s;
                System.out.println(((Ileaf) s).getInfo());
            } else {
                IBranch branch = (IBranch) s;
                System.out.println(branch.getInfo());
                // 再遞歸調用
                getAllSubordinateInfo(branch.getSubordinateInfo());
            }
        }
    }
}      

運作結果:

姓名:王大麻子 職位:總經理 薪資:100000

姓名:劉大瘸子 職位:研發部門經理 薪資:10000

姓名:楊三乜斜 職位:開發一組組長 薪資:5000

姓名:a 職位:開發人員 薪資:2000

姓名:b 職位:開發人員 薪資:2000

姓名:c 職位:開發人員 薪資:2000

姓名:吳大棒槌 職位:開發二組組長 薪資:6000

姓名:d 職位:開發人員 薪資:2000

姓名:e 職位:開發人員 薪資:2000

姓名:f 職位:開發人員 薪資:2000

姓名:鄭老六 職位:研發部副總 薪資:20000

姓名:馬二拐子 職位:銷售部門經理 薪資:20000

姓名:h 職位:銷售人員 薪資:5000

姓名:i 職位:銷售人員 薪資:4000

姓名:趙三駝子 職位:财務部經理 薪資:30000

姓名:j 職位:财務人員 薪資:5000

姓名:k 職位:CEO秘書 薪資:8000

和我們期望要的結果一樣,一棵完整的樹就生成了,而且我們還能夠周遊。看類圖或程式的時候,你

有沒有發覺有問題?getInfo 每個接口都有為什麼不能抽象出來?Root 類和 Branch 類有什麼差别?為什麼

要定義成兩個接口兩個類?如果我要加一個任職期限,你是不是每個類都需要修改?如果我要後序周遊(從

員工找到他的上級上司)能做嗎?——徹底暈菜了!

問題很多,我們一個一個解決,先說抽象的問題,确實可以吧 IBranch 和 IRoot 合并成一個接口,這

個我們先肯定下來,這是個比較大的改動,我們先畫個類圖:

23種設計模式之_組合模式

這個類圖還是有點問題的,接口的作用是什麼?定義共性,那 ILeaf 和 IBranch 是不是也有共性呢?

有 getInfo(),我們是不是要把這個共性也已經封裝起來呢?好,我們再修改一下類圖:

23種設計模式之_組合模式

類圖上有兩個接口,ICorp 是公司所有人員的資訊的接口類,不管你是經理還是員工,你都有名字,職

位,薪水,這個定義成一個接口沒有錯,IBranch 有沒有必要呢?

先看到Icrop接口

package interfac;

public interface ICrop {
    String getInfo();
}
實作

import interfac.ICrop;
import interfac.Ileaf;

public class LeafNode implements ICrop {

    String name;// 姓名
    String postion;// 職位
    int pay;// 薪資

    public LeafNode(String name, String postion, int pay) {
        super();
        this.name = name;
        this.postion = postion;
        this.pay = pay;
    }

    public String getInfo() {

        return " 姓名:" + this.name + "  職位:" + this.postion + "  薪資:" + this.pay;
    }

}      

小兵就隻有這些資訊了,我們是具體幹活的,我們是管理不了其他同僚的,我們來看看那些經理和小

組長是怎麼實作的,先看接口:

package interfac;

import java.util.ArrayList;

public interface IBranch {

    // 那要能增加樹葉節點
    public void add(ICrop leaf);

    // 既然能增加,那要還要能夠周遊,不可能總經理不知道他手下有哪些人
    public ArrayList getSubordinateInfo();
}      

實作類

package compositemodel;

import java.util.ArrayList;

import interfac.IBranch;
import interfac.ICrop;
import interfac.Ileaf;

public class BranchNode implements IBranch, ICrop {

    String name;// 姓名
    String postion;// 職位
    int pay;// 薪資

    ArrayList branchs = new ArrayList();

    public BranchNode(String name, String postion, int pay) {
        super();
        this.name = name;
        this.postion = postion;
        this.pay = pay;
    }

    public ArrayList getSubordinateInfo() {

        return this.branchs;
    }

    public String getInfo() {

        return " 姓名:" + this.name + "  職位:" + this.postion + "  薪資:" + this.pay;
    }

    public void add(ICrop leaf) {
        branchs.add(leaf);

    }

}      

接口也是很簡單的,下面是實作類:

package compositemodel;

import java.util.ArrayList;

/**
 * 組合模式适用屬性結構 層級關系
 * 
 * @author weichyang
 * 
 */
public class Client {

    public static void main(String[] args) {

        // 首先産生了一個根節點
        BranchNode ceo = new BranchNode("王大麻子", "總經理", 100000);
        // 産生三個部門經理,也就是樹枝節點
        BranchNode developDep = new BranchNode("劉大瘸子", "研發部門經理", 10000);
        BranchNode salesDep = new BranchNode("馬二拐子", "銷售部門經理", 20000);
        BranchNode financeDep = new BranchNode("趙三駝子", "财務部經理", 30000);
        // 再把三個小組長産生出來
        BranchNode firstDevGroup = new BranchNode("楊三乜斜", "開發一組組長", 5000);

        BranchNode secondDevGroup = new BranchNode("吳大棒槌", "開發二組組長", 6000);
        // 剩下的及時我們這些小兵了,就是路人甲,路人乙
        LeafNode a = new LeafNode("a", "開發人員", 2000);
        LeafNode b = new LeafNode("b", "開發人員", 2000);
        LeafNode c = new LeafNode("c", "開發人員", 2000);
        LeafNode d = new LeafNode("d", "開發人員", 2000);
        LeafNode e = new LeafNode("e", "開發人員", 2000);
        LeafNode f = new LeafNode("f", "開發人員", 2000);
        LeafNode g = new LeafNode("g", "開發人員", 2000);
        LeafNode h = new LeafNode("h", "銷售人員", 5000);
        LeafNode i = new LeafNode("i", "銷售人員", 4000);
        LeafNode j = new LeafNode("j", "财務人員", 5000);
        LeafNode k = new LeafNode("k", "CEO秘書", 8000);
        LeafNode zhengLaoLiu = new LeafNode("鄭老六", "研發部副總", 20000);
        // 該産生的人都産生出來了,然後我們怎麼組裝這棵樹
        // 首先是定義總經理下有三個部門經理
        ceo.add(developDep);
        ceo.add(salesDep);
        ceo.add(financeDep);
        // 總經理下還有一個秘書
        ceo.add(k);
        // 定義研發部門 下的結構
        developDep.add(firstDevGroup);
        developDep.add(secondDevGroup);
        // 研發部經理下還有一個副總
        developDep.add(zhengLaoLiu);
        // 看看開發兩個開發小組下有什麼
        firstDevGroup.add(a);
        firstDevGroup.add(b);
        firstDevGroup.add(c);
        secondDevGroup.add(d);
        secondDevGroup.add(e);
        secondDevGroup.add(f);
        // 再看銷售部下的人員情況
        salesDep.add(h);
        salesDep.add(i);
        // 最後一個财務
        financeDep.add(j);

        // 樹狀結構寫完畢,然後我們列印出來
        System.out.println(ceo.getInfo());
        // 列印出來整個樹形
        getAllSubordinateInfo(ceo.getSubordinateInfo());

    }

    // 周遊所有的樹枝節點,列印出資訊
    private static void getAllSubordinateInfo(ArrayList subordinateList) {
        int length = subordinateList.size();
        for (int m = 0; m < length; m++) { // 定義一個ArrayList長度,不要在for循環中每次計算
            Object s = subordinateList.get(m);
            if (s instanceof LeafNode) { // 是個葉子節點,也就是員工
                LeafNode employee = (LeafNode) s;
                System.out.println(employee.getInfo());
            } else {
                BranchNode branch = (BranchNode) s;
                System.out.println(branch.getInfo());
                // 再遞歸調用
                getAllSubordinateInfo(branch.getSubordinateInfo());
            }
        }
    }
}      

運作結果是相同的

一個非常清理的樹狀人員資源管理圖出現了,那我們的程式是否還可以優化?可以!你看 Leaf 和 Branch

中都有 getInfo 資訊,是否可以抽象,好,我們抽象一下:

23種設計模式之_組合模式

你一看這個圖,樂了,能不樂嘛,減少很多工作量了,接口沒有了,改成抽象類了,IBranch 接口也沒

有了,直接把方法放到了實作類中了,那我們先來看抽象類:

package compositemodel;

public abstract class Crop {

    String name;// 姓名
    String postion;// 職位
    int pay;// 薪資

    public Crop(String name, String postion, int pay) {

        this.name = name;
        this.postion = postion;
        this.pay = pay;
    }

    public String getInfo() {
        return " 姓名:" + this.name + "  職位:" + this.postion + "  薪資:" + this.pay;
    }

}      

個改動比較多,就幾行代碼就完成了,确實就應該這樣,下面是小頭目的實作類:

package compositemodel;

import java.util.ArrayList;

public class BranchNode extends Crop {

    String name;// 姓名
    String postion;// 職位
    int pay;// 薪資

    ArrayList branchs = new ArrayList();

    public BranchNode(String name, String postion, int pay) {
        super(name, postion, pay);
    }

    public ArrayList getSubordinateInfo() {

        return this.branchs;
    }

    public void add(Crop leaf) {
        branchs.add(leaf);

    }

}      
// 周遊所有的樹枝節點,列印出資訊      
private static void getAllSubordinateInfo(ArrayList subordinateList) {
        int length = subordinateList.size();
        for (int m = 0; m < length; m++) { // 定義一個ArrayList長度,不要在for循環中每次計算
            Object s = subordinateList.get(m);
            if (s instanceof LeafNode) { // 是個葉子節點,也就是員工
                LeafNode employee = (LeafNode) s;
                System.out.println(employee.getInfo());
            } else {
                BranchNode branch = (BranchNode) s;
                System.out.println(branch.getInfo());
                // 再遞歸調用
                getAllSubordinateInfo(branch.getSubordinateInfo());
            }
        }
    }

    public static String getTreeInfo(BranchNode root) {
        ArrayList<Crop> subordinateList = root.getSubordinateInfo();
        String info = "";
        for (Crop s : subordinateList) {
            if (s instanceof LeafNode) { // 是員工就直接獲得資訊
                info = info + s.getInfo() + "\n";
            } else { // 是個小頭目
                info = info + s.getInfo() + "\n" + getTreeInfo((BranchNode) s);
            }
        }
        return info;
    }