创建型模式:组合模式(C语言实现与嵌入式实战)

C语言开发者尤其是嵌入式方向,常面临需同时处理单个对象与同类对象集合的场景。如文件系统中操作文件(单个)与目录(集合),嵌入式GUI中处理按钮(单个)与布局容器(集合)。若为两者分别编写逻辑,会导致代码臃肿,维护扩展困难。

组合模式可精准解决此痛点,核心是"统一"------将单个与组合对象封装为同类结构,用相同接口处理,无需区分类型。本文从核心思想切入,手把手实现C语言版本,结合文件系统、嵌入式GUI实战落地,最后分享递归设计要点与性能优化技巧。

一、核心思想:用树形结构统一单个与组合对象

组合模式核心定义:将对象组合成树形结构,使客户端对单个对象和组合对象的使用具有一致性。通俗理解:单个对象为"叶子",组合对象为"树枝",树枝可嵌套叶子或更小树枝,无论操作叶子还是树枝,均采用同一方式(如显示、删除)。

组合模式有三个核心角色,是实现基础,C开发者可理解为:

  1. 抽象组件(Component):定义公共接口(如显示、增删子节点)与属性。C语言中用"结构体+函数指针"模拟抽象层,使叶子与容器节点统一实现接口。

  2. 叶子节点(Leaf):无子女的单个对象(如文件、按钮),实现抽象接口,不适用的增删方法返回错误或空实现。

  3. 容器节点(Composite):含子节点的组合对象(如目录、布局容器),实现抽象接口,同时维护子节点列表,提供增删逻辑。

组合模式核心优势:①透明性 :客户端无需判断节点类型,直接调用抽象接口;②可扩展性:新增节点类型只需实现抽象接口,无需修改现有代码,符合开闭原则。

二、C语言实现:抽象组件+叶子/容器节点

C语言无类和继承,采用"结构体嵌套+函数指针"模拟抽象组件,让叶子与容器节点嵌套抽象组件实现接口复用。实现分三步:定义抽象组件、实现叶子节点、实现容器节点,最后通过递归遍历演示功能,代码可直接复用。

2.1 第一步:定义抽象组件(Component)

抽象组件是核心,定义公共接口(显示、增删子节点、销毁)与属性(名称)。无论文件、目录还是按钮、布局,均需名称标识,接口则统一操作逻辑。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 向前声明,因为抽象组件的函数指针需要用到容器节点结构体
typedef struct Composite Composite;
// 抽象组件结构体:定义公共接口和属性
typedef struct Component {
    char name[32];  // 公共属性:名称
    // 函数指针:公共接口
    void (*show)(struct Component* self, int depth);  // 显示(depth:层级,用于树形展示)
    int (*add)(struct Component* self, struct Component* child);  // 添加子节点
    int (*remove)(struct Component* self, struct Component* child);  // 删除子节点
    void (*destroy)(struct Component* self);  // 销毁资源
    // 区分节点类型:0-叶子节点,1-容器节点(C语言无RTTI,手动标记)
    int is_composite;
    // 容器节点专属:子节点列表(叶子节点此指针为NULL)
    Composite* composite;
} Component;

说明:is_composite用于区分节点类型(C无RTTI),递归遍历需此标记;composite仅容器节点使用,指向子节点列表,叶子节点置空。

2.2 第二步:实现叶子节点(Leaf)

叶子节点无子女,增删方法返回错误,核心实现显示与销毁。以文件系统的"文件"为例,实现代码如下:

c 复制代码
// 叶子节点:文件(无子节点)
void file_show(Component* self, int depth) {
    for (int i = 0; i < depth; i++) printf("  ");
    printf("文件:%s\n", self->name);
}

int file_add(Component* self, Component* child) {
    printf("错误:文件%s不支持添加子节点!\n", self->name);
    return -1;
}

int file_remove(Component* self, Component* child) {
    printf("错误:文件%s无可用子节点可删除!\n", self->name);
    return -1;
}

void file_destroy(Component* self) {
    free(self); // 叶子节点无额外资源,直接释放
}

