2、分层架构设计原则:核心思想与实战落地

分层架构,说白了就是把代码按职责切几刀。每一层只干自己的事,不越界。我刚开始做嵌入式时,觉得分层就是多写几个文件夹,后来被一个项目狠狠教育了——一个模块改bug,牵连了三个模块一起重编。嗯,从那以后我才真正理解什么叫“层”。

2.1 分层架构的核心思想

核心就一句话:上层依赖下层,下层不依赖上层。每一层只通过接口跟相邻层通信,不能跨层调用。

典型嵌入式三层结构:

  • 应用层:业务逻辑、任务调度、协议处理
  • 中间层:驱动抽象、OS封装、公共组件
  • 硬件抽象层(HAL):寄存器操作、外设驱动、BSP

我在项目中遇到过最典型的反例:应用代码里直接写GPIO寄存器。换了个芯片平台,整个应用层全得重写。这就是没分层的代价。

嵌入式分层架构核心结构 应用层 业务逻辑 · 任务调度 · 协议处理 中间层 驱动抽象 · OS封装 · 公共组件 硬件抽象层(HAL) 寄存器操作 · 外设驱动 · BSP 依赖方向:上层 → 下层,禁止反向依赖和跨层调用

2.2 高内聚低耦合

这两个词听着抽象,我换个说法:高内聚就是每个模块只干一件事,并且干好;低耦合就是模块之间尽量少说话,说话也只通过接口。

原则 含义 反面案例
高内聚 模块内部功能紧密相关,不掺杂无关逻辑 一个.c文件里同时放UART驱动、PID算法、LED闪烁
低耦合 模块间通过接口通信,不直接访问内部数据 task_A.c直接修改task_B.c的全局变量

我的判断标准:如果你改一个模块的代码,需要同时改另外两个模块,那就是耦合太高了。我曾经在一个项目里,就因为一个定时器中断处理函数里塞了三个模块的逻辑,导致每次调参都要翻三个文件。后来拆成三个独立回调,世界清净了。

2.3 接口隔离原则

接口隔离原则(ISP)说:客户端不应该依赖它不需要的接口。翻译成人话:别给调用方塞一堆用不上的函数。

举个例子,你写一个传感器驱动,不要把所有传感器的操作都塞进一个接口里:

// ❌ 坏设计:一个接口塞了所有功能
typedef struct {
    void (*init)(void);
    void (*read_temp)(float *temp);
    void (*read_humidity)(float *hum);
    void (*read_pressure)(float *pres);
    void (*calibrate)(void);
    void (*self_test)(void);
} sensor_if_t;

// ✅ 好设计:按职责拆分接口
typedef struct {
    void (*init)(void);
    void (*read)(float *value);
} sensor_read_if_t;

typedef struct {
    void (*calibrate)(void);
    uint8_t (*self_test)(void);
} sensor_maint_if_t;

我建议:接口越小越好,一个接口最好只干一件事。调用方只需要知道自己用到的接口,不需要知道其他接口的存在。

注意:接口隔离不是让你把接口拆得稀碎。粒度要适中——太粗了耦合高,太细了管理成本高。我个人习惯:一个接口文件不超过5个函数指针。

2.4 依赖倒置原则

依赖倒置原则(DIP)是分层架构的基石。它的核心是:高层模块不应该依赖低层模块,两者都应该依赖抽象。

你想想看,如果应用层直接调用HAL层的函数,那换硬件平台时应用层也得改。正确的做法是:应用层定义接口,HAL层去实现这个接口。

// 应用层定义抽象接口(不依赖具体硬件)
typedef struct {
    void (*send)(uint8_t *data, uint16_t len);
    void (*recv)(uint8_t *data, uint16_t *len);
} uart_if_t;

// HAL层实现这个接口(依赖抽象)
static void uart1_send(uint8_t *data, uint16_t len) {
    // 操作UART1寄存器
}
static void uart1_recv(uint8_t *data, uint16_t *len) {
    // 读取UART1数据
}

const uart_if_t uart1_driver = {
    .send = uart1_send,
    .recv = uart1_recv,
};

这样做的好处:应用层只认接口,不认具体硬件。换芯片?换个驱动实现就行,应用层代码一行不动。

依赖倒置的实战价值:

  • 硬件平台迁移时,应用层零修改
  • 单元测试时,可以注入Mock驱动
  • 多人协作时,接口定义先行,开发可并行

2.5 四个原则的协同关系

这四个原则不是孤立的。我画了张图,你看它们怎么配合:

四个原则的协同关系 分层架构 高内聚 低耦合 接口隔离 依赖倒置 高内聚 + 低耦合 → 模块职责清晰;接口隔离 + 依赖倒置 → 模块间松绑

高内聚和低耦合保证模块内部不乱,接口隔离和依赖倒置保证模块之间不纠缠。四个原则一起用,你的代码才能经得起折腾。

我的经验:新项目开始时,先花半天把接口定义好。接口定了,分层就定了。后面写代码只是填空。我曾经因为急着写代码,跳过接口设计,结果项目中期重构了三次——血的教训。