015.組合模式

公司的人事管理是一個(gè)典型的樹(shù)狀結(jié)構(gòu):

我們今天的任務(wù)就是要把這個(gè)樹(shù)狀結(jié)構(gòu)實(shí)現(xiàn)出來(lái)匾寝,并且還要把它遍歷一遍.

從這個(gè)樹(shù)狀結(jié)構(gòu)上分析,有兩種節(jié)點(diǎn):有分支的節(jié)點(diǎn)(如研發(fā)部經(jīng)理)和無(wú)分支的節(jié)點(diǎn)(如員工A、員工D等)僻澎,總經(jīng)理叫做根節(jié)點(diǎn)废封,類似研發(fā)部經(jīng)理有分支的節(jié)點(diǎn)叫做樹(shù)枝節(jié)點(diǎn)州泊,類似員工A的無(wú)分支的節(jié)點(diǎn)叫做樹(shù)葉節(jié)點(diǎn),三個(gè)類型的的節(jié)點(diǎn)漂洋,那是不是定義三個(gè)類就可以遥皂?好力喷,我們按照這個(gè)思路走下去,先看我們自己設(shè)計(jì)的類圖:

以下是上述類圖的實(shí)現(xiàn):

/**
 * 定義一個(gè)根節(jié)點(diǎn)演训,就為總經(jīng)理服務(wù)
 */
public interface IRoot {

    // 得到總經(jīng)理的信息
    String getInfo();

    // 總經(jīng)理下邊要有小兵弟孟,那要能增加小兵,比如研發(fā)部經(jīng)理样悟,這是個(gè)樹(shù)枝節(jié)點(diǎn)
    void add(IBranch branch);

    // 增加樹(shù)葉節(jié)點(diǎn)
    void add(ILeaf leaf);

    // 遍歷下屬
    ArrayList<Object> getSubordinateInfo();

}

/**
 * 樹(shù)枝節(jié)點(diǎn)拂募,也就是各個(gè)部門(mén)經(jīng)理和組長(zhǎng)的角色
 */
public interface IBranch {

    // 獲取信息
    String getInfo();

    // 增加數(shù)據(jù)節(jié)點(diǎn),例如研發(fā)部下的研發(fā)一組
    void add(IBranch branch);

    // 增加樹(shù)葉節(jié)點(diǎn)
    void add(ILeaf leaf);

    // 獲取下級(jí)信息
    ArrayList<Object> getSubordinateInfo();

}

/**
 * 葉子節(jié)點(diǎn)窟她,也就是最小的小兵了陈症,只能自己干活,不能指派別人了
 */
public interface ILeaf {

    // 獲得自己的信息
    String getInfo();

}

/**
 * 根節(jié)點(diǎn)的實(shí)現(xiàn)類
 */
public class Root implements IRoot {

    // 保存根節(jié)點(diǎn)下的樹(shù)枝節(jié)點(diǎn)和樹(shù)葉節(jié)點(diǎn)震糖,subordinate是下級(jí)的意思
    private ArrayList<Object> subordinateList = new ArrayList<>();
    // 根節(jié)點(diǎn)的名稱
    private String name = "";
    // 根節(jié)點(diǎn)的職位
    private String position = "";
    // 根節(jié)點(diǎn)的薪水
    private int salary = 0;

    // 通過(guò)構(gòu)造函數(shù)傳遞進(jìn)來(lái)總經(jīng)理的信息
    public Root(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    // 得到自己的信息
    @Override
    public String getInfo() {
        return String.format("名稱: %s\t職位: %s\t薪水: %s", this.name, this.position, this.salary);
    }

    // 增加樹(shù)枝節(jié)點(diǎn)
    @Override
    public void add(IBranch branch) {
        this.subordinateList.add(branch);
    }

    // 增加葉子節(jié)點(diǎn)录肯,比如秘書(shū),直接隸屬于總經(jīng)理
    @Override
    public void add(ILeaf leaf) {
        this.subordinateList.add(leaf);
    }

    // 獲得下級(jí)的信息
    @Override
    public ArrayList<Object> getSubordinateInfo() {
        return this.subordinateList;
    }
}

/**
 * 樹(shù)枝節(jié)點(diǎn)吊说,就是各個(gè)部門(mén)經(jīng)理和組長(zhǎng)的角色
 */
public class Branch implements IBranch {