// 创建文件的工厂函数
Component* create_file(const char* name) {
    Component* file = (Component*)malloc(sizeof(Component));
    if (file == NULL) return NULL;
    strncpy(file->name, name, sizeof(file->name)-1);
    // 绑定接口
    file->show = file_show;
    file->add = file_add;
    file->remove = file_remove;
    file->destroy = file_destroy;
    file->is_composite = 0;
    file->composite = NULL;
    return file;
}

2.3 第三步:实现容器节点(Composite)

容器节点需管理子节点,先定义子节点链表,再实现所有抽象接口(增删子节点、递归显示/销毁)。以文件系统的"目录"为例,实现代码如下:

c 复制代码
// 容器节点:目录(支持子节点)
// 子节点链表结构
typedef struct ChildNode {
    Component* child;
    struct ChildNode* next;
} ChildNode;

// 容器专属数据
struct Composite {
    ChildNode* head;  // 链表头
    int child_count;  // 子节点数量,提升查询效率
};

// 递归显示目录及子节点
void directory_show(Component* self, int depth) {
    for (int i = 0; i < depth; i++) printf("  ");
    printf("目录:%s\n", self->name);
    ChildNode* node = self->composite->head;
    while (node != NULL) {
        node->child->show(node->child, depth + 1);
        node = node->next;
    }
}

// 添加子节点
int directory_add(Component* self, Component* child) {
    if (self == NULL || child == NULL) return -1;
    ChildNode* new_node = (ChildNode*)malloc(sizeof(ChildNode));
    if (new_node == NULL) return -1;
    new_node->child = child;
    new_node->next = NULL;
    // 插入链表尾部
    if (self->composite->head == NULL) {
        self->composite->head = new_node;
    } else {
        ChildNode* temp = self->composite->head;
        while (temp->next != NULL) temp = temp->next;
        temp->next = new_node;
    }
    self->composite->child_count++;
    return 0;
}

// 删除子节点
int directory_remove(Component* self, Component* child) {
    if (self == NULL || child == NULL || self->composite->head == NULL) return -1;
    ChildNode* prev = NULL, *curr = self->composite->head;
    while (curr != NULL) {
        if (strcmp(curr->child->name, child->name) == 0) {
            prev ? (prev->next = curr->next) : (self->composite->head = curr->next);
            free(curr);
            self->composite->child_count--;
            return 0;
        }
        prev = curr;
        curr = curr->next;
    }
    printf("错误:目录%s中未找到子节点%s!\n", self->name, child->name);
    return -1;
}

// 递归销毁目录及子节点
void directory_destroy(Component* self) {
    if (self == NULL) return;
    ChildNode* curr = self->composite->head;
    while (curr != NULL) {
        ChildNode* temp = curr;
        curr = curr->next;
        temp->child->destroy(temp->child);
        free(temp);
    }
    free(self->composite);
    free(self);
}

// 创建目录的工厂函数
Component* create_directory(const char* name) {
    Component* dir = (Component*)malloc(sizeof(Component));
    if (dir == NULL) return NULL;
    strncpy(dir->name, name, sizeof(dir->name)-1);
    // 初始化容器数据
    dir->composite = (Composite*)malloc(sizeof(Composite));
    if (dir->composite == NULL) { free(dir); return NULL; }
    dir->composite->head = NULL;
    dir->composite->child_count = 0;
    // 绑定接口
    dir->show = directory_show;
    dir->add = directory_add;
    dir->remove = directory_remove;
    dir->destroy = directory_destroy;
    dir->is_composite = 1;
    return dir;
}

2.4 测试:递归遍历树形结构

创建简单文件系统结构测试:根目录下含"文档"目录和"笔记.txt","文档"目录含"工作计划.docx"和"技术方案.pdf",通过抽象接口验证统一处理逻辑。

c 复制代码
int main() {
    // 创建节点
    Component* root = create_directory("根目录(/)");
    Component* doc_dir = create_directory("文档");
    Component* note_file = create_file("笔记.txt");
    Component* plan_file = create_file("工作计划.docx");
    Component* tech_file = create_file("技术方案.pdf");

    // 组合树形结构
    root->add(root, doc_dir);
    root->add(root, note_file);
    doc_dir->add(doc_dir, plan_file);
    doc_dir->add(doc_dir, tech_file);

    // 显示结构
    printf("=== 初始文件系统结构 ===\n");
    root->show(root, 0);

    // 测试删除
    printf("\n=== 删除文档目录下的工作计划.docx ===\n");
    doc_dir->remove(doc_dir, plan_file);
    root->show(root, 0);

    // 测试叶子节点添加子节点(预期报错)
    printf("\n=== 尝试给笔记.txt添加子节点 ===\n");
    note_file->add(note_file, plan_file);

    // 销毁资源
    root->destroy(root);
    return 0;
}

运行结果验证了组合模式的统一性,树形结构显示及操作效果如下:

text 复制代码
=== 初始文件系统结构 ===
目录:根目录(/)
  目录:文档
    文件:工作计划.docx
    文件:技术方案.pdf
  文件:笔记.txt

=== 删除文档目录下的工作计划.docx ===
目录:根目录(/)
  目录:文档
    文件:技术方案.pdf
  文件:笔记.txt

=== 尝试给笔记.txt添加子节点 ===
错误:文件笔记.txt不支持添加子节点!

三、实战场景:从文件系统到嵌入式GUI

拓展嵌入式GUI高频场景------控件树形布局,验证组合模式通用性。其应用逻辑与文件系统一致,仅需替换节点具体实现。

3.1 嵌入式GUI场景适配

嵌入式GUI中,按钮、文本框为叶子节点(无子控件),布局容器为容器节点(含子控件)。复用抽象组件结构,修改节点实现即可适配,代码如下:

c 复制代码
// 叶子节点:按钮(无子控件)
void button_show(Component* self, int depth) {
    for (int i = 0; i < depth; i++) printf("  ");
    printf("按钮:%s(可点击)\n", self->name);
}

int button_add(Component* self, Component* child) {
    printf("错误:按钮%s不支持添加子控件!\n", self->name);
    return -1;
}

int button_remove(Component* self, Component* child) {
    printf("错误:按钮%s无可用子控件可删除!\n", self->name);
    return -1;
}

void button_destroy(Component* self) { free(self); }

// 创建按钮
Component* create_button(const char* name) {
    Component* btn = (Component*)malloc(sizeof(Component));
    if (btn == NULL) return NULL;
    strncpy(btn->name, name, sizeof(btn->name)-1);
    btn->show = button_show;
    btn->add = button_add;
    btn->remove = button_remove;
    btn->destroy = button_destroy;
    btn->is_composite = 0;
    btn->composite = NULL;
    return btn;
}

// 容器节点:垂直布局
void vlayout_show(Component* self, int depth) {
    for (int i = 0; i < depth; i++) printf("  ");
    printf("垂直布局:%s\n", self->name);
    ChildNode* node = self->composite->head;
    while (node != NULL) {
        node->child->show(node->child, depth + 1);
        node = node->next;
    }
}

// 复用目录的增删、销毁逻辑,修改函数名即可
int vlayout_add(Component* self, Component* child) { /* 同directory_add */ }
int vlayout_remove(Component* self, Component* child) { /* 同directory_remove */ }
void vlayout_destroy(Component* self) { /* 同directory_destroy */ }

// 创建垂直布局
Component* create_vlayout(const char* name) {
    Component* layout = (Component*)malloc(sizeof(Component));
    if (layout == NULL) return NULL;
    strncpy(layout->name, name, sizeof(layout->name)-1);
    layout->composite = (Composite*)malloc(sizeof(Composite));
    if (layout->composite == NULL) { free(layout); return NULL; }
    layout->composite->head = NULL;
    layout->composite->child_count = 0;
    layout->show = vlayout_show;
    layout->add = vlayout_add;
    layout->remove = vlayout_remove;
    layout->destroy = vlayout_destroy;
    layout->is_composite = 1;
    return layout;
}

