C语言设计模式——命令模式
好处:让代码清晰明了,容易添加和删除,易维护。
哪些地方会用到命令模式?(列出几个常见的例子)
1、按键处理,每个按键按下得到一个索引(指的就是命令),一个按键对应一个处理函数。按键处理命令模式
2、协议解析(串口,网口,CAN,等等);以串口为例简单说明一下,比如有如下协议:http类型解析(html,jpg,jpeg...)
帧头 | 命令 | 数据长度 | 数据内容 | 校验 | 帧尾 |
1字节 | 1字节 | 2字节 | n字节 | 2字节 | 1字节 |
命令1:0x01 温度
命令2:0x02 湿度
命令3:0x03 光照强度
传统的实现方式如下:(伪代码)
static uint8_t parse(char *buffer, uint16_t length)
{uint8_t head = buffer[0];uint8_t cmd = buffer[1];uint16_t len = (buffer[2] << 8) | buffer[3];uint16_t crc = CRCCheck(buffer, length - 3);uint8_t tail = buffer[length - 1];if((head != xxx) && (tail != xxx) && (crc != ((buffer[length - 3]) << 8) | buffer[length - 2])){return 0;}switch(cmd){case 0x01:int temperatue = *(int *)&buffer[4];printf("temperatue = %d\n", temperatue);break;case 0x02:int humidity = *(int *)&buffer[4];printf("humidity = %d\n", humidity);break;case 0x03:int illumination= *(int *)&buffer[4];printf("illumination = %d\n", illumination);break;default:printf("parse error\n");break;}return 1;
}
通过这段伪代码可以看出代码结构的一些问题,如果要添加更多的命令,势必需要向switch case语句中加入更多的case语句。使得解析函数越来越臃肿。当然我们可以使用如下方式规避一些问题:(伪代码)
// 当心字节对齐的问题
typedef struct
{uint8_t head;uint8_t cmd;uint16_t length;uint8_t data[1];
} package_t;static int parse_temperature(char *buffer)
{int value = *(int *)buffer;printf("temperature = %d\n", value);
}
static int parse_humidity(char *buffer)
{int value = *(int *)buffer;printf("humidity = %d\n", value);
}static int parse_illumination(char *buffer)
{int value = *(int *)buffer;printf("illumination = %d\n", value);
}static uint8_t parse(char *buffer, uint16_t length)
{package_t *frame = (package_t *)buffer;uint16_t crc = CRCCheck(buffer, length - 3);uint8_t tail = buffer[length - 1];if((frame->head != xxx) && (tail != xxx) && (crc != (buffer[length - 3]) << 8 | buffer[length - 2])){return 0;}switch(frame->cmd){case 0x01:parse_temperature(frame->data);break;case 0x02:parse_humidity(frame->data);break;case 0x03:parse_illumination(frame->data);break;default:printf("parse error\n");}return 1;
}
相比于第一段代码,已经有了很大的改善,扩展性也得到了很大的提升。随着项目的进行,解析函数还是可能会越来越大。接下来就开始介绍命令模式。在命令模式里面,我们只需要维护一个命令列表就行了,而不需要关注解析函数本身。(伪代码)
// 当心字节对齐的问题
typedef struct
{uint8_t head;uint8_t cmd;uint16_t length;uint8_t data[1];
} package_t;static int parse_temperature(char *buffer)
{int value = *(int *)buffer;printf("temperature = %d\n", value);
}
static int parse_humidity(char *buffer)
{int value = *(int *)buffer;printf("humidity = %d\n", value);
}static int parse_illumination(char *buffer)
{int value = *(int *)buffer;printf("illumination = %d\n", value);
}typedef struct
{uint8_t cmd;void (* handle)(char *buffer);
} package_entry_t;static const package_entry_t package_items[] =
{{0x01, parse_temperature},{0x02, parse_humidity},{0x03, parse_illumination},{0xFF, NULL},
};static uint8_t parse(char *buffer, uint16_t length)
{package_t *frame = (package_t *)buffer;uint16_t crc = CRCCheck(buffer, length - 3);uint8_t tail = buffer[length - 1];const package_entry_t *entry;if((frame->head != xxx) && (tail != xxx) && (crc != (buffer[length - 3]) << 8 | buffer[length - 2])){return 0;}for(entry = package_items; entry->handle != NULL; ++entry){if(frame->cmd == entry->cmd){entry->handle(frame->data);break;}}return 1;
}
我们可以看到,解析函数写好之后就不用动了,需要变化的只是一个表。这样写能让代码看起来干净整洁清晰,命令也可以使用宏定义或者枚举,看自己的喜好吧。一个命令对应一个处理函数,尽量使用此类方式去取代swicth case的方式,始终让代码保持整洁易扩展易维护的特性。
上面使用了命令模式作用于串口协议,同样的方式可以适用于各种协议,网口协议的话,格式都不用改。如果是can协议的话,将can的id用作命令,就ok了。其他的,类似。