    // 存儲(chǔ)子節(jié)點(diǎn)的信息
    private ArrayList<Object> subordinateList = new ArrayList<>();
    // 樹(shù)枝節(jié)點(diǎn)的名稱
    private String name = "";
    // 樹(shù)枝節(jié)點(diǎn)的職位
    private String position = "";
    // 樹(shù)枝節(jié)點(diǎn)的薪水
    private int salary = 0;

    // 通過(guò)構(gòu)造函數(shù)傳遞樹(shù)枝節(jié)點(diǎn)的參數(shù)
    public Branch(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    // 獲得自己樹(shù)枝節(jié)點(diǎn)的信息
    @Override
    public String getInfo() {
        return String.format("名稱: %s\t職位: %s\t薪水: %s", this.name, this.position, this.salary);
    }

    // 增加一個(gè)子樹(shù)枝節(jié)點(diǎn)
    @Override
    public void add(IBranch branch) {
        this.subordinateList.add(branch);
    }

    // 增加一個(gè)葉子節(jié)點(diǎn)
    @Override
    public void add(ILeaf leaf) {
        this.subordinateList.add(leaf);
    }

    // 獲得下級(jí)的信息
    @Override
    public ArrayList<Object> getSubordinateInfo() {
        return this.subordinateList;
    }
}

/**
 * 最小的葉子節(jié)點(diǎn)
 */
public class Leaf implements ILeaf {

    // 葉子叫什么名字
    private String name = "";
    // 葉子的職位
    private String position = "";
    // 葉子的薪水
    private int salary = 0;

    // 通過(guò)構(gòu)造函數(shù)傳遞信息
    public Leaf(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    // 最小的小兵只能獲得自己的信息了
    @Override
    public String getInfo() {
        return String.format("名稱: %s\t職位: %s\t薪水: %s", this.name, this.position, this.salary);
    }
}

好了论咏,所有的根節(jié)點(diǎn),樹(shù)枝節(jié)點(diǎn)和葉子節(jié)點(diǎn)都已經(jīng)實(shí)現(xiàn)了疏叨,從總經(jīng)理潘靖、部門(mén)經(jīng)理到最終的員工都已經(jīng)實(shí)現(xiàn)了,然后的工作就是組裝成一個(gè)樹(shù)狀結(jié)構(gòu)和遍歷這個(gè)樹(shù)狀結(jié)構(gòu)蚤蔓,看Client類:

/**
 * Client的作用是組裝這棵樹(shù)卦溢,并遍歷一遍
 */
public class Client {

    public static void main(String[] args) {

        // 首先產(chǎn)生了一個(gè)根節(jié)點(diǎn)
        IRoot ceo = new Root("王大麻子", "CEO", 100000);

        // 產(chǎn)生三個(gè)部門(mén)經(jīng)理,也就是樹(shù)枝節(jié)點(diǎn)
        IBranch developDep = new Branch("劉大瘸子", "研發(fā)部經(jīng)理", 10000);
        IBranch salesDep = new Branch("馬兒拐子", "銷售部經(jīng)理", 20000);
        IBranch financeDep = new Branch("趙三駝子", "財(cái)務(wù)部經(jīng)理", 30000);

        // 再把三個(gè)小組長(zhǎng)產(chǎn)生出來(lái)
        IBranch firstDevGroup = new Branch("楊三乜斜", "開(kāi)發(fā)一組組長(zhǎng)", 5000);
        IBranch secondDevGroup = new Branch("吳大棒槌", "開(kāi)發(fā)二組組長(zhǎng)", 6000);

        // 剩下的就是我們這些小兵了,就是路人甲秀又,路人乙
        ILeaf zhengLaoLiu = new Leaf("鄭老六", "研發(fā)部副總", 20000);
        ILeaf a = new Leaf("A", "開(kāi)發(fā)人員", 2000);
        ILeaf b = new Leaf("B", "開(kāi)發(fā)人員", 2000);
        ILeaf c = new Leaf("C", "開(kāi)發(fā)人員", 2000);
        ILeaf d = new Leaf("D", "開(kāi)發(fā)人員", 2000);
        ILeaf e = new Leaf("E", "開(kāi)發(fā)人員", 2000);
        ILeaf f = new Leaf("F", "開(kāi)發(fā)人員", 2000);
        ILeaf g = new Leaf("G", "開(kāi)發(fā)人員", 2000);
        ILeaf h = new Leaf("H", "銷售人員", 5000);
        ILeaf i = new Leaf("I", "銷售人員", 4000);
        ILeaf j = new Leaf("J", "財(cái)務(wù)人員", 5000);
        ILeaf k = new Leaf("K", "CEO秘書(shū)", 8000);

        // 組裝這棵樹(shù)
        // 首先是定義總經(jīng)理下有三個(gè)部門(mén)經(jīng)理
        ceo.add(developDep);
        ceo.add(salesDep);
        ceo.add(financeDep);
        // 總經(jīng)理下還有一個(gè)秘書(shū)
        ceo.add(k);

        // 定義研發(fā)部門(mén)下的結(jié)構(gòu)
        developDep.add(firstDevGroup);
        developDep.add(secondDevGroup);
        // 研發(fā)部經(jīng)理下還有一個(gè)副總
        developDep.add(zhengLaoLiu);

        // 看看開(kāi)發(fā)兩個(gè)開(kāi)發(fā)小組下有什么
        firstDevGroup.add(a);
        firstDevGroup.add(b);
        firstDevGroup.add(c);
        secondDevGroup.add(d);
        secondDevGroup.add(e);
        secondDevGroup.add(f);

        // 再看銷售部下的人員情況
        salesDep.add(h);
        salesDep.add(i);
        // 最后一個(gè)財(cái)務(wù)
        financeDep.add(j);

        // 樹(shù)狀結(jié)構(gòu)寫(xiě)完畢单寂,然后我們打印出來(lái)
        System.out.println(ceo.getInfo());

        //打印出來(lái)整個(gè)樹(shù)形
        getAllSubordinateInfo(ceo.getSubordinateInfo());

    }

    // 遍歷所有的樹(shù)枝節(jié)點(diǎn),打印出信息
    private static void getAllSubordinateInfo(ArrayList<Object> subordinateList) {
        for (Object obj : subordinateList) {
            if (obj instanceof Leaf) {
                ILeaf leaf = (ILeaf)obj;
                System.out.println(leaf.getInfo());
            } else {
                IBranch branch = (IBranch)obj;
                System.out.println(branch.getInfo());
                getAllSubordinateInfo(branch.getSubordinateInfo());
            }
        }
    }

}

和我們期望要的結(jié)果一樣吐辙,一棵完整的樹(shù)就生成了宣决,而且我們還能夠遍歷,但這樣的類設(shè)計(jì)是有問(wèn)題的昏苏,getInfo()每個(gè)接口都有為什么不能抽象出來(lái)尊沸?Root類和Branch類有什么差別?為什么要定義成兩個(gè)接口兩個(gè)類贤惯?如果我要加一個(gè)任職期限洼专,是不是每個(gè)類都需要修改?如果我要后序遍歷(從員工找到他的上級(jí)領(lǐng)導(dǎo))能做嗎孵构?

問(wèn)題很多屁商,我們一個(gè)一個(gè)解決,先說(shuō)抽象的問(wèn)題颈墅,確實(shí)可以把IBranchIRoot合并成一個(gè)接口蜡镶,這個(gè)我們先肯定下來(lái)雾袱,這是個(gè)比較大的改動(dòng),我們先畫(huà)個(gè)類圖:

這個(gè)類圖還是有點(diǎn)問(wèn)題的官还,接口的作用是什么芹橡?定義共性,那ILeafIBranch是不是也有共性呢妻枕?有getInfo()僻族,我們是不是把這個(gè)共性也已經(jīng)封裝起來(lái),再修改一下類圖:

類圖上有兩個(gè)接口屡谐,ICorp是公司所有人員的信息的接口類述么,不管你是經(jīng)理還是員工,你都有名字愕掏,職位邢笙,薪水如捅,這個(gè)定義成一個(gè)接口沒(méi)有錯(cuò),IBranch 有沒(méi)有必要呢?我們先實(shí)現(xiàn)出來(lái)然后再說(shuō):

/**
 * 公司類档冬,定義每個(gè)員工都有信息
 */
public interface ICorp {

    // 獲取信息
    String getInfo();

}

public class Leaf implements ICorp {

    // 小兵的名字
    private String name = "";
    // 小兵的職位
    private String position = "";
    // 小兵的薪水
    private int salary = 0;

    // 通過(guò)構(gòu)造函數(shù)傳遞信息
    public Leaf(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    // 獲得小兵的信息
    @Override
    public String getInfo() {
        return String.format("名稱: %s\t職位: %s\t薪水: %s", this.name, this.position, this.salary);
    }
}

/**
 * 樹(shù)枝節(jié)點(diǎn)起趾,有下屬節(jié)點(diǎn)
 */
public interface IBranch {

