15.組合模式(Composite Pattern)
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.組合模式的注意事項
只要是樹形結構就要考慮使用組合模式,只要是要體現區域性和整體的關係的時候,而且這種關係還比較深,考慮一下組合模式吧。