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

C# 設計模式深度解析:組合模式 - 處理樹狀結構的統一介面藝術

     編輯:關於C語言

前言:部分-整體層次的統一處理難題

檔案系統中,資料夾包含檔案與子資料夾;UI 介面中,容器控件包含簡單控件與嵌套容器;組織架構中,部門包含員工與子部門。這些場景共同特徵:**樹狀結構**,且客戶端希望**統一對待葉子節點 (檔案/按鈕/員工) 與容器節點 (資料夾/面板/部門)**。組合模式透過定義共同介面,完美解決此問題。

一、 情境模擬:家族譜系的遞歸結構

族譜中,男性 (`Boy`) 可擁有後代 (包含男性/女性),女性 (`Girl`) 為葉子節點。所有族人共享 `Name`、`Intro()` 介面,但只有男性支援 `Add/Remove` 後代操作。這正是**透明式組合模式** (介面定義所有操作,葉子拋出異常) 的典型應用。

組合模式類圖 - 家族譜系

1. 抽象組件:定義統一介面 (含管理子節點方法)

public abstract class FamilyMember
{
    protected string _name;
    public string Name => _name;

    protected FamilyMember(string name) => _name = name;

    // 統一介面:葉子與容器皆有
    public abstract void Add(FamilyMember member);
    public abstract void Remove(FamilyMember member);
    public abstract void Intro(); // 遞歸展示
}

2. 容器節點:男性 - 可擁有後代

public class Boy : FamilyMember
{
    private readonly List _children = new List();

    public Boy(string name) : base(name) { }

    public override void Add(FamilyMember member) => _children.Add(member);
    public override void Remove(FamilyMember member) => _children.Remove(member);

    public override void Intro()
    {
        Console.WriteLine($"我是: {Name} (男性,可傳宗接代)");
        foreach (var child in _children)
        {
            child.Intro(); // 遞歸遍歷
        }
    }
}

3. 葉子節點:女性 - 無後代,操作拋異常

public class Girl : FamilyMember
{
    public Girl(string name) : base(name) { }

    public override void Add(FamilyMember member)
        => throw new NotSupportedException("女性族人無法新增後代至族譜。");

    public override void Remove(FamilyMember member)
        => throw new NotSupportedException("女性族人無法移除族人。");

    public override void Intro()
        => Console.WriteLine($"我是: {Name} (女性,記錄於族譜)");
}

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

static void Main()
{
    // 建構家族樹
    Boy grandfather = new Boy("爺爺");
    grandfather.Add(new Girl("奶奶"));           // 配偶視為成員
    grandfather.Add(new Girl("姑姑"));
    Boy father = new Boy("爸爸");
    grandfather.Add(father);

    father.Add(new Boy("我"));
    Boy me = (Boy)father.GetChild(0); // 假設有 GetChild 或直接引用
    me.Add(new Boy("兒子"));

    // 統一入口:只需呼叫根節點
    Console.WriteLine("=== 完整族譜 ===");
    grandfather.Intro(); // 遞歸輸出整棵樹

    Console.ReadKey();
}

二、 兩種組合模式變體對比

透明式 vs 安全式組合模式

維度透明式 (本例)安全式
Component 介面宣告 `Add/Remove/GetChild`僅宣告業務方法 (如 `Intro`)
Leaf 類別實作管理方法並拋 `NotSupportedException`無管理方法,編譯期保證不被調用
Composite 類別實作管理方法實作管理方法
客戶端調用完全統一,無需區分類型需轉型為 Composite 才能管理子節點
優點客戶端極簡單,多態性純粹類型安全,避免運行時異常
缺點葉子類別被迫實作無意義方法客戶端需 `if (c is Composite)` 判斷
推薦場景樹結構操作頻繁、客戶端不想關心類型葉子與容器行為差異大、嚴格類型安全優先

.NET `System.Windows.Forms.Control` 採用**透明式** (所有控件皆有 `Controls` 集合),葉子控件 `Label` 的 `Controls` 為空集合而非拋異常,是更優雅的透明式實現。

三、 .NET 框架核心應用:WinForms / WebForms 控件樹

  • Component:`System.Windows.Forms.Control` (抽象基類)
  • Leaf:`Button`、`TextBox`、`Label` 等無子控件控件
  • Composite:`Panel`、`GroupBox`、`Form`、`DataGridView` 等容器控件
  • 統一介面:`Controls` 集合 (`Control.ControlCollection`)、`OnPaint` 繪製遞歸、`Find` 查找遞歸

開發者只需 `parent.Controls.Add(child)`,無論 `child` 是 `Button` 還是 `Panel`,繪製、佈局、事件路由皆由框架遞歸處理,完美體現組合模式威力。

四、 模式優缺點與最佳實踐

優點

  • 客戶端代碼簡潔:統一介面處理單個物件與整個樹。
  • 解耦客戶端與複雜樹結構:新增節點類型 (如新增 `CustomContainer`) 無需修改客戶端。
  • 易於擴展:加入新 Leaf 或 Composite 只需實作介面。

缺點與注意事項

  • 設計過於泛化:為了統一介面,可能讓 Leaf 攜帶不需要的方法 (透明式) 或讓客戶端處理類型判斷 (安全式)。
  • 運行時類型安全風險:透明式下調用 Leaf 的 `Add` 會拋異常,需文檔規約或單元測試保障。
  • 遍歷效能:深層樹頻繁遍歷可考慮在父節點緩存扁平化列表。
  • 建議:客戶端盡量持有 `Component` 類型引用,利用多態調用 `Intro()` 等業務方法,避免直接 `new Leaf()` 後調用具體方法。

五、 適用場景檢核表

  • [ ] 需表示「部分-整體」層次結構 (樹狀、遞歸結構)。
  • [ ] 希望客戶端忽略「單個物件」與「組合物件」的差異,統一處理。
  • [ ] 結構中節點類型相對固定,但樹形結構動態變化 (增刪節點)。

六、 總結

組合模式以「一致介面」馴服「遞歸結構」,是處理樹狀資料結構的基石模式。從檔案系統、UI 控件樹、組織架構、XML/JSON DOM 解析、菜單系統到遊戲場景圖,無處不在。關鍵設計決策在於:**選擇透明式還是安全式**。現代框架傾向透明式 (如 .NET Control、Java Swing Component),配合空集合而非異常處理葉子節點,兼顧多態純粹與運行時安全。掌握「抽象共同行為、組合遞歸委派、客戶端面向介面編程」三要素,即可從容應對複雜層次結構。

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