    // 能夠增加小兵(樹(shù)葉節(jié)點(diǎn))或者是經(jīng)理(樹(shù)枝節(jié)點(diǎn))
    void addSubordinate(ICorp corp);

    // 獲取下級(jí)信息
    ArrayList<ICorp> getSubordinateInfo();

}

/**
 * 樹(shù)枝節(jié)點(diǎn)班巩,就是各個(gè)部門(mén)經(jīng)理和組長(zhǎng)的角色
 */
public class Branch implements IBranch, ICorp {

    // 下級(jí)
    private ArrayList<ICorp> subordinateList = new ArrayList<>();
    //姓名
    private String name = "";
    // 職位
    private String position = "";
    // 薪水
    private int salary = 0;

    // 通過(guò)構(gòu)造函數(shù)傳遞樹(shù)枝節(jié)點(diǎn)的參數(shù)
    public Branch(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    // 增加一個(gè)下屬查近,可能是小頭目,也可能是個(gè)小兵
    @Override
    public void addSubordinate(ICorp corp) {
        this.subordinateList.add(corp);
    }

    @Override
    public ArrayList<ICorp> getSubordinateInfo() {
        return this.subordinateList;
    }

    // 獲取自己的信息
    @Override
    public String getInfo() {
        return String.format("名稱: %s\t職位: %s\t薪水: %s", this.name, this.position, this.salary);
    }
}

/**
 * @author YangYunhe
 * @date 2020-12-28 10:20
 * @description 組裝樹(shù)形結(jié)構(gòu)
 */
public class Client {

    public static void main(String[] args) {

        // 組裝一個(gè)組織結(jié)構(gòu)
        Branch ceo = compositeCorpTree();

        // 打印CEO的信息
        System.out.println(ceo.getInfo());

        // 打印所有員工的信息
        System.out.println(getTreeInfo(ceo));

    }

    // 遍歷整棵樹(shù)语卤,只要給我根節(jié)點(diǎn)追逮,我就能遍歷出所有的節(jié)點(diǎn)
    public static String getTreeInfo(Branch root) {
        StringBuilder info = new StringBuilder();
        ArrayList<ICorp> subordinateList = root.getSubordinateInfo();
        for (ICorp iCorp : subordinateList) {
            if(iCorp instanceof Leaf) {
                info.append(iCorp.getInfo()).append("\n");
            } else {
                info.append(iCorp.getInfo()).append("\n").append(getTreeInfo((Branch)iCorp));
            }
        }
        return info.toString();
    }

    public static Branch compositeCorpTree() {
        // 首先產(chǎn)生了CEO
        Branch ceo = new Branch("王大麻子", "CEO", 100000);
        // 產(chǎn)生三個(gè)部門(mén)經(jīng)理
        Branch developDep = new Branch("劉大瘸子", "研發(fā)部經(jīng)理", 10000);
        Branch salesDep = new Branch("馬兒拐子", "銷售部經(jīng)理", 20000);
        Branch financeDep = new Branch("趙三駝子", "財(cái)務(wù)部經(jīng)理", 30000);
        // 再把三個(gè)小組長(zhǎng)產(chǎn)生出來(lái)
        Branch firstDevGroup = new Branch("楊三乜斜", "開(kāi)發(fā)一組組長(zhǎng)", 5000);
        Branch secondDevGroup = new Branch("吳大棒槌", "開(kāi)發(fā)二組組長(zhǎng)", 6000);
        // 把所有的小兵都創(chuàng)建出來(lái)
        Leaf zhengLaoLiu = new Leaf("鄭老六", "研發(fā)部副總", 20000);
        Leaf a = new Leaf("A", "開(kāi)發(fā)人員", 2000);
        Leaf b = new Leaf("B", "開(kāi)發(fā)人員", 2000);
        Leaf c = new Leaf("C", "開(kāi)發(fā)人員", 2000);
        Leaf d = new Leaf("D", "開(kāi)發(fā)人員", 2000);
        Leaf e = new Leaf("E", "開(kāi)發(fā)人員", 2000);
        Leaf f = new Leaf("F", "開(kāi)發(fā)人員", 2000);
        Leaf g = new Leaf("G", "開(kāi)發(fā)人員", 2000);
        Leaf h = new Leaf("H", "銷售人員", 5000);
        Leaf i = new Leaf("I", "銷售人員", 4000);
        Leaf j = new Leaf("J", "財(cái)務(wù)人員", 5000);
        Leaf k = new Leaf("K", "CEO秘書(shū)", 8000);

        // 組裝這棵樹(shù)
        // 定義CEO下的三個(gè)部門(mén)經(jīng)理和一個(gè)秘書(shū)
        ceo.addSubordinate(developDep);
        ceo.addSubordinate(salesDep);
        ceo.addSubordinate(financeDep);
        // 總經(jīng)理下還有一個(gè)秘書(shū)
        ceo.addSubordinate(k);

        // 定義研發(fā)部門(mén)下的結(jié)構(gòu)
        developDep.addSubordinate(firstDevGroup);
        developDep.addSubordinate(secondDevGroup);
        developDep.addSubordinate(zhengLaoLiu);

        // 定義兩個(gè)開(kāi)發(fā)小組下的結(jié)構(gòu)
        firstDevGroup.addSubordinate(a);
        firstDevGroup.addSubordinate(b);
        firstDevGroup.addSubordinate(c);
        secondDevGroup.addSubordinate(d);
        secondDevGroup.addSubordinate(e);
        secondDevGroup.addSubordinate(f);

        // 定義銷售部下的人員
        salesDep.addSubordinate(h);
        salesDep.addSubordinate(i);
        // 定義財(cái)務(wù)部下的人員
        financeDep.addSubordinate(j);

        return ceo;
    }

}

我們的程序還可以繼續(xù)優(yōu)化,LeafBranch中都有getInfo() 方法粹舵,可以抽象出來(lái):

/**
 * 公司人員抽象類
 */
public abstract class Corp {

    // 姓名
    private String name = "";
    // 職位
    private String position = "";
    // 薪水
    private int salary = 0;

    public Corp(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    public String getInfo() {
        return String.format("名稱: %s\t職位: %s\t薪水: %s", this.name, this.position, this.salary);
    }

}

/**
 * 普通員工很簡(jiǎn)單钮孵,就寫(xiě)一個(gè)構(gòu)造函數(shù)就可以了
 */
public class Leaf extends Corp {

    public Leaf(String name, String position, int salary) {
        super(name, position, salary);
    }
}

/**
 * 節(jié)點(diǎn)類,也簡(jiǎn)單了很多
 */
public class Branch extends Corp {

    // 領(lǐng)導(dǎo)下邊有那些下級(jí)領(lǐng)導(dǎo)和小兵
    private ArrayList<Corp> subordinateList = new ArrayList<>();

    public Branch(String name, String position, int salary) {
        super(name, position, salary);
    }

    // 增加一個(gè)下屬眼滤,可能是小頭目巴席,也可能是個(gè)小兵
    public void addSubordinate(Corp corp) {
        this.subordinateList.add(corp);
    }

    // 我有哪些下屬
    public ArrayList<Corp> getSubordinateInfo() {
        return this.subordinateList;
    }

}

public class Client {

    public static void main(String[] args) {

        // 組裝一個(gè)組織結(jié)構(gòu)
        Branch ceo = compositeCorpTree();

        // 打印CEO的信息
        System.out.println(ceo.getInfo());

        // 打印所有員工的信息
        System.out.println(getTreeInfo(ceo));

    }

    // 遍歷整棵樹(shù),只要給我根節(jié)點(diǎn)诅需,我就能遍歷出所有的節(jié)點(diǎn)
    public static String getTreeInfo(Branch root) {
        StringBuilder info = new StringBuilder();
        ArrayList<Corp> subordinateList = root.getSubordinateInfo();
        for (Corp corp : subordinateList) {
            if(corp instanceof Leaf) {
                info.append(corp.getInfo()).append("\n");
            } else {
                info.append(corp.getInfo()).append("\n").append(getTreeInfo((Branch)corp));
            }
        }
        return info.toString();
    }

    public static Branch compositeCorpTree() {
        // 首先產(chǎn)生了CEO
        Branch ceo = new Branch("王大麻子", "CEO", 100000);
        // 產(chǎn)生三個(gè)部門(mén)經(jīng)理
        Branch developDep = new Branch("劉大瘸子", "研發(fā)部經(jīng)理", 10000);
        Branch salesDep = new Branch("馬兒拐子", "銷售部經(jīng)理", 20000);
        Branch financeDep = new Branch("趙三駝子", "財(cái)務(wù)部經(jīng)理", 30000);
        // 再把三個(gè)小組長(zhǎng)產(chǎn)生出來(lái)
        Branch firstDevGroup = new Branch("楊三乜斜", "開(kāi)發(fā)一組組長(zhǎng)", 5000);
        Branch secondDevGroup = new Branch("吳大棒槌", "開(kāi)發(fā)二組組長(zhǎng)", 6000);
        // 把所有的小兵都創(chuàng)建出來(lái)
        Leaf zhengLaoLiu = new Leaf("鄭老六", "研發(fā)部副總", 20000);
        Leaf a = new Leaf("A", "開(kāi)發(fā)人員", 2000);
        Leaf b = new Leaf("B", "開(kāi)發(fā)人員", 2000);
        Leaf c = new Leaf("C", "開(kāi)發(fā)人員", 2000);
        Leaf d = new Leaf("D", "開(kāi)發(fā)人員", 2000);
        Leaf e = new Leaf("E", "開(kāi)發(fā)人員", 2000);
        Leaf f = new Leaf("F", "開(kāi)發(fā)人員", 2000);
        Leaf g = new Leaf("G", "開(kāi)發(fā)人員", 2000);
        Leaf h = new Leaf("H", "銷售人員", 5000);
        Leaf i = new Leaf("I", "銷售人員", 4000);
        Leaf j = new Leaf("J", "財(cái)務(wù)人員", 5000);
        Leaf k = new Leaf("K", "CEO秘書(shū)", 8000);

        // 組裝這棵樹(shù)
        // 定義CEO下的三個(gè)部門(mén)經(jīng)理和一個(gè)秘書(shū)
        ceo.addSubordinate(developDep);
        ceo.addSubordinate(salesDep);
        ceo.addSubordinate(financeDep);
        // 總經(jīng)理下還有一個(gè)秘書(shū)
        ceo.addSubordinate(k);

        // 定義研發(fā)部門(mén)下的結(jié)構(gòu)
        developDep.addSubordinate(firstDevGroup);
        developDep.addSubordinate(secondDevGroup);
        developDep.addSubordinate(zhengLaoLiu);

        // 定義兩個(gè)開(kāi)發(fā)小組下的結(jié)構(gòu)
        firstDevGroup.addSubordinate(a);
        firstDevGroup.addSubordinate(b);
        firstDevGroup.addSubordinate(c);
        secondDevGroup.addSubordinate(d);
        secondDevGroup.addSubordinate(e);
        secondDevGroup.addSubordinate(f);

        // 定義銷售部下的人員
        salesDep.addSubordinate(h);
        salesDep.addSubordinate(i);
        // 定義財(cái)務(wù)部下的人員
        financeDep.addSubordinate(j);

        return ceo;
    }

}

經(jīng)過(guò)這樣一步步的改造漾唉,類、接口減少了很多堰塌,而且程序也簡(jiǎn)單了很多赵刑。

上面我們講到的就是組合模式(也叫合成模式),有時(shí)又叫做部分-整體模式(Part-Whole)蔫仙,主要是用來(lái)描述整體與部分的關(guān)系料睛,用的最多的地方就是樹(shù)形結(jié)構(gòu)丐箩。組合模式通用類圖如下:

我們先來(lái)說(shuō)說(shuō)組合模式的幾個(gè)角色:

  • 抽象構(gòu)件角色(Component):定義參加組合的對(duì)象的共有方法和屬性摇邦,可以定義一些默認(rèn)的行為或?qū)傩孕羯罚槐热缥覀兝又械?code>getInfo() 就封裝到了抽象類中。

  • 葉子構(gòu)件(Leaf):葉子對(duì)象施籍,其下再也沒(méi)有其他的分支居扒。

