嵌入式C编程中的设计模式之一——单件模式和策略模式

一、介绍

关于设计模式,有很多软件工程师认为,设计模式都是高级编程中的事情,并且多少有点被玩烂了。在嵌入式C语言中,设计模式是非常过时且没有实用价值的东西。笔者认为,设计模式在嵌入式编程中其实还是有很多用武之地的。用好了可以在小小的单片机上很好地实现你的需求。

笔者的有关的设计模式的内容来源于《Head First 设计模式》。使用UML2.0建模,并在模型中体现设计模式,再用C代码进行实现。

UML2.0的标准可以参考这个链接:UML Specification
我用的硬件是基于嵌入式单片机或MCU的,比如常见的STM32F系列MCU。软件平台可以考虑KEIL,STM32CUBEIDE,SEGGER STUDIO都可以。

下面先从单件模式开始介绍,比较传统的平铺式和单件模式封装式的差别。然后在单件模式的基础上发展策略模式。

二、单件模式

在嵌入式系统中,往往需要对资源进行封装管理。例如,将一组LED灯封装成警报灯,将传感器封装为一个模块。这就会用到单件模式。
单件模式是指:

单件模式确保一个类只有一个实例,并提供一个全局访问点。——《Head First 设计模式》

就报警灯为例,一种常见的做法是设计leds.h和leds.c两个文件,在头文件中声明相关的函数,在源文件中实现这些函数。这两个文件的代码如下所示。

/* leds.h*/
#ifndef _LEDS_H_
#define _LEDS_H_

#include 
typedef enum Led_ColourKind_Def{
	LED_NONE,
	LED_YELLOW,
	LED_RED,
	LED_BOTH,
}Led_ColourKind;

void leds_init(void);
void leds_setColour(Led_ColourKind);
...

#endif
/*leds.c*/
#include "leds.h"
void leds_init(void);
void leds_setColour(Led_ColourKind);

void leds_init(void){
	;/*Code Implementation*/
}
void leds_setColour(Led_ColourKind Colour){
	;/*Code Implementation*/
}
...

这种写法在抛开做具体项目,只是做个简单的硬件测试的时候是没有问题的。但是如果要做项目,这种办法不能直观得体现UML建模。如果事先使用UML建模,你会得到一个如下所述的模型。
嵌入式C编程中的设计模式之一——单件模式和策略模式_第1张图片
就这个模型而言,前面的代码不能很直观地体现该模型,尽管从功能上可以实现。但是随着模型的成长,这种平铺式的代码就会显得比较乏力。这里给出单件模式的写法。

/*leds.h*/
#ifndef _LEDS_H_
#define _LEDS_H_

#include 
#include 
typedef enum Led_ColourKind_Def{
	LED_NONE,
	LED_YELLOW,
	LED_RED,
	LED_BOTH,
}Led_ColourKind;

typedef struct _Leds_TypeDef{
	void (*init)(void);
	void (*setColour)(Led_ColourKind);
	...
}Leds_TypeDef;

extern const Leds_TypeDef leds;

#endif
/*leds.c*/
#include "leds.h"

extern const Leds_TypeDef leds;

static void init			(void);
static void setColour		(Led_ColourKind);

const Leds_TypeDef leds = {
	.init			= 		init			,
	.setColour      = 		setColour   	,
};
/*省略init和setColour的实现,因为和前面的一样*/

void init(void){
	/*初始化代码*/
}

void setColour		(Led_ColourKind colour){
	/*设置颜色有关代码*/
}

这样设计,则整个系统中只有一个类,及Leds_TypeDef。这个类只有一个实例,就是leds。leds同时也是唯一的全局访问点。这样的设计就是单件模式的实现。应用举例,通过下面的代码就可以在main函数中改变灯的颜色为红色。

.../*其他的代码*/
void main(void){
	leds.init();
	leds.sesColour(LED_RED);
}
.../*其他的代码*/

这里要说明一点,leds对象被定义为const类型的原因是,在大部分MCU中,ROM都是比RAM要大的。所以尽可能把大的数据定义成const类型,这样编译后就可以被放到ROM上。

三、 策略模式

根据需求变更,为了迎合“多用组合,少用继承”和“针对接口编程,不针对实现编程”的面向对象原则,我们的模型发展成下面的样子。
嵌入式C编程中的设计模式之一——单件模式和策略模式_第2张图片
策略模式的定义为:

策略模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。 ——《Head First 设计模式》

