【知识分享】C语言的设计模式——责任链、观察者

一、背景

    责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
    观察者模式(Observer Pattern),则是当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。
    为什么把这两种模式放一起?是因为这两者在C语言里的实现比较接近,责任链是在接收到请求时,分别给接收对象进行处理,当处理的请求不是自身接收对象的请求时(或者是自身的责任范围内的请求,具体视责任链的定义来决定),则传递给下一个接收对象。所以对于责任链来说,只有在职责范围内的请求才会执行。而观察者模式,则是不管职责范围,全体都会执行。所以观察者模式可以看作是责任链的一种特殊情况(即所有条件均满足的情况)。

二、名词释义

    既然提到链,那这里肯定会涉及链表,所以这里其实是两部分的内容,一个是责任的划分,一个是链表的表示。而对于观察者,则是少了一个责任划分的条件而已。

三、C语言例子

责任链

    比如现在要做一个手机转账的功能,需求是当转账金额不大于100时,直接转,不需要任何提示;当转账金额大于100时,提示确认信息;当转账金额大于1000时,需要密码确认;当转账金额大于10000时,需要手机短信验证码确认。比如当转账金额为20000时,需要提示信息,并且密码确认,并且还需要手机短信验证。
在这里插入图片描述
    我们先用常规思路来解答一下。

/* 无动作 */
extern void DoNothing(void);
/* 提示 */
extern void Tips(void);
/* 密码确认 */
extern void PswConfig(void);
/* 验证码确认 */
extern void CodeConfig(void);

void TransferFunc(uint32_t value)
{
	if (value >= 10000)
	{
		CodeConfig();
	}
	else if (value >= 1000)
	{
		PswConfig();
	}
	else if (value >= 100)
	{
		Tips();
	}
	else
	{
		DoNothing();
	}
}

    上面这种实现其实没什么问题,但如果后面要加个操作,比如金额大于10w时,需要本人视频核对。那这个时候就需要去改动TransferFunc这个函数主体内容,一不小心可能还会把原本那些金额的处理给变更成其他操作。为了让执行逻辑与变更对象解耦开,这里使用责任链的方式。

/* 定义责任链的结构 */
struct tagTransfer
{
	uint32_t Value;
	void (*Func)(void);
	struct tagTransfer *Next;
};

/* 无动作 */
extern void DoNothing(void);
/* 提示 */
extern void Tips(void);
/* 密码确认 */
extern void PswConfig(void);
/* 验证码确认 */
extern void CodeConfig(void);

/* 定义链头 */
struct tagTransfer *Head = NULL;

void TransferAddList(uint32_t value, void (*func)(void))
{
	struct tagTransfer *last = Head;
	struct tagTransfer *next = malloc(sizeof(struct tagTransfer));
	if (NULL != next)
	{
		next->value = value;
		next->func = func;

		/* 按责任等级排个序 */
		if (NULL == last)
		{
			last = next;
		}
		else
		{
			for (;NULL != last->next; last = last->next)
			{
				/* 按从大到小排序 */
				if (value > last->next->value)
				{
					next->next = last->next;
					last->next = next;
					return ;
				}
			}
			last->next = next;
		}
	}
}

/* 责任链执行判断 */
void TransferFunc(uint32_t value)
{
	struct tagTransfer *last = Head;
	for (;NULL != last; last = last->next)
	{
		if (value > last->value)
		{
			last->func();
		}
		else
		{
			break;
		}
	}
}

    但对于嵌入式端,没必要使用动态链接的形式增减责任链内容,这里有另一种更适用于嵌入式的静态链接,没错,就是我们的老朋友表驱动。下面用表驱动的例程来展示下。

/* 定义责任链的结构 */
struct tagTransfer
{
	uint32_t Value;
	void (*Func)(void);
};

