當需要表示「部分-整體」層次結構(如:檔案系統目錄/檔案、組織架構部門/員工、UI 容器/控制項、圖形編輯器群組/圖元)時,客戶端往往需要統一處理單個物件與組合物件。組合模式透過定義共同介面,讓葉子節點與容器節點均實作相同操作,客戶端無需區分單個與整體,即可遞迴遍歷整棵樹。本文以訊息實體樹結構為例,展示 MessageModelLeaf(葉子)、MessageModelComposite(樹枝)如何共享 MessageModelComponent 介面實現統一新增、刪除、顯示操作。
MessageModelComponent,定義 Add()、Remove()、GetData(indent) 共同介面,含 _name、_messageModel 共同屬性。MessageModelLeaf,表示樹葉節點,Add/Remove 拋出異常(不支援),GetData 顯示自身資料。MessageModelComposite,表示樹枝節點,內含 List<MessageModelComponent> 子節點集合,實作 Add/Remove 管理子節點,GetData 遞迴遍歷子樹。
public abstract class MessageModelComponent
{
protected MessageModel _messageModel;
protected string _name;
public MessageModelComponent(string name, MessageModel mm)
{
this._name = name;
this._messageModel = mm;
}
public MessageModelComponent(string name) { this._name = name; }
public abstract void Add(MessageModelComponent mmc);
public abstract void Remove(MessageModelComponent mmc);
public abstract string GetData(int indent);
}
關鍵:Add/Remove 定義在抽象層,使葉子與容器擁有統一介面(代價:葉子需拋出異常或空實作)。
public class MessageModelLeaf : MessageModelComponent
{
public MessageModelLeaf(string name, MessageModel mm) : base(name, mm) { }
public override void Add(MessageModelComponent mmc)
=> throw new Exception("不能添加");
public override void Remove(MessageModelComponent mmc)
=> throw new Exception("不能刪除");
public override string GetData(int indent)
{
return new String('—', indent) +
"樹葉名稱:" + _name +
";信息內容:" + _messageModel.Message +
"<br />";
}
}
public class MessageModelComposite : MessageModelComponent
{
private List<MessageModelComponent> _list = new List<MessageModelComponent>();
public MessageModelComposite(string name) : base(name) { }
public override void Add(MessageModelComponent mmc) => _list.Add(mmc);
public override void Remove(MessageModelComponent mmc) => _list.Remove(mmc);
public override string GetData(int indent)
{
string s = new String('—', indent) + "樹枝名稱:" + _name + "<br />";
foreach (var mmc in _list)
s += mmc.GetData(indent + 2); // 遞迴:統一介面調用
return s;
}
}
核心機制:GetData 透過統一介面 mmc.GetData() 遞迴處理子節點,無需知道子節點是葉子還是容器(多型)。
var root = new MessageModelComposite("樹枝A");
root.Add(new MessageModelLeaf("樹葉A", new MessageModel("Message內容A", Now)));
root.Add(new MessageModelLeaf("樹葉B", new MessageModel("Message內容B", Now)));
var comp = new MessageModelComposite("樹枝B");
comp.Add(new MessageModelLeaf("樹葉C", new MessageModel("Message內容C", Now)));
comp.Add(new MessageModelLeaf("樹葉D", new MessageModel("Message內容D", Now)));
root.Add(comp); // 將子樹加入根樹
root.Add(new MessageModelLeaf("樹葉E", new MessageModel("Message內容E", Now)));
var l = new MessageModelLeaf("樹葉F", new MessageModel("Message內容F", Now));
root.Add(l);
root.Remove(l); // 動態管理
Response.Write(root.GetData(1)); // 從根節點統一顯示
—樹枝名稱:樹枝A
———樹葉名稱:樹葉A;信息內容:Message內容A
———樹葉名稱:樹葉B;信息內容:Message內容B
———樹枝名稱:樹枝B
——————樹葉名稱:樹葉C;信息內容:Message內容C
——————樹葉名稱:樹葉D;信息內容:Message內容D
———樹葉名稱:樹葉E;信息內容:Message內容E
縮進清晰呈現樹層級,驗證遞迴遍歷與統一介面正確運作。
Add/Remove(拋異常/空實作),違反介面隔離原則。Component 只定義共同操作(GetData),Add/Remove 只在 Composite 定義。if (c is Composite) 才能呼叫 Add,失去完全透明性。現代傾向:安全式 + 介面隔離,IComponent 定義共同操作,IContainer : IComponent 擴展 Add/Remove。
在 Add() 中設定 child.Parent = this,支援:
this.Parent.Remove(this))。若大量葉子共享相同內部狀態(如圖形編輯器相同圖元),葉子實作為享元,外部狀態(位置、大小)由容器管理。
實作 IEnumerable<Component>,提供 GetEnumerator() 遞迴 yield return 所有後代,支援 foreach、LINQ 查詢(如:tree.OfType)。
定義 Accept(IVisitor),訪問者實作 VisitLeaf、VisitComposite,支援:序列化、匯出、計算、驗證等操作不污染節點類別。
Panel (Composite) 包含 UIElement (Component),統一 Render、Measure、Arrange。XContainer (Composite) 包含 XNode (Component),統一 Descendants()、Add()。Control (Component),ContainerControl (Composite)。EntityType、ComplexType 共用 StructuralType 基類。組合模式以「統一介面 + 遞迴結構」優雅解決「部分-整體」層次表示難題。本範例以訊息樹(樹枝/樹葉)為例,完整展示了抽象構件定義共同協定、葉子實作終端行為、容器管理子集合與遞迴委派、客戶端透明構建操作的全流程。實務中,從 UI 控制項樹、XML DOM、檔案系統到組織架構、BOM 物料清單、AST 語法樹,組合模式是樹形資料處理的基石。掌握其核心——「Component 定義共同語言、Leaf 實作原子行為、Composite 遞迴委派、Client 透明使用」,並理解透明式/安全式權衡、雙向導航、迭代器/訪問者結合等進階技巧,能讓你在面對任意樹形領域模型時,構建出清晰、可擴展、易操作的領域層架構。