虽然在Java中有interface关键字,也有很好的机制去实现各种设计模式以及各种UML语法,但是在C语言中,主要还是靠C语言本身的语法去部分地实现UML概念,并以C语言特色的方法去实现有关的设计模式。策略模式要求C语言中实现interface。

嵌入式C编程中的设计模式之一——单件模式和策略模式_第3张图片
图片来源:OMG® Unified Modeling Language® (OMG UML®) v2.5.1
这里给出interface在UML2.0中的词法定义

An Interface is a kind of Classifier that represents a declaration of a set of public Features and obligations that together constitute a coherent service. An Interface specifies a contract; any instance of a Classifier that realizes the Interface shall fulfill that contract.
——OMG® Unified Modeling Language® (OMG UML®) v2.5.1

参考interface的词法定义和上面的用UML描述的interface在该语言中的定义,可以理解为只定义了标识而不去实现它,它的实现放在它的类或者数据结构中做。如果是个C++类,那么就相当于是虚基类、虚函数。放到C语言中,可以是个结构体指针,或者是函数指针。这样,概括来说,包含了这个接口的类、结构体或其他标识,

  1. 谁包含了这个接口,谁就要实现它。
  2. 可以有多个实现,接口可以被动态设置为其中某个实现。

本例中,我们的Leds_TypeDef包含了一个接口叫IEmergency,这个接口有一个行为叫blink(),有三个实现分别是_Emergency_Low、_Emergency_Normal和_Emergency_High。笔者考虑使用一个间接的函指针blink_emergency和函数列表blink_emergency_list[]来实现。具体代码如下所示。

/*leds.h*/
#ifndef _LEDS_H_
#define _LEDS_H_

#include 
#include 
typedef enum Led_ColourKind_Def{
	LED_NONE,
	LED_YELLOW,
	LED_RED,
	LED_BOTH,
}Led_ColourKind;
typedef enum Led_EmergencyKind_Def{
	LED_EMERGENCY_HIGH,
	LED_NORMAL,
	LED_LOW,
}Led_EmergencyKind;
typedef struct _Leds_TypeDef{
	void (*init)(void);
	void (*setColour)(Led_ColourKind);
	void (*blink)(bool);
	void (*set_Emergency)(Led_EmergencyKind);
	...
}Leds_TypeDef;

extern const Leds_TypeDef leds;

#endif
/*leds.c*/
#include "leds.h"

extern const Leds_TypeDef leds;

static void init			(void);
static void setColour		(Led_ColourKind);
static void blink			(bool);
static void set_Emergency	(Led_EmergencyKind);

static void _blink_emergency_high	(void);
static void _blink_emergency_normal	(void);
static void _blink_emergency_low	(void);
static void (*_blink_emergency_list[])(void) = {
	_blink_emergency_high	,
	_blink_emergency_normal	,
	_blink_emergency_low	,
}
static void (*blink_emergency)(void) = _blink_emergency_low;
const Leds_TypeDef leds = {
	.init			= 		init			,
	.setColour      = 		setColour   	,
	.blink          = 		blink     	 	,
	.set_Emergency	= 		set_Emergency	,
};
/*省略init和setColour的实现,因为和前面的一样*/

void blink(bool state){
	if(state){
		blink_emergency();
	}
	else{
		/*关闭闪烁*/
	}
}

void set_Emergency(Led_EmergencyKind emg){
	blink_emergency = _blink_emergency_list[emg];
}

在外部通过以下的调用方法来改变接口的实现,并执行接口的行为改成高紧急度,并启动闪烁。

void main(void){
	/*其他的代码*/
	leds.set_Emergency(LED_EMERGENCY_HIGH);
	leds.blink(true);
	/*其他的代码*/
}	

四、结论

本文中笔者使用结构体、函数指针和函数列表为实现手段,以UML2.0为建模语言为建模方法,以单件模式和策略模式为例实现了动态改变leds的接口行为和执行。可以看出,

  1. 在嵌入式中使用设计模式,并用UML建模是有意义的。
  2. 在嵌入式C语言下UML模型可以被实现
  3. 不同的建模方法可能都能用一样的实现手段,同一种建模方法也可以用不同的实现手段。
  4. 设计模式一般对应的模型都是没有较大的争议的。

因此,在嵌入式下使用设计模式是可行并且有效的。只是在使用的时候,要考虑在当前的编译环境下语法的特点;要摆脱思维定式,在理解了UML的词法和语法后用恰当的C语言表达。就可以在实际项目中发挥设计模式的优势,做出高效、高可维护性的软件编码设计。

你可能感兴趣的:(嵌入式C语言,UML与设计模式,c语言,arm,mcu,单片机)