第三章 架构设计原则:模块化设计、分层架构、高内聚低耦合

好,咱们进入正题。这一章聊的是架构设计的核心原则。说白了,就是怎么把一个大系统拆得明明白白,让每个人各司其职,出了问题也能快速定位。

我做了这么多年嵌入式中间件,见过太多「一锅粥」式的代码。一个文件几千行,全局变量满天飞,改一个功能要动十个模块。嗯,这种项目,后期维护成本高得吓人。所以今天咱们把这三个原则掰开揉碎了讲清楚。

3.1 模块化设计:拆,是门艺术

模块化设计,就是把一个复杂系统拆成若干个独立的功能单元。每个单元负责一件事,做好它。

为什么要拆?你想想看,一个网关中间件,要处理协议转换、数据缓存、日志记录、设备管理……如果全揉在一起,一个人根本hold不住。拆开之后,每个人负责一个模块,开发效率翻倍。

我个人习惯,模块划分遵循两个原则:

  • 功能单一:一个模块只做一件事。比如「协议解析模块」只管解析,别管数据存哪。
  • 接口清晰:模块之间通过定义好的接口通信,别偷偷摸摸访问全局变量。

举个例子,一个简单的日志模块:

/* log_module.h */
#ifndef LOG_MODULE_H
#define LOG_MODULE_H

typedef enum {
    LOG_LEVEL_DEBUG,
    LOG_LEVEL_INFO,
    LOG_LEVEL_WARN,
    LOG_LEVEL_ERROR
} log_level_t;

void log_init(const char *filename);
void log_write(log_level_t level, const char *fmt, ...);
void log_close(void);

#endif /* LOG_MODULE_H */

你看,这个模块只暴露了三个函数:初始化、写日志、关闭。内部怎么实现?用文件还是串口?那是模块自己的事。调用者不需要知道。

我的经验:模块化设计初期会多花点时间规划接口,但后期收益巨大。我曾经在一个项目里,因为日志模块接口设计得好,后来从文件日志切换到网络日志,只改了模块内部代码,上层一行没动。

3.2 分层架构:三层结构,各司其职

分层架构,就是把系统按职责分成几个「层」。每一层只跟相邻层打交道。咱们的嵌入式网关中间件,我建议分三层:

层次 职责 典型内容
硬件抽象层(HAL) 屏蔽硬件差异 GPIO、UART、SPI、I2C 驱动封装
核心服务层 提供通用服务 协议栈、数据缓存、任务调度、日志
应用接口层 暴露给上层应用 API 接口、回调注册、配置管理

硬件抽象层,说白了就是给硬件穿件「外套」。不同厂家的MCU,GPIO操作可能完全不同。但通过HAL,上层看到的都是统一的接口:hal_gpio_set()hal_uart_send()。换芯片?只改HAL层就行。

核心服务层,这是中间件的「大脑」。协议转换、数据缓存、任务调度都在这里。它不关心底层是哪个芯片,也不关心上层是什么应用。它只做好自己的事。

应用接口层,是给应用开发者看的。他们不需要知道底层怎么跑,只需要调用几个API就能完成功能。比如:

/* 应用接口层示例 */
int gateway_send_data(uint8_t *data, uint32_t len);
int gateway_register_callback(gateway_event_t event, void (*cb)(void *));
注意:分层架构最忌讳「跨层调用」。我曾经见过一个项目,应用层直接调用了HAL层的函数。结果换芯片时,应用层代码也得改,分层就白做了。记住:每一层只能调用相邻层。

3.3 高内聚低耦合:模块的「内功」与「外交」

这两个词,你肯定听过。但真正理解的人不多。

高内聚,就是一个模块内部的东西,应该紧密相关。比如一个「数据缓存模块」,里面所有函数都应该跟缓存操作有关。别把「计算校验和」也塞进去,那是另一个模块的事。

低耦合,就是模块之间的依赖要尽可能少。A模块改了,B模块最好不用动。

怎么做到?我分享几个实战技巧:

  • 用接口,别用全局变量:模块之间通过函数调用或消息队列通信,别共享全局变量。全局变量是耦合的万恶之源。
  • 定义好契约:接口的参数、返回值、行为,要明确写在头文件里。调用者只依赖这个契约,不依赖实现细节。
  • 依赖倒置:高层模块不依赖低层模块,两者都依赖抽象接口。这个有点深,后面章节会细讲。

举个例子,一个低耦合的定时器模块设计:

/* timer_module.h */
typedef void (*timer_callback_t)(void *arg);

int timer_create(uint32_t interval_ms, timer_callback_t cb, void *arg);
int timer_delete(int timer_id);

你看,这个模块只暴露了创建和删除定时器的接口。具体用硬件定时器还是软件定时器?那是模块内部的事。调用者只需要提供回调函数和参数就行。

核心要点:

  • 模块化:拆成独立单元,每个单元只做一件事
  • 分层:HAL、核心服务、应用接口,三层各司其职
  • 高内聚:模块内部紧密相关
  • 低耦合:模块之间依赖最少

嗯,这三个原则,说起来简单,做起来需要经验积累。我刚开始做架构时,也经常把模块拆得太碎,或者层与层之间界限模糊。后来慢慢摸索,才找到平衡点。

记住一句话:好的架构,是改出来的。别指望一次设计就完美。先搭个框架,跑起来,然后根据实际需求不断调整。这才是实战中的常态。

下一章,咱们聊聊具体怎么搭这个三层架构的代码骨架。到时候我会拿一个真实的网关项目做例子,手把手带你走一遍。