3.2 测试GUI布局组合

以登录页面布局测试:主垂直布局含"用户名文本框""密码文本框""登录按钮",通过组合模式统一管理。

c 复制代码
int main() {
    Component* main_layout = create_vlayout("登录页面主布局");
    Component* user_text = create_button("用户名文本框");
    Component* pwd_text = create_button("密码文本框");
    Component* login_btn = create_button("登录按钮");

    // 组合布局
    main_layout->add(main_layout, user_text);
    main_layout->add(main_layout, pwd_text);
    main_layout->add(main_layout, login_btn);

    // 显示布局
    printf("=== 登录页面GUI布局 ===\n");
    main_layout->show(main_layout, 0);

    // 销毁资源
    main_layout->destroy(main_layout);
    return 0;
}

运行可清晰显示树形布局,后续添加"忘记密码"按钮只需调用main_layout->add,无需修改现有代码,扩展性极强。

四、递归设计要点与性能优化

组合模式依赖树形结构,遍历需递归。递归简洁但在嵌入式场景易出现栈溢出、性能问题,以下是设计要点与优化技巧。

4.1 递归设计要点

  1. 明确递归终止条件:如叶子节点无子女,调用show接口不递归,避免栈溢出(嵌入式栈空间有限)。

  2. 统一接口参数:如show接口的depth参数,确保递归传递准确,避免逻辑混乱。

  3. 递归销毁资源:容器节点destroy需先销毁所有子节点,避免嵌入式系统内存泄漏。

4.2 性能优化技巧

  1. 迭代替代深层递归:深层嵌套时,用自定义栈/队列模拟递归,避免栈溢出。

  2. 缓存子节点数量child_count属性可快速获取子节点数,无需遍历链表,减少CPU占用。

  3. 预分配内存池:避免频繁malloc/free导致嵌入式系统内存碎片,提升稳定性。

  4. 缓存遍历结果:多次遍历树形结构时,缓存首次结果,避免重复递归,提升查询性能。

五、总结与互动引导

本文从C开发者实战视角,讲清组合模式"树形结构统一单个与组合对象"的核心,通过"抽象组件+叶子/容器节点"实现文件系统、嵌入式GUI场景落地,分享递归设计与优化技巧,代码可直接复用。

组合模式在嵌入式C开发中应用广泛,除文件系统、GUI,还可用于传感器网络、任务管理等场景。掌握它可简化代码结构,提升项目扩展性与可维护性。

若本文对你有帮助,欢迎点赞、收藏、关注!后续将分享更多设计模式的C语言实现及嵌入式应用案例,助力优化代码、提升效率。

若实现过程有疑问,或有其他设计模式需求,欢迎在评论区留言讨论!

相关推荐
Fcy6482 小时前
C++ 11 新增特性(下)
开发语言·c++·c++11·lambda·包装器
zhengfei6112 小时前
CVE-2025-55182 的 POC,可在 Next.js 16.0.6 上运行
开发语言·javascript·ecmascript
m0_635647482 小时前
Qt中使用opencv库imread函数读出的图片是空
开发语言·c++·qt·opencv·计算机视觉
少控科技2 小时前
QT新手日记034
开发语言·qt
玄同7652 小时前
MermaidTrace库:让Python运行时“自己画出”时序图
开发语言·人工智能·python·可视化·数据可视化·日志·异常
燃于AC之乐2 小时前
【C++手撕STL】Vector模拟实现:从零到一的容器设计艺术
开发语言·c++·容器·stl·vector·底层·模板编程
进击的小头2 小时前
创建型模式:装饰器模式(C语言实战指南)
c语言·开发语言·装饰器模式
开开心心就好2 小时前
视频伪装软件,.vsec格式批量伪装播放专用
java·linux·开发语言·网络·python·电脑·php
松涛和鸣2 小时前
63、IMX6ULL ADC驱动开发
c语言·arm开发·驱动开发·单片机·gpt·fpga开发