/* 无动作 */
extern void DoNothing(void);
/* 提示 */
extern void Tips(void);
/* 密码确认 */
extern void PswConfig(void);
/* 验证码确认 */
extern void CodeConfig(void);

/* 做好责任划分的静态定义 */
static const struct tagTransfer FuncTable[] =
{
	{0, DoNothing},
	{100, Tips},
	{1000, PswConfig},
	{10000, CodeConfig},
};

/* 责任链执行判断 */
void TransferFunc(uint32_t value)
{
	for (uint8_t i = 0; i < sizeof(FuncTable) / sizeof(FuncTable[0]); i++)
	{
		if (value >= FuncTable[i].Value)
		{
			FuncTable[i].Func();
		}
	}
}

观察者

    对于观察者模式,可以使用同样的结构,只是使用的场景不大一样,一般需要订阅、通知的场景,就像公众号,只要订阅了公众号,当公众号有新的推文时,会直接通知到所有订阅这个公众号的人。当前技术中,有使用这种方式的,比较典型的,就是MQTT协议。
    这里我们也来做个小例子,比如现在有个触摸屏,当点击屏幕的按键时,需要显示按键被按下的状态,另外还需要响一下蜂鸣器。那我们先用常规思路处理一下。

/* 按键状态 */
enum emKeyState
{
	KEY_STA_bounce = 0,		/* 按键弹起 */
	KEY_STA_press = 1,		/* 按键按下 */
};

extern uint8_t KeySta = KEY_STA_bounce;

/* 蜂鸣器动作 */
extern void BuzzerFunc(void);

/* 显示屏刷新 */
extern void DisplayFunc(void);

void main(void)
{
	while(1)
	{
		/* 按键按下时动作 */
		if (KEY_STA_press == KeySta)
		{
			/* 蜂鸣器响 */
			BuzzerFunc();
	
			/* 界面刷新 */
			DisplayFunc();
		}
	}
}

    如果这时候需要再执行一个点亮LED的动作,那就需要找到按键按下这个判断条件,增加一个点亮LED的动作。

/* 按键状态 */
enum emKeyState
{
	KEY_STA_bounce = 0,		/* 按键弹起 */
	KEY_STA_press = 1,		/* 按键按下 */
};

extern uint8_t KeySta = KEY_STA_bounce;

/* 蜂鸣器动作 */
extern void BuzzerFunc(void);

/* 显示屏刷新 */
extern void DisplayFunc(void);

/* 点亮LED灯 */
extern void LEDFunc(void);

void main(void)
{
	while(1)
	{
		/* 按键按下时动作 */
		if (KEY_STA_press == KeySta)
		{
			/* 蜂鸣器响 */
			BuzzerFunc();
	
			/* 界面刷新 */
			DisplayFunc();
	
			/* 点亮LED灯 */
			LEDFunc();
		}
	}
}

    上面这种写法有问题么?实现起来没问题,也添加功能也很方便,但是有个问题,就是如果现在想把按键的功能封装起来放一个模块里,这时候按键触发的执行动作应该怎么处理?总不能把功能也封装进去,那以后每加一个功能就得改一次模块。所以这里就要用到观察者模式了。
    这里的按键按下,可以看作是一个事件,当触发这个事件时,需要执行蜂鸣器响、界面刷新、点亮LED灯等操作。其实就是当发生了按键按下的事件时,需要同步通知蜂鸣器、界面和LED同步动作。这个模式在嵌入式里其实就是一个回调函数的应用。下面我们来看下怎么实现。

/*************************按键模块的实现.c****************************/
/* 按键状态 */
enum emKeyState
{
	KEY_STA_bounce = 0,		/* 按键弹起 */
	KEY_STA_press = 1,		/* 按键按下 */
};

/* 观察者链表结构 */
struct tagKeyFunc
{
	void (*Func)(void);
	struct tagKeyFunc *Next;
};

struct tagKeyFunc *Last = NULL;

uint8_t KeySta = KEY_STA_bounce;

