组合模式(Composite Pattern)的核心是将对象组合成树形结构,统一处理单个对象和组合对象 ,使客户端无需区分两者,可用一致的方式操作。在C语言中,可以通过结构体继承(模拟统一接口)+ 链表/数组(管理子对象) 实现:定义"组件"接口,叶子节点(单个对象)和容器节点(组合对象)都实现该接口,容器节点可包含子节点(叶子或其他容器)。
C语言实现组合模式的思路
- 抽象组件(Component) :定义所有节点(叶子和容器)的统一接口(如
operation、add、remove等函数指针)。 - 叶子节点(Leaf) :实现组件接口,代表不可再分的单个对象(无
add/remove功能)。 - 容器节点(Composite):实现组件接口,内部包含子组件列表(叶子或其他容器),可管理子节点并递归调用其方法。
示例:文件系统(文件和文件夹的树形结构)
文件系统是组合模式的经典场景:
- 文件夹(
Folder):容器节点,可包含文件或其他文件夹。 - 文件(
File):叶子节点,不可包含子节点。
两者都支持"显示信息"和"计算大小"的操作,客户端可统一处理。
步骤1:定义抽象组件(文件系统节点接口)
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 抽象组件:文件系统节点接口
typedef struct FSComponent {
char name[64]; // 节点名称(文件/文件夹名)
// 统一操作:显示节点信息(递归显示子节点)
void (*display)(struct FSComponent* self, int depth); // depth:缩进深度,用于树形显示
// 统一操作:计算节点大小(文件大小/文件夹总大小)
int (*get_size)(struct FSComponent* self);
// 容器节点特有:添加子节点(叶子节点可设为NULL)
void (*add)(struct FSComponent* self, struct FSComponent* child);
// 容器节点特有:移除子节点
void (*remove)(struct FSComponent* self, struct FSComponent* child);
// 用于链表管理子节点(仅容器节点使用)
struct FSComponent* next; // 下一个兄弟节点
} FSComponent;
步骤2:实现叶子节点(文件,File)
文件是不可再分的叶子节点,不支持add/remove,这两个方法可设为NULL或提示错误。
c
// 叶子节点:文件
typedef struct {
FSComponent component; // 继承抽象组件
int size; // 文件大小(KB)
} File;
// 文件的显示方法:打印文件名和大小
static void file_display(FSComponent* self, int depth) {
// 打印缩进(树形结构)
for (int i = 0; i < depth; i++) printf(" ");
printf("- 文件: %s (大小: %dKB)\n", self->name, ((File*)self)->size);
}
// 文件的大小计算:直接返回自身大小
static int file_get_size(FSComponent* self) {
return ((File*)self)->size;
}
// 创建文件(叶子节点)
FSComponent* file_create(const char* name, int size) {
File* file = (File*)malloc(sizeof(File));
if (!file) return NULL;
// 初始化组件接口
strncpy(file->component.name, name, sizeof(file->component.name)-1);
file->component.display = file_display;
file->component.get_size = file_get_size;
file->component.add = NULL; // 叶子节点不支持添加子节点
file->component.remove = NULL;
file->component.next = NULL;
// 初始化文件特有属性
file->size = size;
return (FSComponent*)file;
}
步骤3:实现容器节点(文件夹,Folder)
文件夹可包含多个子节点(文件或其他文件夹),通过链表管理子节点,并递归调用子节点的方法。
c
// 容器节点:文件夹
typedef struct {
FSComponent component; // 继承抽象组件
FSComponent* children; // 子节点链表(头指针)
} Folder;
// 文件夹的显示方法:先显示自身,再递归显示所有子节点
static void folder_display(FSComponent* self, int depth) {
Folder* folder = (Folder*)self;
// 打印自身信息
for (int i = 0; i < depth; i++) printf(" ");
printf("+ 文件夹: %s\n", self->name);
// 递归显示子节点(深度+1,增加缩进)
FSComponent* child = folder->children;
while (child) {
child->display(child, depth + 1);
child = child->next;
}
}
// 文件夹的大小计算:递归累加所有子节点的大小
static int folder_get_size(FSComponent* self) {
Folder* folder = (Folder*)self;
int total_size = 0;
FSComponent* child = folder->children;
while (child) {
total_size += child->get_size(child); // 递归计算子节点大小
child = child->next;
}
return total_size;
}
// 文件夹添加子节点(链表头部插入)
static void folder_add(FSComponent* self, FSComponent* child) {
if (!child) return;
Folder* folder = (Folder*)self;
// 新节点的next指向原头节点
child->next = folder->children;
// 头指针指向新节点
folder->children = child;
}
// 文件夹移除子节点(从链表中删除)
static void folder_remove(FSComponent* self, FSComponent* child) {
if (!child) return;
Folder* folder = (Folder*)self;
FSComponent* prev = NULL;
FSComponent* curr = folder->children;
// 查找子节点并从链表中删除
while (curr) {
if (curr == child) {
if (prev) {
prev->next = curr->next; // 前节点指向后节点
} else {
folder->children = curr->next; // 头节点更新
}
curr->next = NULL; // 断开被移除节点的链接
break;
}
prev = curr;
curr = curr->next;
}
}
// 创建文件夹(容器节点)
FSComponent* folder_create(const char* name) {
Folder* folder = (Folder*)malloc(sizeof(Folder));
if (!folder) return NULL;
// 初始化组件接口
strncpy(folder->component.name, name, sizeof(folder->component.name)-1);
folder->component.display = folder_display;
folder->component.get_size = folder_get_size;
folder->component.add = folder_add;
folder->component.remove = folder_remove;
folder->component.next = NULL;
// 初始化子节点链表(空)
folder->children = NULL;
return (FSComponent*)folder;
}
步骤4:使用组合模式(统一操作树形结构)
客户端可通过抽象组件接口FSComponent统一操作文件和文件夹,无需区分类型。
c
int main() {
// 1. 创建叶子节点(文件)
FSComponent* file1 = file_create("笔记.txt", 10);
FSComponent* file2 = file_create("图片.jpg", 200);
FSComponent* file3 = file_create("数据.csv", 50);
// 2. 创建容器节点(文件夹)
FSComponent* docs_folder = folder_create("文档");
FSComponent* pics_folder = folder_create("图片");
FSComponent* root_folder = folder_create("根目录");
// 3. 组合树形结构
docs_folder->add(docs_folder, file1); // 文档文件夹添加笔记.txt
docs_folder->add(docs_folder, file3); // 文档文件夹添加数据.csv
pics_folder->add(pics_folder, file2); // 图片文件夹添加图片.jpg
root_folder->add(root_folder, docs_folder); // 根目录添加文档文件夹
root_folder->add(root_folder, pics_folder); // 根目录添加图片文件夹
// 4. 统一操作:显示整个树形结构(从根目录开始)
printf("文件系统结构:\n");
root_folder->display(root_folder, 0); // depth=0(无缩进)
// 5. 统一操作:计算总大小
printf("\n根目录总大小: %dKB\n", root_folder->get_size(root_folder));
// 6. 清理内存(简化处理,实际需递归释放所有节点)
free(file1);
free(file2);
free(file3);
free(docs_folder);
free(pics_folder);
free(root_folder);
return 0;
}
输出结果
文件系统结构:
+ 文件夹: 根目录
+ 文件夹: 图片
- 文件: 图片.jpg (大小: 200KB)
+ 文件夹: 文档
- 文件: 数据.csv (大小: 50KB)
- 文件: 笔记.txt (大小: 10KB)
根目录总大小: 260KB
核心思想总结
- 树形结构统一处理 :无论是单个文件(叶子)还是文件夹(容器),客户端都通过
FSComponent接口操作,无需区分类型(如display和get_size对两者都适用)。 - 递归组合 :容器节点可包含其他容器节点(如"根目录"包含"文档"文件夹),形成多级树形结构,且操作可递归传递(如文件夹的
get_size会递归计算所有子节点大小)。 - 灵活性 :新增节点类型(如"压缩文件")时,只需实现
FSComponent接口,现有客户端代码无需修改,符合开放-封闭原则。
C语言通过结构体继承(File和Folder包含FSComponent)和链表管理子节点,完美模拟了组合模式的核心,适合处理树形结构场景(如文件系统、组织架构、UI组件树等)。