一、C语言与设计模式概述
大家好,我是你们的老朋友。今天咱们聊聊C语言和设计模式。
很多人觉得,设计模式是Java、C++这些面向对象语言的专利。C语言?过程式语言,跟设计模式八竿子打不着。我以前也这么想,直到我在一个嵌入式项目中吃了大亏。
那是一个通信协议栈的项目,代码量大概五万行。一开始写得挺爽,后来需求一变,改一处要动全身。我花了整整两周重构,才把代码理顺。从那以后,我开始认真思考:C语言到底需不需要设计模式?
1.1 C语言特性回顾
咱们先快速过一下C语言的核心特性。这些是后面所有模式的基础。
- 指针:C语言的灵魂。函数指针更是实现回调、多态的关键。
- 结构体:把数据打包。配合函数指针,就能模拟出“类”的效果。
- 宏定义:预处理阶段的“代码生成器”。用好了能减少大量重复代码。
- 模块化:.h文件声明接口,.c文件隐藏实现。这就是信息隐藏。
- 内存管理:malloc/free。没有垃圾回收,一切靠自己。
你想想看,这些特性其实已经为设计模式埋下了伏笔。指针可以指向不同的函数,结构体可以包含不同的数据——这不就是多态和封装吗?
核心观点:C语言虽然没有class关键字,但通过“结构体+函数指针”的组合,完全可以实现面向对象的核心机制。
1.2 设计模式概念
设计模式是什么?说白了,就是前人踩过的坑、总结出来的套路。
GoF那本书里定义了23种经典模式。但别被数字吓到。实际项目中常用的,也就那么七八种。我个人的习惯是:先理解思想,再套用模式,而不是反过来。
设计模式有三个核心要素:
- 上下文:这个模式在什么场景下用?
- 问题:它要解决什么痛点?
- 解决方案:具体的代码结构是怎样的?
举个例子。你写了一个串口驱动,上层应用要接收数据。最简单的做法是轮询——但这样CPU就被占死了。怎么办?回调函数。这就是观察者模式的雏形。
我的经验:不要为了用模式而用模式。我见过有人在一个只有几百行代码的驱动里硬套了三个设计模式,结果代码比原来还难维护。模式是工具,不是目的。
1.3 为什么C语言需要设计模式
这个问题我问过很多同行。答案五花八门。我总结一下:
| 痛点 | 没有模式时 | 用了模式后 |
|---|---|---|
| 代码耦合度高 | 改一个模块,牵连一片 | 接口隔离,各管各的 |
| 扩展性差 | 加功能要改原有代码 | 开闭原则,加新模块就行 |
| 可读性低 | 一堆if-else,逻辑混乱 | 结构清晰,一看就懂 |
| 复用困难 | 代码和业务强绑定 | 模块解耦,到处能用 |
我曾经接手过一个老项目,里面有一个状态机,用switch-case写了三百多行。每次加一个状态,就要在好几个case里加代码。后来我把它重构为状态模式,每个状态一个文件。嗯,从那以后,加状态再也不用提心吊胆了。
避坑指南:C语言没有异常处理机制。设计模式中的错误处理,一定要通过返回值或错误码来传递。我曾经在回调函数里直接exit(),结果整个系统崩了。血的教训。
1.4 软件架构基础
设计模式是微观层面的。架构是宏观层面的。两者相辅相成。
嵌入式软件架构,常见的有这么几种:
- 分层架构:驱动层、中间件层、应用层。层与层之间通过接口通信。
- 事件驱动架构:通过事件队列解耦各个模块。适合GUI、通信协议等场景。
- 微内核架构:核心系统只提供最基本的功能,其他都做成插件。比如一些RTOS就是这么设计的。
我个人比较喜欢分层架构。原因很简单:每一层只关心自己的事。驱动层只管读写寄存器,中间件层只管协议解析,应用层只管业务逻辑。出了问题,定位也快。
下面这张图,是我对本章知识体系的一个梳理:
从这张图可以看出,C语言特性是地基,设计模式是框架,架构是蓝图。三者缺一不可。
1.5 一个简单的例子
光说不练假把式。咱们看一段代码。假设你要实现一个LED驱动,支持开关和闪烁。
// led.h
typedef struct {
void (*turn_on)(void);
void (*turn_off)(void);
void (*blink)(int interval_ms);
} LedOps;
// led_gpio.c
static void gpio_on(void) {
// 写GPIO寄存器
GPIO_PORT->BSRR = LED_PIN;
}
static void gpio_off(void) {
GPIO_PORT->BRR = LED_PIN;
}
static void gpio_blink(int interval_ms) {
// 定时器控制
}
LedOps led_gpio = {
.turn_on = gpio_on,
.turn_off = gpio_off,
.blink = gpio_blink
};
你看,通过结构体+函数指针,我们把操作封装起来了。上层应用只需要调用led_gpio.turn_on(),根本不用关心底层是GPIO还是I2C。这就是策略模式的雏形。
小技巧:我习惯在结构体里再加一个void *priv字段,用来存放私有数据。这样每个实例可以有自己的状态,更接近面向对象的感觉。
好了,这一章就到这里。记住一句话:设计模式不是银弹,但它能帮你写出更干净、更可维护的C代码。后面我们会一个一个模式地拆解,结合真实项目案例,让你真正掌握它们。
公众号:蓝海资料掘金营,微信deep3321