一、设计模式概述:什么是设计模式、为什么C语言也需要设计模式、面向过程与面向对象的设计思想对比

1.1 什么是设计模式?说白了就是“套路”

我刚开始做嵌入式开发那会儿,总觉得“设计模式”是Java、C++那些高级语言的专利。C语言嘛,直接撸代码就完事了。后来被现实狠狠教育了一回——一个项目写到两万行,代码乱得像一锅粥,改一个bug能引出三个新bug。那时候我才意识到:写代码容易,写好代码难。

设计模式是什么?说白了就是前辈们在长期实践中总结出来的“套路”。它不是具体的代码,而是一种解决问题的思路。就像下棋有定式,盖房子有图纸,写代码也有它的“模式”。

设计模式的核心定义:在特定场景下,针对反复出现的问题,给出的可复用的解决方案。

举个例子。你写一个LED驱动,今天用GPIO控制,明天客户说要用PWM调光。如果你把控制逻辑和硬件操作写死在一起,那改起来就痛苦了。但如果你用了一个“接口抽象”的模式,把“控制”和“实现”分开,换硬件就像换灯泡一样简单。这就是设计模式的价值。

1.2 为什么C语言也需要设计模式?

有人会问:“C语言不是面向过程的吗?设计模式不是面向对象的专利吗?”

嗯,这个问题我当年也纠结过。其实设计模式不依赖语言,它依赖的是你的思维方式。C语言虽然没有class、继承、多态这些语法糖,但我们可以用结构体、函数指针、宏定义等手段,实现同样的效果。

我在项目中遇到过这样一个场景:一个通信协议栈,需要支持UART、SPI、I2C三种物理层。如果用面向过程的方式写,就是三个大switch-case,每个case里塞一堆硬件操作代码。后来我用了“策略模式”的思路,把每种物理层的收发函数封装成函数指针,挂在结构体里。调用方只关心“发送数据”,不关心“怎么发送”。

你看,这不就是设计模式吗?

我的建议:在嵌入式C语言开发中,设计模式不是“要不要用”的问题,而是“怎么用好”的问题。它能帮你写出更易维护、更易扩展、更易测试的代码。

为什么C语言特别需要设计模式?因为嵌入式项目有几个特点:

  • 资源受限:不能随便new对象,不能滥用动态内存。设计模式能帮你用最小的开销,获得最大的灵活性。
  • 硬件耦合高:代码和硬件绑得太紧,换芯片就像换心脏。设计模式能帮你解耦,让代码“活”起来。
  • 长期维护:嵌入式产品生命周期长,动辄五到十年。没有好的架构,后期维护就是噩梦。

1.3 面向过程 vs 面向对象:不是对立,是互补

很多人把面向过程和面向对象对立起来,觉得C语言就是面向过程,C++就是面向对象。其实没那么绝对。

面向过程的核心思想是:数据 + 算法。你定义一些数据结构,然后写一堆函数去操作它们。代码的流程是线性的,从main()开始,一步步往下走。

面向对象的核心思想是:对象 + 消息。你把数据和操作数据的方法打包在一起,形成一个“对象”。对象之间通过“消息”(也就是函数调用)来交互。

我个人的理解是:面向过程关注“怎么做”,面向对象关注“谁来做”。

举个例子。你要控制一个电机:

  • 面向过程:写一个motor_set_speed(int speed)函数,里面直接操作寄存器。谁调用它,谁就负责传参数。
  • 面向对象:定义一个Motor结构体,里面包含速度、方向、状态等数据,以及set_speed、stop、get_status等函数指针。每个电机实例有自己的“行为”。

你看,C语言完全可以用结构体+函数指针的方式,模拟出“对象”的效果。这就是我们这门课要讲的核心——用C语言实现设计模式。

注意:不要为了用模式而用模式。我曾经见过一个项目,硬生生把简单的LED控制写成了“工厂模式+观察者模式+状态机模式”,代码量翻了三倍,可读性反而下降了。设计模式是工具,不是目的。

1.4 本章知识体系总览

下面这张图,是我梳理的本章知识结构。你可以把它当作一个“地图”,后面每讲一个模式,都能在这张图上找到它的位置。

设计模式概述 - 知识体系 设计模式概述 什么是设计模式 可复用的解决方案 为什么C语言也需要 资源受限·硬件耦合·长期维护 面向过程 vs 面向对象 数据+算法 vs 对象+消息 套路/定式 场景驱动 可复用性 资源受限 硬件解耦 长期维护 数据+算法 对象+消息 互补关系 核心:用C语言的工具,实现面向对象的思想 设计模式是桥梁,连接“怎么写”和“怎么设计”

1.5 一个简单的例子:用C语言实现“封装”

说了这么多理论,不如看一段代码。下面这个例子,展示了如何用C语言实现“封装”——把数据和操作打包在一起。

// 传统的面向过程写法
int motor_speed = 0;

void motor_set_speed(int speed) {
    motor_speed = speed;
    // 直接操作硬件寄存器
    // REG_SPEED = speed;
}

// 使用设计模式思想的写法
typedef struct {
    int speed;
    int direction;
    void (*set_speed)(struct Motor *self, int speed);
    void (*stop)(struct Motor *self);
} Motor;

void motor_set_speed_impl(Motor *self, int speed) {
    self->speed = speed;
    // 操作硬件寄存器
    // REG_SPEED = speed;
}

void motor_stop_impl(Motor *self) {
    self->speed = 0;
    // 停止电机
    // REG_SPEED = 0;
}

// 初始化一个电机对象
Motor motor1 = {
    .speed = 0,
    .direction = 1,
    .set_speed = motor_set_speed_impl,
    .stop = motor_stop_impl
};

// 使用
motor1.set_speed(&motor1, 100);

你看,第二种写法虽然代码量多了几行,但好处很明显:

  • 可扩展:再增加一个电机,只需要再初始化一个Motor结构体实例。
  • 可替换:如果换了硬件,只需要改motor_set_speed_impl和motor_stop_impl这两个函数,调用方完全不受影响。
  • 可测试:可以轻松地写一个“模拟电机”,用于单元测试。

一个小技巧:在实际项目中,我习惯把这种“对象”的定义放在头文件里,把实现放在.c文件里。这样调用方只看到接口,看不到实现细节——这就是“信息隐藏”。

1.6 避坑指南

我曾经在一个项目里,把设计模式用“过”了。一个简单的按键扫描,我硬是搞出了“状态模式+观察者模式+命令模式”。结果代码写出来,连我自己都看不懂了。后来重构,只用了一个简单的状态机,代码量减少60%,可读性反而更好。

所以,记住三条原则:

  1. 够用就好:不要为了用模式而用模式。一个if-else能解决的问题,别硬上策略模式。
  2. 先有需求,后有模式:模式是解决问题的,不是制造问题的。先搞清楚你要解决什么问题,再去找对应的模式。
  3. 模式是活的:不要死记硬背GoF的23种模式。在实际项目中,你可能会创造出自己的“模式”。

嗯,这一章就到这里。设计模式不是玄学,它就是你写代码时的一种“思维方式”。后面我们会一个一个地拆解,看看在C语言里,这些模式到底怎么落地。

可可精品资料库