C# Composite(组合)模式:从冗余代码到统一树形结构

认真发呆
认真发呆
发布时间:2026-10-15| 2人浏览过
原创

本文通过C#语言实例,解析如何在处理具有层次结构的对象集合时使用Composite模式。文章首先展示了原始设计中存在的冗余问题,随后演示了利用抽象类和接口将叶子节点与容器节点统一化的方法,并对比了透明型和安全型两种架构选择。旨在帮助开发者构建更易于扩展且逻辑清晰的树形数据结构。

问题的由来

在处理如文件系统、组织架构图等具有部分-整体层级关系的场景时,对象往往表现为树形结构。如果不借助设计模式,通常需要针对“普通节点”和“包含子节点的容器”编写不同的类和方法。随着层级的增加,这种写法会导致代码大量重复,且难以应对新增类型的挑战。

初步尝试:分别定义员工类型

为了理解需求,我们先设定一个简单的背景:建立一个包含老板、部门经理和基层员工的公司架构。在基础思路中,这两类人的行为存在差异——经理可以拥有下属,而基层员工不可以。

第1步:定义没有下属的基层员工

此类只需要显示自身的信息即可:

class BasicLevelEmployee
{
    public string ID { get; set; }

    public void ShowStatus(int indent)
    {
        // 根据缩进层级打印节点名称
        string str = ID;
        str = str.PadLeft(ID.Length + indent, '-');
        Console.WriteLine(str);
    }
}

第2步:定义拥有下属的经理类由于需要维护多层级关系,经理类不仅要显示自己,还要递归显示其所有直接下属。

class Manager
{
    public string ID { get; set; }
    
    // 分别维护不同类型的下属列表
    public List<BasicLevelEmployee> Subordinate = new List<BasicLevelEmployee>();
    public List<Manager> SubordinateManagers = new List<Manager>();

    public void ShowStatus(int indent)
    {
        string str = ID;
        str = str.PadLeft(ID.Length + indent, '-');
        Console.WriteLine(str);
        indent += 4;
        // 必须遍历两个不同的列表
        Subordinate.ForEach(s => s.ShowStatus(indent));
        SubordinateManagers.ForEach(m => m.ShowStatus(indent));
    }

    public void AddSubordinate(BasicLevelEmployee e) { Subordinate.Add(e); }
    public void AddSubordinate(Manager e) { SubordinateManagers.Add(e); }
    public void RemoveSubordinate(BasicLevelEmployee e) { Subordinate.Remove(e); }
    public void RemoveSubordinate(Manager e) { SubordinateManagers.Remove(e); }
}

第3步:组装公司架构并运行

在客户端调用中,我们需要一步步添加各个层级的对象:

class Program
{
    static void Main(string[] args)
    {
        var boss = new Manager() { ID = "BOSS" };
        var devManager = new Manager() { ID = "Dev Manager" };
        var financeManager = new Manager() { ID = "Finance Manager" };
        var devLead = new Manager() { ID = "Dev Lead" };
        var qcLead = new Manager() { ID = "QC Lead" };

        // 添加下属关系
        boss.AddSubordinate(devManager);
        boss.AddSubordinate(financeManager);
        financeManager.AddSubordinate(new BasicLevelEmployee() { ID = "Purchase" });
        devManager.AddSubordinate(devLead);
        devManager.AddSubordinate(qcLead);
        devLead.AddSubordinate(new BasicLevelEmployee() { ID = "Developer1" });
        devLead.AddSubordinate(new BasicLevelEmployee() { ID = "Developer2" });
        qcLead.AddSubordinate(new BasicLevelEmployee() { ID = "QuanityControl1" });
        qcLead.AddSubordinate(new BasicLevelEmployee() { ID = "QuanityControl2" });

        CompanyHierachy company = new CompanyHierachy() { CEO = boss };
        company.ShowStatus();
    }
}

虽然能够成功构建出如图所示的层级结构 ,但这段代码暴露出了严重的缺陷:

  • 缺乏抽象共性:基层员工和经理都有ID属性和ShowStatus方法,却被迫写成两个独立的类。
  • 扩展困难:如果未来增加“实习生”或“外包顾问”,Manager类必须再次修改,增加新的List和维护方法,这违背了开闭原则。

使用组合模式进行重构

组合模式(Composite Pattern)通过一种透明的方式处理树形结构,让单个对象(叶子节点)和组合对象(容器节点)具有一致的接口。此时,我们对结构有了直观的演变认知 。

核心重构方案

我们需要提取出一个共同的父类(或抽象类/接口),将所有操作方法的并集放入其中。这样,无论当前对象是经理还是员工,外部调用者都可以统一对其进行操作。

透明型与ili安全型的抉择:对于叶子节点(基层员工)不支持的方法(如添加下属),有两种处理哲学:

  • 透明型 (Transparent Approach):父类包含所有方法。叶子节点实现这些方法时抛出异常或忽略。优点是客户端无需知道具体类型,可直接对树进行操作 。
  • 安全型 (Safe Approach):父类只包含叶子节点和组合节点共有交集的方法。若需操作子节点,必须进行向下转型。优点是保证了对叶子节点绝对的安全性 。

实施透明型重构(推荐)

我们选用业界更为常用的透明型,将所有的行为都抽象到基类中:

第1步:创建抽象雇员基类

abstract class Employee
{
    public string ID { get; set; }
    public abstract void ShowStatus(int indent);
    public abstract void AddSubordinate(Employee e);
    public abstract void RemoveSubordinate(Employee e);
}

第2步:重写基层员工类(叶子节点)

由于继承了基类,必须实现所有抽象方法。对于它无法支持的Add和Remove方法,我们引入NotImplementedException以提供明确的调试信号。

class BasicLevelEmployee : Employee
{
    public override void ShowStatus(int indent)
    {
        string str = ID;
        str = str.PadLeft(ID.Length + indent, '-');
        Console.WriteLine(str);
    }

    public override void AddSubordinate(Employee e) { throw new NotImplementedException(); }
    public override void RemoveSubordinate(Employee e) { throw new NotImplementedException(); }
}

第3步:重写经理类(容器节点)

得益于基类的统一抽象,Manager现在只需维护一个类型为 List<Employee> 的集合。无论是新员工、老员工还是新加入的副经理,都能无缝接入同一个列表中,彻底消除了冗余的分支判断。

class Manager : Employee
{
    public override void ShowStatus(int indent)
    {
        string str = ID;
        str = str.PadLeft(ID.Length + indent, '-');
        Console.WriteLine(str);
        indent += 4;
        // 统一迭代所有类型的下属
        Subordinate.ForEach(s => s.ShowStatus(indent));
    }

    // 统一的下属管理列表
    public List<Employee> Subordinate = new List<Employee>();

    public override void AddSubordinate(Employee e) { Subordinate.Add(e); }
    public override void RemoveSubordinate(Employee e) { Subordinate.Remove(e); }
}

第4步:验证重构结果

客户端的构建逻辑保持不变,运行后得到的组织结构依然完整有效,但背后的代码已经实现了高度的解耦与统一化。

总结

通过上述重构,我们不仅简化了原有的臃肿代码,还赋予了系统更好的扩展能力。当需要向树结构中添加新类型的节点时,只要它们能正确实现 Employee 基类的契约,就不需要再去改动现有的任何类。这就是结构型设计模式中组合模式的强大之处。

以上就是C#中Composite(组合)模式的详细内容,更多关于C#设计模式技巧的资料请关注本站其它相关文章!
相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。