  • 樹(shù)枝構(gòu)件(Composite):樹(shù)枝對(duì)象,它的作用是組合樹(shù)枝節(jié)點(diǎn)和葉子節(jié)點(diǎn)丑慎;

組合模式有兩種模式喜喂,透明模式和安全模式,這兩個(gè)模式有什么區(qū)別呢竿裂?先看類圖:

這兩種模式各有優(yōu)缺點(diǎn)玉吁,透明模式是把用來(lái)組合使用的方法放到抽象類中,比如add()腻异、remove()以及getChildren() 等方法进副,(順便說(shuō)一下,getChildren() 一般返回的結(jié)果為Iterable的實(shí)現(xiàn)類)悔常,不管葉子對(duì)象還是樹(shù)枝對(duì)象都有相同的結(jié)構(gòu)影斑,通過(guò)判斷getChildren 的返回值確認(rèn)是葉子節(jié)點(diǎn)還是樹(shù)枝節(jié)點(diǎn),如果處理不當(dāng)机打,這個(gè)會(huì)在運(yùn)行期出現(xiàn)問(wèn)題矫户,不建議使用這種方式;安全模式把樹(shù)枝節(jié)點(diǎn)和樹(shù)葉節(jié)點(diǎn)徹底分開(kāi)残邀,樹(shù)枝節(jié)點(diǎn)單獨(dú)擁有用來(lái)組合的方法皆辽,這種方法比較安全,我們的例子使用了安全模式罐旗。

組合模式的優(yōu)缺點(diǎn):

  • 只要是樹(shù)形結(jié)構(gòu)膳汪,就要考慮使用組合模式,只要是要體現(xiàn)局部和整體的關(guān)系的時(shí)候九秀,而且這種關(guān)系還可能比較深遗嗽,就可以考慮使用組合模式吧

  • 組合模式有一個(gè)非常明顯的缺點(diǎn),在Client類中的的定義樹(shù)葉和樹(shù)枝使用時(shí)使用了如下代碼:

    Branch developDep = new Branch("劉大瘸子","研發(fā)部門(mén)經(jīng)理",10000);
    Leaf g = new Leaf("g","開(kāi)發(fā)人員",2000);
    

    直接使用了實(shí)現(xiàn)類去創(chuàng)建對(duì)象鼓蜒,這個(gè)在面向接口編程上是很不恰當(dāng)?shù)摹?/p>

我們?cè)谏厦嬉策€提到了一個(gè)問(wèn)題痹换,就是樹(shù)的遍歷問(wèn)題,從上到下遍歷沒(méi)有問(wèn)題都弹,但是我要是從下往上遍歷呢娇豫?比如在人力資源這顆樹(shù)上,我從中抽取一個(gè)用戶畅厢,要找到它的上級(jí)有哪些冯痢,下級(jí)有哪些,怎么處理?先看類圖:

看類圖中的紅色方框浦楣,只要增加兩個(gè)方法就可以了袖肥,一個(gè)是設(shè)置父節(jié)點(diǎn)是誰(shuí),一個(gè)是查找父節(jié)點(diǎn)是誰(shuí)振劳,我們來(lái)看一下程序的改變:

/**
 * 公司人員抽象類
 */
public abstract class Corp {

    // 姓名
    private String name = "";
    // 職位
    private String position = "";
    // 薪水
    private int salary = 0;
    // 父節(jié)點(diǎn)
    private Corp parent = null;

    public Corp(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    public String getInfo() {
        return String.format("名稱: %s\t職位: %s\t薪水: %s", this.name, this.position, this.salary);
    }

    /**
     * 增加了以下兩個(gè)方法
     */
    // 設(shè)置父節(jié)點(diǎn)
    protected void setParent(Corp parent) {
        this.parent = parent;
    }

    // 得到父節(jié)點(diǎn)
    public Corp getParent() {
        return this.parent;
    }

}

/**
 * 節(jié)點(diǎn)類
 */
public class Branch extends Corp {

    // 領(lǐng)導(dǎo)下邊有那些下級(jí)領(lǐng)導(dǎo)和小兵
    private ArrayList<Corp> subordinateList = new ArrayList<>();

    public Branch(String name, String position, int salary) {
        super(name, position, salary);
    }

    // 增加一個(gè)下屬椎组,可能是小頭目,也可能是個(gè)小兵
    public void addSubordinate(Corp corp) {
        // 重要的是這行历恐,添加下屬的時(shí)候給下屬設(shè)置父節(jié)點(diǎn)為自己
        corp.setParent(this);
        this.subordinateList.add(corp);
    }