/* 回调函数注册 */
void KeyPress_AddFunc(void(*func)(void))
{
	struct tagKeyFunc *next = malloc(sizeof(struct tagKeyFunc));

	next->Func = func;
	next->Next = Last;

	Last = next;
}

/* 事件触发时执行注册的回调函数 */
void KeyEventFunc(void)
{
	struct tagKeyFunc *cur = Last;

	/* 按键按下时,执行所有注册的功能 */
	if (KEY_STA_press == KeySta)
	{
		for (; NULL != cur; cur = cur->Next)
		{
			cur->Func();
		}
	}
}
/********************************************************************/
/***************************应用.c*********************************/
extern void KeyPress_AddFunc(void(*func)(void));
extern void KeyEventFunc(void);

/* 蜂鸣器动作 */
extern void BuzzerFunc(void);

/* 显示屏刷新 */
extern void DisplayFunc(void);

/* 点亮LED灯 */
extern void LEDFunc(void);

void main(void)
{
	/* 按键触发功能注册 */
	KeyPress_AddFunc(BuzzerFunc);
	KeyPress_AddFunc(DisplayFunc);
	KeyPress_AddFunc(LEDFunc);

	while(1)
	{
		KeyEventFunc();
	}
}
/********************************************************************/

四、适用范围

    责任链,适用于不同操作对象各自有明确的职责划分。
    观察者,适用于需要遍历通知的场景。

五、优劣势

  • 优势
  1. 两者都有一个比较明显的优势,就是把触发事件和执行功能两者解耦开。
  2. 如果使用链式结构,可以很方便地进行动态添加和删除。
  • 劣势
  1. 功能执行的位置比较有局限性,不灵活,比如上面例子,所有功能只能在按键按下时才能执行,如果需要在弹起时执行则不能实现,需要另外增加一个弹起执行的回调,也就是功能执行的位置完全取决于事件开放的位置。
  2. 使用回调时,不容易发现无限递归的情况,即使用时有A回调B,B回调A这种无限调用的风险。
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
责任设计模式是一种软件设计模式,其中一个请求在若干个对象中流转,直到被处理。每个对象都有机会处理该请求,并决定是否将该请求传递给下一个对象。 下面是一个使用C语言实现责任设计模式的示例代码: ``` #include <stdio.h> #include <stdlib.h> // 声明责任抽象类 typedef struct Chain Chain; struct Chain { int level; Chain* next; void (*handleRequest)(Chain*, int); }; // 具体处理者1 typedef struct ConcreteHandler1 ConcreteHandler1; struct ConcreteHandler1 { Chain chain; }; // 具体处理者2 typedef struct ConcreteHandler2 ConcreteHandler2; struct ConcreteHandler2 { Chain chain; }; // 具体处理者3 typedef struct ConcreteHandler3 ConcreteHandler3; struct ConcreteHandler3 { Chain chain; }; // 具体处理者1的处理方法 void ConcreteHandler1_handleRequest(Chain* chain, int request) { if (chain->level >= 1) { printf("ConcreteHandler1 handled the request %d\n", request); } else { printf("ConcreteHandler1 passed the request %d\n", request); chain->next->handleRequest(chain->next, request); } } // 具体处理者2的处理方法 void ConcreteHandler2_handleRequest(Chain* chain, int request) { if (chain->level >= 2) { printf("ConcreteHandler2 handled the request %d\n", request); } else { printf("ConcreteHandler2 passed the request %d\n", request); chain->next->handleRequest(chain->next, request); } } // 具体处理者3的处理方法 void ConcreteHandler3_handleRequest(Chain* chain, int request) { if (chain->level >= 3) { printf("ConcreteHandler3 handled the request %d\n", request); } else { printf("ConcreteHandler3 passed the request %d\n", request); chain->next->handleRequest(chain->next, request); } } // 初始

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知识噬元兽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值