====== C# 设计模式:组合模式 (Composite Pattern) ======
组合模式(Composite Pattern)是一种**结构型设计模式**。它允许你将对象组合成**树形结构**来表现“整体/部分”的层次结构。组合能让客户端以一致的方式处理个别对象(叶子)和对象组合(容器)。
===== 1. 核心概念 =====
在 C# 中实现组合模式,主要涉及三个角色:
* **Component (抽象构件)**: 这是一个接口或抽象类,为组合中的对象声明接口。它实现了所有类共有接口的默认行为。
* **Leaf (叶子节点)**: 在组合中表示叶子节点对象,叶子节点没有子节点。它实现了 Component 中的业务方法。
* **Composite (容器节点)**: 定义有子部件的那些部件的行为。它存储子部件,并实现在 Component 接口中定义的与子部件有关的操作(如 Add, Remove)。
===== 2. 操作步骤与代码实现 =====
我们以**“文件系统”**为例:文件夹(Composite)可以包含文件(Leaf),也可以包含其他文件夹。
==== 第一步:定义抽象构件 (Component) ====
这是所有对象的基类。为了保证“透明性”,我们通常在这里定义管理子节点的方法(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("叶子节点不支持移除子项");
}
}
}
==== 第二步:定义叶子节点 (Leaf) ====
叶子节点代表具体的实体(如文件)。它**不包含**子节点列表。
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,因为继承的默认行为是抛出异常,符合逻辑
}
}
==== 第三步:定义容器节点 (Composite) ====
容器节点包含一个子节点列表,并实现了管理子节点的方法。**关键点在于它的业务方法通常会递归调用子节点的业务方法。**
namespace CompositePatternDemo
{
// 容器节点:文件夹
public class Directory : FileSystemItem
{
// 内部维护一个子组件列表
private List _children = new List();
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);
}
}
===== 3. 两种实现方式的权衡:透明 vs 安全 =====
在 C# 实现中,关于 `Add` 和 `Remove` 方法放在哪里,有两种流派:
- **透明方式 (Transparency)**:
- **操作**: 如上例所示,在抽象类 `Component` 中声明 `Add`/`Remove`。
- **优点**: 客户端不需要区分是叶子还是容器,调用一致。
- **缺点**: 叶子节点调用 `Add` 会在运行时报错(不够安全)。
- **安全方式 (Safety)**:
- **操作**: 在 `Component` 中**不**声明 `Add`/`Remove`,只在 `Composite` 类中声明。
- **优点**: 编译期就能检查错误,叶子节点根本没有 `Add` 方法。
- **缺点**: 客户端必须区分对待,如果想添加子节点,必须先将对象强制转换为 `Composite` 类型。
===== 4. 适用场景 =====
* **BIM 开发**: 如“筏板(Raft)”包含“电梯井(Pit)”,“楼层”包含“墙/柱/梁”。
* **UI 控件树**: WinForms 或 WPF 中,Panel 可以包含 Button,也可以包含其他 Panel。
* **图形编辑器**: 一个“分组”可以包含圆形、矩形,也可以包含另一个“分组”。
===== 5. 总结 =====
组合模式的核心在于利用**多态**和**递归**。通过将容器和叶子抽象为同一类型,使得极其复杂的树形结构在客户端看来只是一个简单的对象。