创建型模式:组合模式(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语言实现及嵌入式应用案例,助力优化代码、提升效率。

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

相关推荐
Scott9999HH3 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社4 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海4 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖5 小时前
JDK 26 新特性详解
java·开发语言
马优晨5 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区7 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大8 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai8 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜8 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa8 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio