问题描述
在进行嵌入式开发时,常常会需要根据事件来调用对应的处理函数。
例如发送照片时,可以选择微信发送、小红书发送、qq发送等。
这个流程可以通过很多种方法实现,但它们各自的优缺点是什么呢?
一、列表式
列表式的优点是结构清晰,容易理解;缺点是代码耦合度高,不方便移植和修改。例如以下代码,结构非常清晰,可以看到所有支持的APP种类和相应的处理函数;但是如果在一个新的项目中,提出了新的要求,例如取消对微信的支持,添加对微博的支持,那么需要在send_image这个函数中增加WEIBO_TYPE
,如果要不编译微信模块,还需要删除wechat_module.h
。
// send_iamge.c
#include "wechat_module.h"
#include "red_book_module.h"
#include "qq_module.h"
void send_image(uint8_t app_type, uint8_t *data, uint32_t data_len)
{
switch (app_type) {
case WECHAT_TYPE: {
wechat_image_handler(data, data_len);
break;
}
case RED_BOOK_TYPE: {
red_book_image_handler(data, data_len);
break;
}
case QQ_TYPE: {
qq_image_handler(data, data_len);
break;
}
default:
break;
}
}
二、注册式
注册式是通过注册的方式将处理函数注册在一个列表中,根据app_type
去调用相应的函数。它在一定程度上减少了代码的耦合,但指令处理流程已经没有列表式那么清晰了。而且要先调用wechat_init
这个函数才可以,这样虽然减少了增加减少模块时需要修改的代码,但解耦也不是很彻底。
// send_image.h
#define APP_NUM_MAX 20 // 最多支持的APP数量
typedef struct
{
uint8_t app_num;
void (*app_handler[APP_NUM_MAX])(uint8_t *data, uint32_t data_len);
} app_register_list_t;
// send_image.c
static app_register_list_t app_list = {0};
void app_init(void)
{
wechat_init();
...
}
void app_register(uint8_t app_type, void (*app_handler)(uint8_t *data, uint32_t data_len))
{
if (app_type >= APP_NUM_MAX) {
printf("invalid app type: %d", app_type);
return;
}
app_list.app_handler[app_type] = app_handler;
app_list.app_num++;
}
void send_image(uint8_t app_type, uint8_t *data, uint32_t data_len)
{
if (app_type >= APP_NUM_MAX) {
printf("invalid app type: %d", app_type);
return;
}
if (NULL == app_list.app_handler[app_type]) {
printf("unregistered app type: %d", app_type);
return;
}
app_list.app_handler[app_type](data, data_len);
}
// wechat.c
static void wechat_image_handler(uint8_t *data, uint32_t data_len)
{
/* 图片发送 */
}
void wechat_init(void)
{
app_register(WECHAT_TYPE, wechat_image_handler);
}
三、静态函数注册式
这种方式是在注册式的基础上,使用__attribute__(section)
将模块的初始化函数在链接时预先注册到内存中,在程序初始化时再注册需要的模块。这种方法实现了模块和发送动作的完全解耦,在增减模块时只需要修改模块代码,无需修改程序的其它部分。
// send_image.h
#define APP_NUM_MAX 20 // 最多支持的APP数量
extern uint32_t app_type_table_start[];
extern uint32_t app_type_table_end[];
typedef void (*app_init_handler_t)(void);
typedef struct
{
uint8_t app_num;
void (*app_handler[APP_NUM_MAX])(uint8_t *data, uint32_t data_len);
} app_register_list_t;
typedef struct
{
uint8_t app_type;
app_init_handler_t init_handler;
} app_init_list_t;
#define ADD_APP_TYPE(app_type, init_handler) \
const app_init_list_t app_type##_entry __attribute__((used, section(".app_type_init_table"))) = \
{app_type, init_handler};
// send_image.c
void app_init(void)
{
app_init_list_t *app_init_ptr = app_type_table_start;
for (; app_init_ptr < app_type_table_end; ++app_init_ptr) {
app_init_ptr->init_handler();
}
}
void app_register(uint8_t app_type, void (*app_handler)(uint8_t *data, uint32_t data_len))
{
if (app_type >= APP_NUM_MAX) {
printf("invalid app type: %d", app_type);
return;
}
app_list.app_handler[app_type] = app_handler;
app_list.app_num++;
}
void send_image(uint8_t app_type, uint8_t *data, uint32_t data_len)
{
if (app_type >= APP_NUM_MAX) {
printf("invalid app type: %d", app_type);
return;
}
if (NULL == app_list.app_handler[app_type]) {
printf("unregistered app type: %d", app_type);
return;
}
app_list.app_handler[app_type](data, data_len);
}
// wechat.c
static void wechat_image_handler(uint8_t *data, uint32_t data_len)
{
/* 图片发送 */
}
void wechat_init(void)
{
app_register(WECHAT_TYPE, wechat_image_handler);
}
ADD_APP_TYPE(WECHAT_TYPE, wechat_init);
总结
这三种方法各有利弊,第一种方法结构清晰,适合初学者或者对模块解构要求不高的场景。第二种和第三种结构都不太清晰,但代码非常简洁,耦合度很低。与第二种方法相比,第三种方法需要修改链接脚本,对基本功的要求更高,不适合初学者,但极大地提高了代码的可移植性,实现了模块的高内聚和低耦合。