1.定義
将對象組合成樹形結構以表示“部分-整體”的層次結構,使得使用者對單個對象群組合對象的使用具有一緻性。
組合模式又叫做樹形模式。其在項目開發中涉及樹的結構都會想到組合模式。
2.引子
我們計算機的檔案系統就是一個典型組合模式的使用,檔案分為兩種:一、檔案夾,二、檔案。其中檔案夾可以包含檔案,也可以包含子檔案夾。如果我們用資料總管打開某個檔案夾,發現它展開在左側的是一個樹形結構。
而我們的組合模式就是為了解決這種樹形結構的遞歸問題。這種問題在生活中很常見,比如某個機關的人事關系圖。

3.組合模式的使用場景
- 維護和展示部分-整體關系的場景,比如樹形菜單、檔案和檔案夾管理。
- 從一個整體中能獨立出部分子產品或功能的場景。
下面以人事關系的代碼為例講解該模式的實作:
package _15CompositePattern;
/**
* 某公司的抽象員工類
* 無論上司或者普通職員都有的屬性
*/
public abstract class Corp {
private String name;// 姓名
private int salary;// 薪水
public Corp(String name, int salary)
{
this.name = name;
this.salary = salary;
}
public void prinInfo()
{
System.out.println("Name: " + name + ", Salary:" + salary);
}
}
package _15CompositePattern;
import java.util.ArrayList;
import java.util.List;
/**
* 上司類
* 出了擁有普通員工的權限,偶爾還要關懷一下下屬,要不他們不幹活
*/
public class Branch extends Corp {
// 手下的所有下屬
private List<Corp> subList = new ArrayList<Corp>();
public Branch(String name, int salary) {
super(name, salary);
}
// 增加一個下屬,這個下屬可能是小兵,也可能是上司(當然職位比我低)
public void addSub(Corp sub) {
subList.add(sub);
}
// 獲得我的所有下屬
public List<Corp> getSubList() {
return subList;
}
}
package _15CompositePattern;
/**
* 普通員工類
* 除了自己不需要關心任何别人的事情
*/
public class Leaf extends Corp {
public Leaf(String name, int salary) {
super(name, salary);
}
}
package _15CompositePattern;
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
Corp bossCorp = new Branch("張三", 100000);
// 設定了張三是一個boss,給他加小弟的代碼就不詳細寫了
// ...
printAllCorpInfo(bossCorp);
}
// 給我根節點,我列印出全部資訊
public static void printAllCorpInfo(Corp root)
{
if(root instanceof Leaf)
{
root.prinInfo();
}
else
{
for(Corp sub : ((Branch)root).getSubList())
{
printAllCorpInfo(sub);
}
}
}
}
4.組合模式的三個角色
- Component抽象構件角色:定義參加組合對象的共有方法和屬性,可以定義一些預設的行為或屬性,比如我們例子中的name屬性,prinInfo()方法就封裝在抽象類中
- Leaf葉子構件:葉子對象,其下再也沒有其他的分支,也就是周遊的最小機關
- Composite樹枝構件:樹枝對象,它的作用是組合樹枝節點和葉子節點形成一個樹狀結構
5.組合模式的類型
組合模式中必須提供對子對象的管理方法,不然無法完成對子對象的添加删除等等操作,也就失去了靈活性和擴充性。但是管理方法是在Component中就聲明還是在Composite中聲明呢?
上面例子中給出的代碼叫做安全模式,隻在Composite裡面聲明所有的用來管理子類對象的方法(如下圖所示)。這樣就避免了上一種方式的安全性問題,但是由于葉子和分支有不同的接口,是以又失去了透明性。
另一種方式是在Component裡面聲明所有的用來管理子類對象的方法,以達到Component接口的最大化(如下圖所示)。目的就是為了使客戶看來在接口層次上樹葉和分支沒有差別——透明性。但樹葉是不存在子類的,是以Component聲明的一些方法對于樹葉來說是不适用的。這樣也就帶來了一些安全性問題。
《設計模式》一書認為:在這一模式中,相對于安全性,我們比較強調透明性。對于第一種方式中葉子節點内不需要的方法可以使用空處理或者異常報告的方式來解決。
參考一下Java中File類型的實作,它同時包含了檔案和檔案夾的操作方法,可以看出是一種透明類型的組合模式。
6.組合模式的優點
- 高層子產品調用簡單:一棵樹形機構中的所有節點都是Component,局部和整體對調用者來說沒有任何差別,也就是說,高層子產品不必關心自己處理的是單個對象還是整個組合結構,簡化了高層子產品的代碼。
- 節點自由增加:想要增加一個節點,隻需要找到它的父節點就行了,非常友善。
7.組合模式的缺點
組合模式有一個非常明顯的缺點,看到我們場景類中,樹枝和樹葉直接使用了實作類。這在面向接口程式設計上是很不恰當的,與依賴倒置原則沖突。
8.組合模式的注意事項
隻要是樹形結構就要考慮使用組合模式,隻要是要展現局部和整體的關系的時候,而且這種關系還比較深,考慮一下組合模式吧。