命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
将实际含义功能映射为命令功能码进行表示,就像007只是一个间谍代号,而不是直接以名字进行区分。这样做的好处是可以统一编号管理。
假如现在要做一个设置串口参数的接口,应该怎么做?先来看下最常修改的串口配置,是波特率吧。所以我们先来实现一个串口波特率的修改。
void Uart_Config(uint32_t baud)
{
/* 修改波特率 */
Uart_SetBaud(baud);
}
后来发现,大部分串口调试助手上面,对串口的设置,基本都有4个,就是波特率,数据位,校验位,停止位。于是我们来修改下设置接口。
void Uart_Config(uint32_t baud,
uint8_t data_bits,
uint8_t parity,
uint8_t stop_bits)
{
/* 修改波特率 */
Uart_SetBaud(baud);
/* 修改数据位 */
Uart_SetDataBits(data_bits);
/* 修改校验位 */
Uart_SetParity(parity);
/* 修改停止位 */
Uart_SetStopBits(stop_bits);
}
但有没有发现,上面这种实现方式,接口变了,函数传参数变了,也就意味着上层调用这个接口的地方也需要变更。而且这里还有一个问题,就是当我只需要修改波特率时,实际确对数据位、校验位和停止位也进行了操作。为了解决上面两个问题,这里引入命令模式,接口只传两个参数,一个是命令类型,一个是设置的数值,函数内根据当前的命令类型来区分数值的作用。
/* 串口命令类型 */
enum emUartCmdType
{
UARTCMD_TYPE_set_baud = 0,
UARTCMD_TYPE_set_data_bits = 1,
UARTCMD_TYPE_set_parity = 2,
UARTCMD_TYPE_set_stop_bits = 3,
};
void Uart_ConfigCommand(uint32_t cmd,
uint32_t set_para)
{
switch (cmd)
{
case UARTCMD_TYPE_baud:
{
/* 修改波特率 */
Uart_SetBaud(set_para);
break;
}
case UARTCMD_TYPE_data_bits:
{
/* 修改数据位 */
Uart_SetDataBits(set_para);
break;
}
case UARTCMD_TYPE_parity:
{
/* 修改校验位 */
Uart_SetParity(set_para);
break;
}
case UARTCMD_TYPE_stop_bits:
{
/* 修改停止位 */
Uart_SetStopBits(set_para);
break;
}
}
}
上面的实现有没有看到熟悉的优化方式,没错,我们的表驱动又可以派上用场了。下面稍微使用表驱动优化亿点点。
/* 串口命令类型 */
enum emUartCmdType
{
UARTCMD_TYPE_set_baud = 0,
UARTCMD_TYPE_set_data_bits = 1,
UARTCMD_TYPE_set_parity = 2,
UARTCMD_TYPE_set_stop_bits = 3,
};
/* 增加表驱动结构,命令对应操作 */
struct tagUartCmdFunc
{
uint32_t Cmd;
void (*Func)(uint32_t);
};
/* 映射表 */
struct tagUartCmdFunc UartCmdTable[] =
{
{UARTCMD_TYPE_set_baud, Uart_SetBaud},
{UARTCMD_TYPE_set_data_bits, Uart_SetDataBits},
{UARTCMD_TYPE_set_parity, Uart_SetParity},
{UARTCMD_TYPE_set_stop_bits, Uart_SetStopBits},
};
void Uart_ConfigCommand(uint32_t cmd,
uint32_t set_para)
{
for (uint32_t i = 0; i < sizeof(UartCmdTable) / sizeof(UartCmdTable[0]); i++)
{
(UartCmdTable[i].Cmd == cmd)?(UartCmdTable[i].Func(set_para)):(0);
}
}
如果现在要新增一个Modbus-RTU的上层协议,此时Modbus本身也有一些设置参数,比如从机地址,所以也存在Modbus的设置函数。这时候就有两个设置的函数了,一个是设置串口的,一个是设置Modbus的。但最好的接口实现应该是使用Modbus设置参数的接口函数,同时也可以设置串口的相关参数。到这里,如果整个系统共用同一套命令系统,其实完全可以实现继承的效果。
/******************************************Command.h***********************************************/
/* 系统命令类型 */
enum emSysCmdType
{
UARTCMD_TYPE_set_baud = 0,
UARTCMD_TYPE_set_data_bits = 1,
UARTCMD_TYPE_set_parity = 2,
UARTCMD_TYPE_set_stop_bits = 3,
MODBUS_TYPE_set_slave_id = 10,
};
/* 增加表驱动结构,命令对应操作 */
struct tagSysCmdFunc
{
uint32_t Cmd;
void (*Func)(uint32_t);
};
/*************************************************************************************************/
/******************************************Uart.c***********************************************/
#include "Command.h"
/* 串口设置映射表 */
struct tagSysCmdFunc UartCmdTable[] =
{
{UARTCMD_TYPE_set_baud, Uart_SetBaud},
{UARTCMD_TYPE_set_data_bits, Uart_SetDataBits},
{UARTCMD_TYPE_set_parity, Uart_SetParity},
{UARTCMD_TYPE_set_stop_bits, Uart_SetStopBits},
};
void Uart_ConfigCommand(uint32_t cmd,
uint32_t set_para)
{
/* 串口的命令处理 */
for (uint32_t i = 0; i < sizeof(UartCmdTable) / sizeof(UartCmdTable[0]); i++)
{
(UartCmdTable[i].Cmd == cmd)?(UartCmdTable[i].Func(set_para)):(0);
}
}
/*************************************************************************************************/
/******************************************Modbus.c***********************************************/
#include "Command.h"
/* Modbus设置映射表 */
struct tagSysCmdFunc ModbusCmdTable[] =
{
{MODBUS_TYPE_set_slave_id, Modbus_SetSlaveID},
};
extern void Uart_ConfigCommand(uint32_t cmd,
uint32_t set_para);
/* 最终只调这个接口就可以实现串口及Modbus的设置 */
void Modbus_ConfigCommand(uint32_t cmd,
uint32_t set_para)
{
/* 先查询是否有串口设置的命令 */
Uart_ConfigCommand(cmd, set_para);
/* Modbus的命令处理 */
for (uint32_t i = 0; i < sizeof(ModbusCmdTable) / sizeof(ModbusCmdTable[0]); i++)
{
(ModbusCmdTable[i].Cmd == cmd)?(ModbusCmdTable[i].Func(set_para)):(0);
}
}
/*************************************************************************************************/