前言
23種設計模式組合模式也屬于一種比較常用的模式,類似層級菜單,公司組織架構,資料結構中的樹形結構(root,Branch,Leaf)等等。
Uml通用類圖
源碼分析
大家在上學的時候應該都學過“資料結構”這門課程吧,還記得其中有一節叫“二叉樹”吧,我們上
學那會兒這一章節是必考内容,左子樹,右子樹,什麼先序周遊後序周遊什麼,重點就是二叉樹的的周遊,
我還記得當時老師就說,考試的時候一定有二叉樹的建構和周遊,現在想起來還是覺的老師是正确的,樹
狀結果在實際項目應用的非常廣泛。
咱就先說個最常見的例子,公司的人事管理就是一個典型的樹狀結構,你想想你公司的結構是不是這樣。
最 高 的老大,往下一層一層的管理,最後到我們這層小兵,很典型的樹狀結構(說明一下,這不是二叉樹,有關二叉樹的定義可以翻翻以前的教科書),我們今天的任務就是要把這個樹狀結構實作出來,并且還要把它遍
曆一遍,你要确認你建立的樹是否有問題呀。
從這個樹狀結構上分析,有兩種節點:有分支的節點(如研發部經理)和無分支的節點(如員工 A、員
工 D 等),我們增加一點學術術語上去,總經理叫做根節點(是不是想到 XML 中的那個根節點 root,那就對
了),類似研發部經理有分支的節點叫做樹枝節點,類似員工 A 的無分支的節點叫做樹葉節點,都很形象,三個類型的的節點,那是不是定義三個類就可以?好,我們按照這個思路走下去,先看我們自己設計的類
圖:
這個類圖是初學者最容易想到的類圖(如果你已經看明白這個類圖的缺陷了,就可以不看下邊的實作
了,我是循序漸進的講課,呵呵),我那來看這個實作:
先看最進階别的根節點的實作
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 合并成一個接口,這
個我們先肯定下來,這是個比較大的改動,我們先畫個類圖:
這個類圖還是有點問題的,接口的作用是什麼?定義共性,那 ILeaf 和 IBranch 是不是也有共性呢?
有 getInfo(),我們是不是要把這個共性也已經封裝起來呢?好,我們再修改一下類圖:
類圖上有兩個接口,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 資訊,是否可以抽象,好,我們抽象一下:
你一看這個圖,樂了,能不樂嘛,減少很多工作量了,接口沒有了,改成抽象類了,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;
}