组合模式(Composite Pattern)

本节介绍的是结构型模式中的组合模式

1. 模式概述

组合模式(Composite Pattern) 是一种结构型设计模式,用于将对象组合成树形结构 以表示"部分-整体"的层次结构。组合模式使得客户端对单个对象和组合对象的使用具有一致性,可以像处理单个对象一样处理对象集合。

2. 模式结构

主要角色:

  1. 组件(Component):定义组合中所有对象的通用接口

  2. 叶子(Leaf):表示组合中的叶子节点对象,没有子节点

  3. 容器(Composite):表示组合中的容器节点对象,可以包含子节点

  4. 客户端(Client):通过组件接口操作组合中的对象

3. 基本示例:文件系统

文件系统是组合模式最常见的应用,在作为客户端的控制台展现的文件树中,我们可以清楚的看到

  • 被用作容器的文件夹

  • 以及视作叶子的各类文件

而文件系统项抽象类则是组件

(1)抽象组件

C# 复制代码
// 文件系统项抽象类
public abstract class FileSystemItem
{
    // 文件系统项名称
    public string? Name { get; protected set; }
    // 文件系统项大小
    public long Size { get; protected set; }
    // 构造函数
    public FileSystemItem(string name)
    {
        Name = name;
    }
    // 显示文件系统项信息
    public abstract void Display(int depth);
    // 获取文件系统项大小
    public abstract long GetSize();
    // 添加子项
    public virtual void Add(FileSystemItem item) 
    {
        throw new NotSupportedException("无法将项添加到此FileSystemItem类型中。");
    }
    // 移除子项
    public virtual void Remove(FileSystemItem item) 
    {
        throw new NotSupportedException("无法从此FileSystemItem类型中删除项。"); 
    }
    // 获取子项
    public virtual FileSystemItem? GetChild(int index) 
    {
        throw new NotSupportedException("此FileSystemItem类型不包含子项。");
    }
    // 获取缩进字符串
    protected string GetIndentation(int depth)
    {
        return new string('-', depth * 2);
    }
}

(2)叶子节点 - 文件类

C# 复制代码
/// <summary>
/// 表示文件系统中的一个文件,提供对其名称和大小的访问。
/// </summary>
public class ExFile : FileSystemItem
{
    /// <summary>
    /// 使用指定的名称和大小初始化 ExFile 类的新实例。
    /// </summary>
    /// <param name="name">文件名</param>
    /// <param name="size">文件大小</param>
    public ExFile(string name,long size) : base(name)
    {
        Size = size;
    }
    /// <summary>
    /// 在控制台中显示文件的名称和大小,并根据指定的深度进行缩进格式化。
    /// </summary>
    /// <param name="depth">显示文件时应用的缩进级别。必须大于或等于0。根级别为0</param>
    public override void Display(int depth = 0)
    {
        Console.WriteLine($"{GetIndentation(depth)}[文件]{Name}({Size}bytes)");
    }
    /// <summary>
    /// 获取文件大小。
    /// </summary>
    /// <returns>文件大小(单位:byte)</returns>
    public override long GetSize() => Size;
}

(3)容器节点 - 文件夹类

C# 复制代码
/// <summary>
/// 表示文件系统中的一个文件夹,该文件夹可以包含文件和其他文件夹。
/// </summary>
/// <remarks>文件夹是一个复合文件系统项,可以包含多个子项,包括文件和子文件夹。
/// 使用提供的方法可以添加、删除和枚举其中的项。
/// 文件夹的大小计算为其子项大小的总和。
/// 此类通常用于构建层次化的文件系统结构。</remarks>
public class ExFolder : FileSystemItem
{
    // 子项集合
    private List<FileSystemItem> _items = [];
    /// <summary>
    /// 使用指定名称初始化Folder类的新实例。
    /// </summary>
    /// <param name="name">文件夹名称</param>
    public ExFolder(string name) : base(name)
    {
        Size = 0;
    }
    /// <summary>
    /// 在控制台中显示当前文件夹及其内容,使用缩进表示文件夹的层级结构。
    /// </summary>
    /// <remarks>此方法递归显示文件夹中包含的所有项目,每嵌套一层就增加一层缩进。输出内容包括文件夹名称及其总大小(以字节为单位)。</remarks>
    /// <param name="depth">要应用的缩进级别,表示文件夹在层级结构中的深度。必须为零或大于零。</param>
    public override void Display(int depth = 0)
    {
        Console.WriteLine($"{GetIndentation(depth)}[文件夹]{Name}(共{Size}bytes)");

        foreach (var item in _items)
        {
            item.Display(depth + 1);
        }
    }
    /// <summary>
    /// 获取当前文件夹及其所有子项的总大小。
    /// </summary>
    public override long GetSize()
    {
        long totalSize = 0;
        foreach (var item in _items)
        {
            totalSize += item.GetSize();
        }
        return totalSize;
    }
    /// <summary>
    /// 添加一个文件系统项到当前文件夹中。
    /// </summary>
    public override void Add(FileSystemItem item)
    {
        _items.Add(item);
        Size += item.GetSize();
    }
    /// <summary>
    /// 移除当前文件夹中的一个文件系统项。
    /// </summary>
    public override void Remove(FileSystemItem item)
    {
        _items.Remove(item);
        Size -= item.GetSize();
    }
    /// <summary>
    /// 基于索引位置获取当前文件夹中的一个子项。
    /// </summary>
    public override FileSystemItem? GetChild(int index)
    {
        if (index < 0 || index >= _items.Count)
        {
            throw new ArgumentOutOfRangeException(nameof(index), "索引超出范围。");
        }
        return _items[index];
    }
    /// <summary>
    /// 获取当前文件夹中子项的数量。
    /// </summary>
    public int GetItemCount()
    {
        return _items.Count;
    }
}

