用C语言实现组合模式
组合模式(Composite Pattern)的核心是将对象组合成树形结构,统一处理单个对象和组合对象,使客户端无需区分两者,可用一致的方式操作。在C语言中,可以通过结构体继承(模拟统一接口)+ 链表/数组(管理子对象) 实现:定义“组件”接口,叶子节点(单个对象)和容器节点(组合对象)都实现该接口,容器节点可包含子节点(叶子或其他容器)。
C语言实现组合模式的思路
- 抽象组件(Component):定义所有节点(叶子和容器)的统一接口(如
operation
、add
、remove
等函数指针)。 - 叶子节点(Leaf):实现组件接口,代表不可再分的单个对象(无
add
/remove
功能)。 - 容器节点(Composite):实现组件接口,内部包含子组件列表(叶子或其他容器),可管理子节点并递归调用其方法。
示例:文件系统(文件和文件夹的树形结构)
文件系统是组合模式的经典场景:
- 文件夹(
Folder
):容器节点,可包含文件或其他文件夹。 - 文件(
File
):叶子节点,不可包含子节点。
两者都支持“显示信息”和“计算大小”的操作,客户端可统一处理。
步骤1:定义抽象组件(文件系统节点接口)
#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
或提示错误。
// 叶子节点:文件
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)
文件夹可包含多个子节点(文件或其他文件夹),通过链表管理子节点,并递归调用子节点的方法。
// 容器节点:文件夹
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
统一操作文件和文件夹,无需区分类型。
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); // 文档文件夹添加笔记.txtdocs_folder->add(docs_folder, file3); // 文档文件夹添加数据.csvpics_folder->add(pics_folder, file2); // 图片文件夹添加图片.jpgroot_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组件树等)。