设计模式_C语言_表驱动模式

背景

其实在《设计模式——可利用面向对象软件的基础》一书中,提及的23种设计模式里并没有表驱动这种模式,因为《设计模式》一书更多的是根据面向对象的应用提取出来的设计方法。而表驱动模式本身是强烈依赖于数组这种数据结构的,跟对象扯不上关系,所以没有被收录在此书中。但由于它在C语言中的影响力之大,适用面之广,所以被收录在了《代码大全》(这可是另一本经典著作呀)一书中。

名词释义

表驱动本身是强烈依赖于数组结构,可以是一维数组,也可以是多维数组,然后根据该数据的分布式结构进行数据索引。即使是使用一维数组,也是通过数组下标索引到对应的数据,从索引这个角度来看,是Key-Value这种键-键值的对应关系,像极了在表格中查找数据(通过行和列找到对应的格子),所以称之为表驱动。

#include <stdint.h>
/* LED灯1亮 */
extern void LED1_On(void);
/* LED灯1灭 */
extern void LED1_Off(void);
/* LED灯2亮 */
extern void LED2_On(void);
/* LED灯2灭 */
extern void LED2_Off(void);

void LED_Ctrl(void)
{
	static uint32_t sta = 0;
	
	if (0 == sta) {
		LED1_On();
	} else {
		LED1_Off();
	}
	if (1 == sta) {
		LED2_On();
	} else {
		LED2_Off();
	}

	/* 两个灯,最大不超过2 */
	sta = (sta + 1) % 2;
}

/* 主函数运行 */
int main(void)
{
	while(1) {
		LED_Ctrl();
		os_delay(200);
	}
}

上面的实现有个问题,就比如现在要加一个灯,那首先需要实现LEDx_On和LEDx_Off,另外需要把这两个添加到LED_Ctrl里。如下所示:可以看到,加个灯,需要改三个地方,缺一不可,这对于软件维护是个大忌,很容易出问题。那应该怎么让修改点尽量的少呢?接下来就是重点了,怎么使用表驱动对上面的例子进行优化。

索引访问

现实可能不会有像直接访问那么理想的数据形式,有可能现在有10个数据,但并不是从第一行开始算起,比如第10行对应A数据,第20行对应B数据,这种就需要添加一个索引号来进行间接访问。如现在要获取A数据,那需要先找到10这个行号,再找到对应的A数据。对应到数组中,就是使用二维数据,一维作为索引,一维作为数据。
    还是以上面的跑马灯为例,如果现在硬件工程师跟你说:哎呀,LED1和LED2的PCB线拉反了,PCB定稿了,你这边改一下应该很快吧。行吧,说改就改,这里可以有两种做法,一种是直接把表里面LED1和LED2的操作调换一下。还有一种就是增加索引信息,实现如下:

/* LED灯1亮 */
extern void LED1_On(void);
/* LED灯1灭 */
extern void LED1_Off(void);
/* LED灯2亮 */
extern void LED2_On(void);
/* LED灯2灭 */
extern void LED2_Off(void);
/* LED灯3亮 */
extern void LED3_On(void);
/* LED灯3灭 */
extern void LED3_Off(void);
void LED_Ctrl(void)
{
	static uint32_t sta = 0;

	if (0 == sta) {
		LED1_On();
	} else {
		LED1_Off();
	}
	if (1 == sta) {
		LED2_On();
	} else
	{
		LED2_Off();
	}
	/* 两个灯,最大不超过2 */
	sta = (sta + 1) % 2;
}

/* 把同一个灯的操作封装起来 */
struct tagLEDFuncCB
{
	uint8_t Index;				/* 增加的索引 */
	void(*LedOn)(void);
	void(*LedOff)(void);
};

const static struct tagLEDFuncCB LedOpTable[] =
{
	/* 小序号优先执行--别问我为什么是LED2比LED1先跑,问硬件的去 */
	{1, LED1_On, LED1_Off},
	{0, LED2_On, LED2_Off},
	{2, LED3_On, LED3_Off},
};

void LED_Ctrl(void)
{
	static uint32_t sta = 0;
	uint8_t i = 0;

	for (i = 0; i < sizeof(LedOpTable) / sizeof(LedOpTable[0]); i++)
	{
		/* 跟直接访问的区别在于,是通过索引号来决定调用顺序,而不是表的顺序 */
		(sta == LedOpTable[i].Index) ? (LedOpTable[i].LedOn()) : (LedOpTable[i].LedOff());
	}

	/* 跑下个灯 */
	sta = (sta + 1) % (sizeof(LedOpTable) / sizeof(LedOpTable[0]));
}

int main(void)
{
	while (1) {
		LED_Ctrl();
		// os_delay(200);
	}
}

适用范围

大量用到"if/case"的地方,或者大量重复性操作,基本都可以使用表驱动,把变化部分抽象出来放到表中。

优势

在某些应用场合下,可以节省代码空间。
数据可变部分结构化,方便使用文件或其他形式,对数据可变部分进行替换。
代码简洁优雅。

劣势

执行效率比起直接使用switch-case要慢一些。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值