【知识分享】C语言中的设计模式——命令模式

背景

    命令模式(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);
	}
}
/*************************************************************************************************/

适用范围

  1. 对于函数参数个数会有扩展且相互独立的时候,可以使用命令模式,以增强其扩展方式。

优势

  1. 命令易于扩展,不需要修改接口。
  2. 当命令之间不冲突时,可以实现继承的效果。

劣势

  1. 需要维护一套命令码,命令与实际含义之间没有必然联系,代码不直观。

你可能感兴趣的:(知识分享,#,设计模式,c语言,设计模式,命令模式)