一、概述
我們常常可以看到這樣一種形式,比如說電腦中的磁盤管理,我的電腦中有C槽、D盤、E盤,在C槽中又有A檔案夾、B檔案夾等,在A檔案夾下又有A1檔案夾、A2檔案夾、A3檔案等等;再比如說在桌子上有背包、電腦、水杯、書架、筆筒等,在背包裡有書、本子、IPad等,在書架上有書A、書B等,在筆筒裡有中性筆、鋼筆、鉛筆、彩筆等;再比如北京公司總部有财務部、人力部、上海分公司、深圳分公司等,在上海分公司又有财務部、人力部、南京辦事處、杭州辦事處等,在南京辦事處下面又有财務部、人力部等。我這麼說,可能看不出什麼,下面用圖表示一下這3個例子。
圖1 我的電腦結構示意圖
圖2 桌面結構示意圖
圖3 公司結構示意圖
這樣一看他們結構是不是一目了然,其實就是樹形結構,有根、有枝幹、有葉子。如果這個時候boss抛來一個需求,讓所有的枝幹具有一定的功能A,所有的葉子具有一定的功能B,或者,更具體點,從圖3來說,要求所有公司的财務部具有相同的功能A,人力部具有功能B,這個時候我們要怎麼實作呢。
當當當當~ 組合模式應運而生!
二、組合模式
1. 定義
組合模式,将對象組合成樹形結構以表示“部分-整體”的層次結構。組合模式使得使用者對單個對象群組合對象的使用具有一緻性。(引自《大話設計模式》)
2. 基本結構圖
Component:抽象類或接口,為組合中的對象聲明接口。通過add和remove方法提供增加和删除樹枝、樹葉的功能。
Leaf:葉子,為組合中最末端的節點,沒有子節點。
Composite:枝節點,上有父節點,下有子節點。定義了枝節點的行為,用來存儲子部件。在Component接口中實作與子部件有關的操作——增加add和删除remove。
3. 基本代碼
Component抽象類
public abstract class Component {
protected String name;
public Component(String name) {
this.name = name;
}
public abstract void add(Component c);
public abstract void remove(Component c);
public abstract void display(int depth);
}
Leaf葉子類,由于葉子沒有子節點,無需再增葉子和樹枝,是以這裡的add和remove方法就沒有實作。但卻保留了這兩個方法,是為了與樹枝節點Composite類保持一緻的接口,以消除葉節點和枝節點在抽象層次的差別。
public class Leaf extends Component{
public Leaf(String name) {
super(name);
}
@Override
public void add(Component c) {
}
@Override
public void remove(Component c) {
}
@Override
public void display(int depth) {
for(int i=0;i<depth;i++) {
System.out.print("-");
}
System.out.println(name);
}
}
Composite枝節點類
public class Composite extends Component{
private List<Component> children = new ArrayList<Component>();
public Composite(String name) {
super(name);
}
@Override
public void add(Component c) {
children.add(c);
}
@Override
public void remove(Component c) {
if(children.contains(c)) {
children.remove(c);
}
}
@Override
public void display(int depth) {
for(int i=0;i<depth;i++) {
System.out.print("-");
}
System.out.println(name);
for (Component component : children) {
component.display(depth+2);
}
}
}
測試類
public class CompositeTest {
public static void main(String[] args) {
//建立根節點
Composite root = new Composite("Root");
//添加兩個葉節點
root.add(new Leaf("Leaf A"));
root.add(new Leaf("Leaf B"));
//建立枝節點
Composite c1 = new Composite("Composite X");
//給枝節點X添加兩個葉節點
c1.add(new Leaf("Leaf XA"));
c1.add(new Leaf("Leaf XB"));
//将枝節點X添加到根節點上
root.add(c1);
//建立枝節點
Composite c2 = new Composite("Composite XY");
//給枝節點XY添加兩個葉節點
c2.add(new Leaf("Leaf XYA"));
c2.add(new Leaf("Leaf XYB"));
//将枝節點XY添加到枝節點X上
c1.add(c2);
//給根節點添加葉節點C
root.add(new Leaf("Leaf C"));
//建立葉節點D
Leaf d = new Leaf("Leaf D");
//将葉節點D添加到根節點上
root.add(d);
//将葉節點D從根節點移除
root.remove(d);
//顯示樹狀結構結果
root.display(1);
}
}
結果:
用一張圖來表示這個結果的話,如下
進一步說明,這個例子裡的display方法隻是為了輸出結果顯示出比較明确的層次結構,實際應用中可以根據需求做相應調整。
三、透明方式與安全方式
1. 透明方式
上面例子裡,葉節點Leaf類裡面的add和remove方法其實是無意義的方法,沒有實作内容,這種方式叫做透明方式。
即,在Component接口中聲明所有用來管理子對象的方法,包括add和remove等,這樣實作Component接口的所有子類都具備了add和remove方法。
這樣做的優點,上面也提到過了,就是讓枝節點和葉節點對外界來說沒有差別,具備完全一緻的行為接口。
相應的缺點就是,葉節點實作了完全沒有任何意義的add和remove方法。
2. 安全方式
與透明方式相對應,如果葉節點Leaf類中不用add和remove方法,這種方式就叫做安全方式。
即,在Component接口中不聲明add和remove方法,而是在子部件Composite中聲明所有用來管理子類對象的方法。
這樣做的優點就是上面提到的透明方式的缺點,這裡不會在Leaf類中出現無意義的方法。
相應的缺點也很明顯,就是在用戶端調用時需要做判斷進行區分,稍稍麻煩一些。
四、實戰應用
這裡就用上面的公司結構來舉例說明好了
抽象公司類Company,就是上面的Component
public abstract class Company {
protected String name;
public Company(String name) {
this.name = name;
}
public abstract void add(Company company);
public abstract void remove(Company company);
public abstract void display(int depth);
public abstract void duty();
}
具體公司實作類ConcreteCompany,就是上面的Composite
public class ConcreteCompany extends Company{
//聲明公司集合
private List<Company> companies = new ArrayList<Company>();
public ConcreteCompany(String name) {
super(name);
}
/**
* 添加子公司
*/
@Override
public void add(Company company) {
companies.add(company);
}
/**
* 删除子公司
*/
@Override
public void remove(Company company) {
if (companies.contains(company)) {
companies.remove(company);
}
}
/**
* 顯示
*/
@Override
public void display(int depth) {
for(int i=0;i<depth;i++) {
System.out.print("-");
}
System.out.println(name);
for (Company company : companies) {
company.display(depth+2);
}
}
/**
* 公司職責
*/
@Override
public void duty() {
for (Company company : companies) {
company.duty();
}
}
}
人力部HRCompany,就是上面的葉子類Leaf
/**
* 人力資源部
*/
public class HRDepartment extends Company{
public HRDepartment(String name) {
super(name);
}
@Override
public void add(Company company) {
}
@Override
public void remove(Company company) {
}
@Override
public void display(int depth) {
for(int i=0;i<depth;i++) {
System.out.print("-");
}
System.out.println(name);
}
@Override
public void duty() {
System.out.println(name+"職責:招聘人才");
}
}
财務部FinanceCompany,就是上面的Leaf。這個例子中有兩個Leaf——人力部和财務部,它們都沒有子節點,符合葉子特征,但是由于這裡人力部和财務部具有不同的公司職責,是以分開兩個實作。
/**
* 财務部
*/
public class FinanceDepartment extends Company{
public FinanceDepartment(String name) {
super(name);
}
@Override
public void add(Company company) {
}
@Override
public void remove(Company company) {
}
@Override
public void display(int depth) {
for(int i=0;i<depth;i++) {
System.out.print("-");
}
System.out.println(name);
}
@Override
public void duty() {
System.out.println(name+"職責:公司财務管理");
}
}
測試類
public class CompositeTest {
public static void main(String[] args) {
ConcreteCompany root = new ConcreteCompany("北京總公司");
root.add(new HRDepartment("總公司人力部"));
root.add(new FinanceDepartment("總公司财務部"));
ConcreteCompany c1 = new ConcreteCompany("上海分公司");
c1.add(new HRDepartment("上海分公司人力部"));
c1.add(new FinanceDepartment("上海分公司财務部"));
root.add(c1);
ConcreteCompany c2 = new ConcreteCompany("南京辦事處");
c2.add(new HRDepartment("南京辦事處人力部"));
c2.add(new FinanceDepartment("南京辦事處财務部"));
c1.add(c2);
ConcreteCompany c3 = new ConcreteCompany("杭州辦事處");
c3.add(new HRDepartment("杭州辦事處人力部"));
c3.add(new FinanceDepartment("杭州辦事處财務部"));
c1.add(c3);
System.out.println("各公司名稱:");
root.display(1);
System.out.println("\n各部門職責:");
root.duty();
}
}
結果
五、總結
使用場合:
(1)如果各對象展現出“部分-整體”層次結構(或樹形結構)時,可以考慮使用組合模式。
(2)當客戶希望忽略單個對象與組合對象的差別,可以使用統一接口調用時,可以考慮使用組合模式。
組合模式,簡單易用,核心其實就是一個遞歸群組合,展開來說就是一個很複雜的對象由許多基本對象構成,其中基本對象又可以構成稍複雜一些的組合對象,而組合對象又可以被重組成更複雜的組合對象,如此遞歸下去就構成了最終的對象。
寫在最後,
本文主要是小貓看了《大話設計模式》的一些記錄筆記,再加之自己的一些了解整理出此文,友善以後查閱,僅供參考。