什么是組合模式?
組合模式允許你將對象組合成樹形結構來表現”部分-整體“的層次結構,使得客戶以一致的方式處理單個對象以及對象的組合。
組合模式實現的最關鍵的地方是——簡單對象和復合對象必須實現相同的接口。這就是組合模式能夠將組合對象和簡單對象進行一致處理的原因。
- 組合部件(Component):它是一個抽象角色,為要組合的對象提供統一的接口。
- 葉子(Leaf):在組合中表示子節點對象,葉子節點不能有子節點。
- 合成部件(Composite):定義有枝節點的行為,用來存儲部件,實現在Component接口中的有關操作,如增加(Add)和刪除(Remove)。
代碼實現
透明式的組合模式
在Component中聲明所有來管理子對象的方法,其中包括Add,Remove等。這樣實現Component接口的所有子類都具備了Add和Remove方法。這樣做的好處是葉節點和枝節點對於外界沒有區別,它們具備完全一致的接口。
/// <summary>
/// 一個抽象構件,聲明一個接口用於訪問和管理Component的子部件
/// </summary>
public abstract class Component
{
protected string name;
public Component(string name)
{
this.name = name;
}
/// <summary>
/// 增加一個節點
/// </summary>
/// <param name="component"></param>
public abstract void Add(Component component);
/// <summary>
/// 移除一個節點
/// </summary>
/// <param name="component"></param>
public abstract void Remove(Component component);
/// <summary>
/// 顯示層級結構
/// </summary>
public abstract void Display(int level);
}
/// <summary>
/// 葉子節點
/// </summary>
public class Leaf : Component
{
public Leaf(string name)
: base(name)
{ }
/// <summary>
/// 由於葉子節點沒有子節點,所以Add和Remove方法對它來說沒有意義,但它繼承自Component,這樣做可以消除葉節點和枝節點對象在抽象層次的區別,它們具備完全一致的接口。
/// </summary>
/// <param name="component"></param>
public override void Add(Component component)
{
Console.WriteLine("Can not add a component to a leaf.");
}
/// <summary>
/// 實現它沒有意義,只是提供了一個一致的調用接口
/// </summary>
/// <param name="component"></param>
public override void Remove(Component component)
{
Console.WriteLine("Can not remove a component to a leaf.");
}
public override void Display(int level)
{
Console.WriteLine(new string('-', level) + name);
}
}
/// <summary>
/// 定義有枝節點的行為,用來存儲部件,實現在Component接口中對子部件有關的操作
/// </summary>
public class Composite : Component
{
public Composite(string name)
: base(name)
{ }
/// <summary>
/// 一個子對象集合,用來存儲其下屬的枝節點和葉節點
/// </summary>
private List<Component> children = new List<Component>();
/// <summary>
/// 增加子節點
/// </summary>
/// <param name="component"></param>
public override void Add(Component component)
{
children.Add(component);
}
/// <summary>
/// 移除子節點
/// </summary>
/// <param name="component"></param>
public override void Remove(Component component)
{
children.Remove(component);
}
public override void Display(int level)
{
Console.WriteLine(new string('-', level) + name);
// 遍歷其子節點並顯示
foreach (Component component in children)
{
component.Display(level + 2);
}
}
}
/// <summary>
/// 調用
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
// 生成樹根,並為其增加兩個葉子節點
Component root = new Composite.Composite("Root");
root.Add(new Leaf("Leaf A in Root"));
root.Add(new Leaf("Leaf B in Root"));
// 為根增加兩個枝節點
Component branchX = new Composite.Composite("Branch X in Root");
Component branchY = new Composite.Composite("Branch Y in Root");
root.Add(branchX);
root.Add(branchY);
// 為BranchX增加頁節點
branchX.Add(new Leaf("Leaf A in Branch X"));
// 為BranchX增加枝節點
Component branchZ = new Composite.Composite("Branch Z in Branch X");
branchX.Add(branchZ);
// 為BranchY增加葉節點
branchY.Add(new Leaf("Leaf in Branch Y"));
// 為BranchZ增加葉節點
branchZ.Add(new Leaf("Leaf in Branch Z"));
// 顯示樹
root.Display(1);
}
弊端:客戶端對葉節點和枝節點是一致的,但葉節點並不具備Add和Remove的功能,因而對它們的實現是沒有意義的
安全式組合模式
在Component中不去聲明Add和Remove方法,那么子類的Leaf就不需要實現它,而是在Composit聲明所有用來管理子類對象的方法。
/// <summary>
/// 一個抽象構件,聲明一個接口用於訪問和管理Component的子部件
/// </summary>
public abstract class Component
{
protected string name;
public Component(string name)
{
this.name = name;
}
/// <summary>
/// 顯示層級結構
/// </summary>
public abstract void Display(int level);
}
/// <summary>
/// 葉子節點
/// </summary>
public class Leaf : Component
{
public Leaf(string name)
: base(name)
{ }
public override void Display(int level)
{
Console.WriteLine(new string('-', level) + name);
}
}
/// <summary>
/// 定義有枝節點的行為,用來存儲部件,實現在Component接口中對子部件有關的操作
/// </summary>
public class Composite : Component
{
public Composite(string name)
: base(name)
{ }
/// <summary>
/// 一個子對象集合,用來存儲其下屬的枝節點和葉節點
/// </summary>
private List<Component> children = new List<Component>();
/// <summary>
/// 增加子節點
/// </summary>
/// <param name="component"></param>
public void Add(Component component)
{
children.Add(component);
}
/// <summary>
/// 移除子節點
/// </summary>
/// <param name="component"></param>
public void Remove(Component component)
{
children.Remove(component);
}
public override void Display(int level)
{
Console.WriteLine(new string('-', level) + name);
// 遍歷其子節點並顯示
foreach (Component component in children)
{
component.Display(level + 2);
}
}
}
/// <summary>
/// 調用
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
// 生成樹根,並為其增加兩個葉子節點
Composite.Composite root = new Composite.Composite("Root");
root.Add(new Leaf("Leaf A in Root"));
root.Add(new Leaf("Leaf B in Root"));
// 為根增加兩個枝節點
Composite.Composite branchX = new Composite.Composite("Branch X in Root");
Composite.Composite branchY = new Composite.Composite("Branch Y in Root");
root.Add(branchX);
root.Add(branchY);
// 為BranchX增加頁節點
branchX.Add(new Leaf("Leaf A in Branch X"));
// 為BranchX增加枝節點
Composite.Composite branchZ = new Composite.Composite("Branch Z in Branch X");
branchX.Add(branchZ);
// 為BranchY增加葉節點
branchY.Add(new Leaf("Leaf in Branch Y"));
// 為BranchZ增加葉節點
branchZ.Add(new Leaf("Leaf in Branch Z"));
// 顯示樹
root.Display(1);
}
弊端:葉節點無需在實現Add與Remove這樣的方法,但是對於客戶端來說,必須對葉節點和枝節點進行判定,為客戶端的使用帶來不便。
組合模式的優缺點
優點:
- 組合模式使得客戶端代碼可以一致地處理對象和對象容器,無需關系處理的單個對象,還是組合的對象容器。
- 將”客戶代碼與復雜的對象容器結構“解耦。
- 可以更容易地往組合對象中加入新的構件。
缺點: 使得設計更加復雜。客戶端需要花更多時間理清類之間的層次關系。(這個是幾乎所有設計模式所面臨的問題)。
注意的問題:
- 有時候系統需要遍歷一個樹枝結構的子構件很多次,這時候可以考慮把遍歷子構件的結構存儲在父構件里面作為緩存。
- 客戶端盡量不要直接調用樹葉類中的方法(在我上面實現就是這樣的,創建的是一個樹枝的具體對象;),而是借用其父類(Graphics)的多態性完成調用,這樣可以增加代碼的復用性。
組合模式的使用場景
在以下情況下應該考慮使用組合模式:
- 當想表達對象的部分-整體的層次結構時。
- 希望用戶忽略組合對象與單個對象的不同,用戶將統一地使用組合結構中的所有對象時。
.NET 中Winform 中的空間類型大多用到了該種設計模式。另, 《設計模式》一書中提倡:相對於安全性,我們比較強調透明性。對於第一種方式中葉子節點內不需要的方法可以使用空處理或者異常報告的方式來解決。

