首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 組合模式深度解析:樹形結構統一介面與遞迴操作藝術

     編輯:關於C#

前言

當需要表示「部分-整體」層次結構(如:檔案系統目錄/檔案、組織架構部門/員工、UI 容器/控制項、圖形編輯器群組/圖元)時,客戶端往往需要統一處理單個物件與組合物件。組合模式透過定義共同介面,讓葉子節點與容器節點均實作相同操作,客戶端無需區分單個與整體,即可遞迴遍歷整棵樹。本文以訊息實體樹結構為例,展示 MessageModelLeaf(葉子)、MessageModelComposite(樹枝)如何共享 MessageModelComponent 介面實現統一新增、刪除、顯示操作。

模式結構與四大角色

  • Component(抽象構件):MessageModelComponent,定義 Add()、Remove()、GetData(indent) 共同介面,含 _name、_messageModel 共同屬性。
  • Leaf(葉子構件):MessageModelLeaf,表示樹葉節點,Add/Remove 拋出異常(不支援),GetData 顯示自身資料。
  • Composite(容器構件):MessageModelComposite,表示樹枝節點,內含 List<MessageModelComponent> 子節點集合,實作 Add/Remove 管理子節點,GetData 遞迴遍歷子樹。
  • Client(客戶端):建立樹結構,透過根節點統一操作。
組合模式類別圖

核心程式碼機制深度解析

1. 抽象構件:統一介面與共同屬性

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 定義在抽象層,使葉子與容器擁有統一介面(代價:葉子需拋出異常或空實作)。

2. 葉子構件:終端節點實作

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 />";
    }
}

3. 容器構件:遞迴結構核心

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() 遞迴處理子節點,無需知道子節點是葉子還是容器(多型)。

4. 客戶端:建構樹與統一操作


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

縮進清晰呈現樹層級,驗證遞迴遍歷與統一介面正確運作。

組合模式兩大設計變體

1. 透明式:介面定義在 Component(本範例)

  • 優點:客戶端完全統一對待葉子與容器,無需類型檢查。
  • 缺點:葉子需實作無意義的 Add/Remove(拋異常/空實作),違反介面隔離原則。

2. 安全式:介面僅定義在 Composite

  • Component 只定義共同操作(GetData),Add/Remove 只在 Composite 定義。
  • 優點:葉子介面乾淨,型別安全(編譯期防止對葉子呼叫 Add)。
  • 缺點:客戶端需型別檢查 if (c is Composite) 才能呼叫 Add,失去完全透明性。

現代傾向:安全式 + 介面隔離,IComponent 定義共同操作,IContainer : IComponent 擴展 Add/Remove。

進階應用與最佳實務

雙向導航:父節點參考

在 Add() 中設定 child.Parent = this,支援:

  • 從葉子向上查找根路徑(檔案系統完整路徑)。
  • 移除時自動從父節點集合移除(this.Parent.Remove(this))。
  • 事件冒泡:子節點變更通知父節點(UI 佈局失效、資料驗證)。

享元模式結合:共享無狀態葉子

若大量葉子共享相同內部狀態(如圖形編輯器相同圖元),葉子實作為享元,外部狀態(位置、大小)由容器管理。

迭代器模式結合:統一遍歷介面

實作 IEnumerable<Component>,提供 GetEnumerator() 遞迴 yield return 所有後代,支援 foreach、LINQ 查詢(如:tree.OfType().Where(l => l.Name.Contains("A")))。

訪問者模式結合:複雜樹操作分離

定義 Accept(IVisitor),訪問者實作 VisitLeaf、VisitComposite,支援:序列化、匯出、計算、驗證等操作不污染節點類別。

.NET 實際應用案例

  • WPF/WinForms/UI Framework:Panel (Composite) 包含 UIElement (Component),統一 Render、Measure、Arrange。
  • System.Xml.Linq:XContainer (Composite) 包含 XNode (Component),統一 Descendants()、Add()。
  • ASP.NET Control Tree:Control (Component),ContainerControl (Composite)。
  • Entity Framework Metadata:EntityType、ComplexType 共用 StructuralType 基類。

優缺點總結

優點

  • 統一介面:客戶端無需區分葉子/容器,程式碼簡潔。
  • 遞迴天然:樹操作(遍歷、搜尋、序列化)自然映射為遞迴演算法。
  • 開閉原則:新增節點類型(新葉子/新容器)無需修改客戶端代碼。
  • 靈活構建:動態增刪節點、移動子樹、重組結構。

缺點

  • 設計過度通用:為了統一介面,可能強行定義不適合葉子的操作(透明式缺點)。
  • 型別安全降低:客戶端可能誤對葉子呼叫容器操作(安全式緩解)。
  • 深度遞迴風險:極深樹導致堆疊溢位(改用顯式堆疊迭代)。
  • 功能擴展受限:新增操作需修改 Component 介面(訪問者模式緩解)。

總結

組合模式以「統一介面 + 遞迴結構」優雅解決「部分-整體」層次表示難題。本範例以訊息樹(樹枝/樹葉)為例,完整展示了抽象構件定義共同協定、葉子實作終端行為、容器管理子集合與遞迴委派、客戶端透明構建操作的全流程。實務中,從 UI 控制項樹、XML DOM、檔案系統到組織架構、BOM 物料清單、AST 語法樹,組合模式是樹形資料處理的基石。掌握其核心——「Component 定義共同語言、Leaf 實作原子行為、Composite 遞迴委派、Client 透明使用」,並理解透明式/安全式權衡、雙向導航、迭代器/訪問者結合等進階技巧,能讓你在面對任意樹形領域模型時,構建出清晰、可擴展、易操作的領域層架構。


參考資源:DoFactory Composite Pattern

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved