csharp:gof23种设计模式:组合模式

C# 设计模式:组合模式 (Composite Pattern)

组合模式(Composite Pattern)是一种结构型设计模式。它允许你将对象组合成树形结构来表现“整体/部分”的层次结构。组合能让客户端以一致的方式处理个别对象(叶子)和对象组合(容器)。

在 C# 中实现组合模式,主要涉及三个角色:

  • Component (抽象构件): 这是一个接口或抽象类,为组合中的对象声明接口。它实现了所有类共有接口的默认行为。
  • Leaf (叶子节点): 在组合中表示叶子节点对象,叶子节点没有子节点。它实现了 Component 中的业务方法。
  • Composite (容器节点): 定义有子部件的那些部件的行为。它存储子部件,并实现在 Component 接口中定义的与子部件有关的操作(如 Add, Remove)。

我们以“文件系统”为例:文件夹(Composite)可以包含文件(Leaf),也可以包含其他文件夹。

这是所有对象的基类。为了保证“透明性”,我们通常在这里定义管理子节点的方法(Add/Remove),尽管叶子节点可能不需要它们。

using System;
using System.Collections.Generic;
 
namespace CompositePatternDemo
{
    // 抽象构件
    public abstract class FileSystemItem
    {
        protected string _name;
 
        public FileSystemItem(string name)
        {
            _name = name;
        }
 
        // 核心业务方法:所有节点都有这个功能
        public abstract void Display(int depth);
 
        // 管理子节点的方法(虚方法,默认抛出异常,由容器节点重写)
        // 这种方式称为“透明方式”,客户端不需要区分是叶子还是容器
        public virtual void Add(FileSystemItem item)
        {
            throw new NotImplementedException("叶子节点不支持添加子项");
        }
 
        public virtual void Remove(FileSystemItem item)
        {
            throw new NotImplementedException("叶子节点不支持移除子项");
        }
    }
}

叶子节点代表具体的实体(如文件)。它不包含子节点列表。

namespace CompositePatternDemo
{
    // 叶子节点:文件
    public class File : FileSystemItem
    {
        public File(string name) : base(name) { }
 
        public override void Display(int depth)
        {
            // 通过连字符显示层级深度
            Console.WriteLine(new String('-', depth) + " File: " + _name);
        }
 
        // File 不需要重写 Add 和 Remove,因为继承的默认行为是抛出异常,符合逻辑
    }
}

容器节点包含一个子节点列表,并实现了管理子节点的方法。关键点在于它的业务方法通常会递归调用子节点的业务方法。

namespace CompositePatternDemo
{
    // 容器节点:文件夹
    public class Directory : FileSystemItem
    {
        // 内部维护一个子组件列表
        private List<FileSystemItem> _children = new List<FileSystemItem>();
 
        public Directory(string name) : base(name) { }
 
        // 重写 Add 方法
        public override void Add(FileSystemItem item)
        {
            _children.Add(item);
        }
 
        // 重写 Remove 方法
        public override void Remove(FileSystemItem item)
        {
            _children.Remove(item);
        }
 
        // 核心:递归调用
        public override void Display(int depth)
        {
            // 1. 显示自己
            Console.WriteLine(new String('-', depth) + " Directory: " + _name);
 
            // 2. 遍历并显示所有子节点(递归)
            foreach (var component in _children)
            {
                component.Display(depth + 2);
            }
        }
    }
}

客户端代码不需要关心它处理的是简单的 `File` 还是复杂的 `Directory`,它只需要针对 `FileSystemItem` 编程。

class Program
{
    static void Main(string[] args)
    {
        // 1. 创建根节点
        Directory root = new Directory("Root");
 
        // 2. 创建叶子节点
        File file1 = new File("Config.xml");
        File file2 = new File("Logo.png");
 
        // 3. 创建子容器
        Directory subDir = new Directory("Bin");
        File subFile = new File("App.exe");
 
        // 4. 组装树形结构
        root.Add(file1);
        root.Add(file2);
 
        subDir.Add(subFile); // 向子文件夹添加文件
        root.Add(subDir);    // 将子文件夹添加到根目录
 
        // 5. 操作整体
        // 客户端只需要调用根节点的 Display,整个树结构会自动递归处理
        Console.WriteLine("文件系统结构图:");
        root.Display(1);
    }
}

在 C# 实现中,关于 `Add` 和 `Remove` 方法放在哪里,有两种流派:

  1. 透明方式 (Transparency):
    1. 操作: 如上例所示,在抽象类 `Component` 中声明 `Add`/`Remove`。
    2. 优点: 客户端不需要区分是叶子还是容器,调用一致。
    3. 缺点: 叶子节点调用 `Add` 会在运行时报错(不够安全)。
  1. 安全方式 (Safety):
    1. 操作: 在 `Component` 中声明 `Add`/`Remove`,只在 `Composite` 类中声明。
    2. 优点: 编译期就能检查错误,叶子节点根本没有 `Add` 方法。
    3. 缺点: 客户端必须区分对待,如果想添加子节点,必须先将对象强制转换为 `Composite` 类型。
  • BIM 开发: 如“筏板(Raft)”包含“电梯井(Pit)”,“楼层”包含“墙/柱/梁”。
  • UI 控件树: WinForms 或 WPF 中,Panel 可以包含 Button,也可以包含其他 Panel。
  • 图形编辑器: 一个“分组”可以包含圆形、矩形,也可以包含另一个“分组”。

组合模式的核心在于利用多态递归。通过将容器和叶子抽象为同一类型,使得极其复杂的树形结构在客户端看来只是一个简单的对象。

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。

  • csharp/gof23种设计模式/组合模式.txt
  • 最后更改: 2026/01/09 12:33
  • 张叶安