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

C# 設計模式實戰:組合模式 構建樹狀結構的通用遞歸

     編輯:關於C語言

組合模式將物件組合成樹狀結構以表示「部分-整體」層次,讓客戶端以統一介面處理單一物件與組合物件。本文以「檔案系統(資料夾/檔案)」為經典場景,展示 C# 實作要點,並探討如何避免常見陷阱。

核心概念:統一介面下的遞歸結構

  • Component(抽象元件):定義共同介面 Add/Remove/Display
  • Leaf(葉子節點):檔案,無子節點,Add/Remove 拋異常或無視
  • Composite(合成節點):資料夾,內部維護子元件集合,遞歸實作 Display

完整代碼實作

1. 抽象基類


using System;
using System.Collections;

public abstract class FileSystemElement
{
    protected string _name;

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

    public abstract void Add(FileSystemElement element);
    public abstract void Remove(FileSystemElement element);
    public abstract void Display(int depth = 0);
}

2. 葉子節點:檔案


public class FileElement : FileSystemElement
{
    public FileElement(string name) : base(name) { }

    public override void Add(FileSystemElement element)
        => Console.WriteLine($"Cannot add to a file: {_name}");

    public override void Remove(FileSystemElement element)
        => Console.WriteLine($"Cannot remove from a file: {_name}");

    public override void Display(int depth = 0)
        => Console.WriteLine($"{new string(' ', depth * 2)}📄 {_name}");
}

3. 合成節點:資料夾


public class FolderElement : FileSystemElement
{
    private readonly ArrayList _children = new ArrayList();

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

    public override void Add(FileSystemElement element)
        => _children.Add(element);

    public override void Remove(FileSystemElement element)
        => _children.Remove(element);

    public override void Display(int depth = 0)
    {
        Console.WriteLine($"{new string(' ', depth * 2)}📁 {_name}");
        foreach (FileSystemElement child in _children)
            child.Display(depth + 1);
    }
}

4. 客戶端測試


class Program
{
    static void Main()
    {
        var root = new FolderElement("Z:/");
        var games = new FolderElement("Games/");

        games.Add(new FileElement("cstrike.exe"));
        games.Add(new FileElement("hl2.exe"));
        root.Add(games);

        var docs = new FolderElement("Documents/");
        docs.Add(new FileElement("resume.pdf"));
        root.Add(docs);

        root.Display();
        Console.ReadKey();
    }
}

輸出結果

📁 Z:/
  📁 Games/
    📄 cstrike.exe
    📄 hl2.exe
  📁 Documents/
    📄 resume.pdf

關鍵設計決策與陷阱規避

1. Add/Remove 放在 Component 還是 Composite?

  • 放在 Component(GoF 原版):客戶端統一介面,但 Leaf 需拋異常/無視,違反介面隔離原則
  • 僅放在 Composite:類型安全,但客戶端需轉型,失去透明性
  • 建議:現代 C# 採用介面隔離——IComposite 定義 Add/Remove,IFileSystemElement 僅定義 Display,Leaf 不實作 IComposite

2. 子元件集合選型

集合類型優點缺點
ArrayList / List<T>簡單、隨機存取頻繁插刪效率低
LinkedList<T>插刪 O(1)無隨機存取、記憶體開銷大
Dictionary<string, T>按名稱快速查找順序不穩定、鍵不可重複

一般場景 List<FileSystemElement> 足夠;需高效查找時改 Dictionary。

3. 雙向導航:父節點引用

若需「從檔案找資料夾路徑」,在 Add 時設定 element.Parent = this,但要小心循環引用與序列化問題。

4. 迭代器模式解耦遍歷邏輯


public interface ITreeIterator
{
    bool MoveNext();
    FileSystemElement Current { get; }
}

public class DepthFirstIterator : ITreeIterator { ... }
public class BreadthFirstIterator : ITreeIterator { ... }

將遍歷策略外部化,符合單一職責原則。

現代 C# 改良版:泛型 + 介面隔離 + 模式匹配


public interface IFileSystemNode
{
    string Name { get; }
    void Display(string indent = "");
}

public interface IContainer : IFileSystemNode
{
    void Add(IFileSystemNode node);
    void Remove(IFileSystemNode node);
    IReadOnlyList Children { get; }
}

public class FileNode : IFileSystemNode
{
    public string Name { get; }
    public FileNode(string name) => Name = name;
    public void Display(string indent = "") => Console.WriteLine($"{indent}📄 {Name}");
}

public class FolderNode : IContainer
{
    public string Name { get; }
    private readonly List _children = new();
    public FolderNode(string name) => Name = name;
    public void Add(IFileSystemNode node) => _children.Add(node);
    public void Remove(IFileSystemNode node) => _children.Remove(node);
    public IReadOnlyList Children => _children.AsReadOnly();
    public void Display(string indent = "")
    {
        Console.WriteLine($"{indent}📁 {Name}");
        foreach (var child in _children) child.Display(indent + "  ");
    }
}

適用場景檢核表

  • ✅ 部分-整體樹狀結構(檔案系統、組織架構、UI 控制項樹、AST 語法樹)
  • ✅ 客戶端需統一處理單一與組合物件
  • ✅ 結構動態變化(運行時增刪節點)
  • ⚠️ 深度極大(>1000)需注意堆疊溢出,改用迭代器非遞歸
  • ⚠️ 頻繁查找特定路徑,考慮加索引或改用 Trie

結語:組合模式以「遞歸結構 + 統一介面」優雅解決樹狀結構操作難題。現代 C# 配合介面隔離與泛型,能將 GoF 原版的「Leaf 拋異常」缺陷徹底修正。下次遇到樹狀結構,先問自己:這裡有部分-整體關係嗎?客戶端需要統一介面嗎? 答案若是肯定,組合模式就是首選。

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