    // 我有哪些下屬
    public ArrayList<Corp> getSubordinateInfo() {
        return this.subordinateList;
    }

}

每個(gè)節(jié)點(diǎn)無(wú)論是樹(shù)枝節(jié)點(diǎn)還是樹(shù)葉節(jié)點(diǎn)寸癌,都增加了一個(gè)屬性:父節(jié)點(diǎn)對(duì)象,這樣在樹(shù)枝節(jié)點(diǎn)增加子節(jié)點(diǎn)或葉子的時(shí)候設(shè)置父節(jié)點(diǎn)弱贼,然后整棵樹(shù)除了根節(jié)點(diǎn)外每個(gè)
節(jié)點(diǎn)都一個(gè)父節(jié)點(diǎn)蒸苇,這樣每個(gè)節(jié)點(diǎn)上都有父節(jié)點(diǎn)了,有了這個(gè)parent 屬性吮旅,后序遍歷(從下往上找)填渠、中序遍歷(從中間某個(gè)環(huán)節(jié)往上或往下遍歷)都解決了,這個(gè)就不多說(shuō)了鸟辅。再提一個(gè)擴(kuò)展問(wèn)題氛什,樹(shù)葉節(jié)點(diǎn)和樹(shù)枝節(jié)點(diǎn)是有順序的,比如我們上面的例子匪凉,研發(fā)一組下邊有三個(gè)成員枪眉,這三個(gè)成員是要進(jìn)行排序的,這種情況怎么處理再层?

本文原書(shū):

《您的設(shè)計(jì)模式》 作者:CBF4LIFE

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末贸铜,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子聂受,更是在濱河造成了極大的恐慌蒿秦,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛋济,死亡現(xiàn)場(chǎng)離奇詭異棍鳖,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)碗旅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)渡处,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人祟辟,你說(shuō)我怎么就攤上這事医瘫。” “怎么了旧困?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵醇份,是天一觀的道長(zhǎng)稼锅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)僚纷,這世上最難降的妖魔是什么缰贝? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮畔濒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锣咒。我一直安慰自己侵状,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布毅整。 她就那樣靜靜地躺著趣兄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悼嫉。 梳的紋絲不亂的頭發(fā)上艇潭,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音戏蔑,去河邊找鬼蹋凝。 笑死,一個(gè)胖子當(dāng)著我的面吹牛总棵,可吹牛的內(nèi)容都是我干的鳍寂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼情龄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼迄汛!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起骤视,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鞍爱,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后专酗,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體睹逃,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年祷肯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了唯卖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡躬柬,死狀恐怖拜轨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情允青,我是刑警寧澤橄碾,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布卵沉,位于F島的核電站,受9級(jí)特大地震影響法牲,放射性物質(zhì)發(fā)生泄漏史汗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一拒垃、第九天 我趴在偏房一處隱蔽的房頂上張望停撞。 院中可真熱鬧,春花似錦悼瓮、人聲如沸戈毒。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)埋市。三九已至,卻和暖如春命贴,著一層夾襖步出監(jiān)牢的瞬間道宅,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工胸蛛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留污茵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓葬项,卻偏偏與公主長(zhǎng)得像省咨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子玷室,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 組合模式 在DebugMybatis的源碼時(shí)零蓉,在DynamicSqlSource.getBoundSql動(dòng)態(tài)獲取s...
    不學(xué)無(wú)數(shù)的程序員閱讀 1,050評(píng)論 0 6
  • 大家在上學(xué)的時(shí)候應(yīng)該都學(xué)過(guò)“數(shù)據(jù)結(jié)構(gòu)”這門(mén)課程吧,還記得其中有一節(jié)叫“二叉樹(shù)”吧穷缤,我們上學(xué)那會(huì)兒這一章節(jié)是必考內(nèi)容...
    遼A丶孫悟空閱讀 241評(píng)論 0 8
  • 公司的人事管理就是一個(gè)典型的樹(shù)狀結(jié)構(gòu)敌蜂,老大,往下一層一層的管理津肛,最后到我們這層小兵章喉,很典型的樹(shù)狀結(jié)構(gòu) 今天的任務(wù)就...
    涼快先生閱讀 246評(píng)論 0 1
  • 概述 UML類圖 代碼栗子 總結(jié) 概述概念 組合模式是指將對(duì)象組合成樹(shù)形結(jié)構(gòu)以表示“部分-整體”的層次結(jié)構(gòu),組合模...
    tanoak閱讀 376評(píng)論 0 0
  • 漸變的面目拼圖要我怎么拼摊唇? 我是疲乏了還是投降了? 不是不允許自己墜落涯鲁, 我沒(méi)有滴水不進(jìn)的保護(hù)膜巷查。 就是害怕變得面...
    悶熱當(dāng)乘涼閱讀 4,244評(píng)論 0 13