(4)客户端实现

C# 复制代码
Console.WriteLine("Hello, World!");

Console.WriteLine("=== 文件系统示例 ===");

// 创建示例文件
ExFile file1 = new ExFile("文件1.txt", 1200);
ExFile file2 = new ExFile("文件2.jpg", 3400);
ExFile file3 = new ExFile("文件3.docx", 5600);
ExFile file4 = new ExFile("文件4.mp4", 7800);
// 创建文件夹
ExFolder folder1 = new ExFolder("文件夹1");
ExFolder folder2 = new ExFolder("文件夹2");
ExFolder rootFolder = new ExFolder("根文件夹");
// 构建文件系统层次结构
folder1.Add(file1);
folder1.Add(file2);
folder2.Add(file3);
rootFolder.Add(folder1);
rootFolder.Add(folder2);
rootFolder.Add(file4);
rootFolder.Add(folder2);

// 显示文件系统结构
Console.WriteLine("\n文件系统结构:");
rootFolder.Display();

// 计算并显示根文件夹的总大小
Console.WriteLine($"\n根文件夹总大小: {rootFolder.GetSize()} bytes");

运行结果如下:

bash 复制代码
Hello, World!
=== 文件系统示例 ===

文件系统结构:
[文件夹]根文件夹(共23600bytes)
--[文件夹]文件夹1(共4600bytes)
----[文件]文件1.txt(1200bytes)
----[文件]文件2.jpg(3400bytes)
--[文件夹]文件夹2(共5600bytes)
----[文件]文件3.docx(5600bytes)
--[文件]文件4.mp4(7800bytes)
--[文件夹]文件夹2(共5600bytes)
----[文件]文件3.docx(5600bytes)

根文件夹总大小: 23600 bytes

4. 组合模式的优势

  • 优点:
  • 简化客户端代码:客户端可以一致地处理单个对象和组合对象

  • 更容易添加新类型的组件:添加新的叶子或容器类不会影响现有代码

  • 定义了包含基本对象和组合对象的类层次结构:可以更灵活地构建复杂结构

  • 可以方便地遍历整个组合结构:便于执行批量操作

  • 缺点:
  • 设计较为抽象:需要正确识别应用场景

  • 限制了类型约束:组合模式中的组件类型是相同的,可能不够类型安全

  • 使设计变得更加一般化:有时难以限制组合中的组件

5. 适用场景

  • 表示对象的部分-整体层次结构

    • 文件系统(文件夹和文件)

    • 组织架构(部门和员工)

    • 图形系统(复合图形和简单图形)

    • 希望客户端忽略组合对象与单个对象的差异

    • 客户端统一对待所有对象

    • 需要对对象集合执行统一操作

    • 批量操作(如移动、删除、计算总大小)

6. 实际应用建议

  • 最佳实践:

    • 明确定义组件接口:确保接口适合所有具体类

    • 在适当位置实现默认行为:在抽象类中提供默认实现

    • 保持叶子节点简单:避免在叶子节点中实现不必要的容器方法

考虑使用透明式组合模式:

csharp 复制代码
// 透明式:在抽象类中声明所有方法(包括管理子组件的方法)
// 安全式:只在容器类中声明管理子组件的方法

透明式 vs 安全式:

csharp 复制代码
// 透明式组合模式(推荐)
public abstract class Component
{
    // 公共操作
    public abstract void Operation();
    
    // 子组件管理(透明)
    public virtual void Add(Component c) { /* 默认实现 */ }
    public virtual void Remove(Component c) { /* 默认实现 */ }
    public virtual Component GetChild(int i) { return null; }
}

// 安全式组合模式
public abstract class Component
{
    // 只包含公共操作
    public abstract void Operation();
}

public class Composite : Component
{
    // 容器特有的方法
    public void Add(Component c) { }
    public void Remove(Component c) { }
    public Component GetChild(int i) { return null; }
    
    public override void Operation() { }
}

7. 与其他模式的关系

与装饰器模式:两者都使用递归组合,但装饰器模式为对象添加职责,而组合模式创建对象树

与迭代器模式:可以结合使用迭代器模式来遍历组合结构

与访问者模式:可以结合使用访问者模式对组合结构中的所有元素执行操作

8. 总结

组合模式通过树形结构组织对象,实现了部分-整体层次结构的表示。它使得客户端可以一致地处理单个对象和组合对象,简化了复杂结构的操作和维护。在实际应用中,组合模式特别适合需要构建层次化对象系统的场景。

相关推荐
鱼跃鹰飞4 小时前
DDD中的防腐层
java·设计模式·架构
会员果汁5 小时前
15.设计模式-组合模式
设计模式·组合模式
YUEchn6 小时前
无处不在的Agent
设计模式·llm·agent
茶本无香9 小时前
设计模式之二—原型模式:灵活的对象克隆机制
java·设计模式·原型模式
GISer_Jing9 小时前
Nano Banana+LoveArt三大核心功能解析:重构AI设计全链路,让创意落地更高效
人工智能·设计模式·aigc
会员果汁10 小时前
14.设计模式-备忘录模式
设计模式·备忘录模式
xiaolyuh12320 小时前
Spring 框架 核心架构设计 深度详解
spring·设计模式·spring 设计模式
GISer_Jing1 天前
智能体工具使用、规划模式
人工智能·设计模式·prompt·aigc
GISer_Jing1 天前
AI Agent:学习与适应、模型上下文协议
人工智能·学习·设计模式·aigc