封装 —— 保护程序的隐私
, 不该让调用者知道的事,坚决不能暴露出来。
为什么要封装
-
隔离变化
程序的隐私通常是程序最容易变化的部分,比如内部数据结构、内部使用的函数和全局变量等,需要把这些代码封装起来,从而让它们变化不会影响系统其他部分
-
降低复杂度
接口最小化是软件设计基本原则之一,最小化接口最容易被理解和使用,所以封装内部细节,只保留用户需要的最小接口。
如何封装
-
隐藏数据结构
图方便直接访问数据结构的成员,容易造成模块之间紧密耦合,从而给以后的修改带来困难。
- 内部使用的数据结构,外部完全不会应用,定义在C文件中即可,不要放在头文件中
- 如果数据结构内外都要使用,则在头文件暴露数据结构
具体实现细节做法:
- 在头文件中声明该数据结构
- 在C文件中定义该数结构
- 提供操作该数据结构的函数。哪怕只是存取数据结构的成员,也要包装成相应的函数
-
隐藏内部函数
- 内部函数通常实现一些特定算法(如果通用,应该放到一个公共函数库里),而这些是接口使用者不需要知道的内部细节。
- 不隐藏内部函数函数名也会污染全局名字空间,可能造成重名
- 不隐藏内部函数容易诱导调用者绕过正规接口走捷径,造成代码不必要的耦合。
隐藏内部函数的做法:
- 在头文件中,只放最少的接口函数的声明
- 在C文件中,所有内部函数都加上static关键字限定在本文件使用
-
禁用全局变量
当把程序从单线程该为多线程,对并发的程序产生不利影响。更重的是直接使用全局变量,会造成调用者和实现者之间的耦合。
——— 学习摘录自《系统程序员成长计划》
隔离变化
像C++具有OOP语法糖的编程语言中可以直接使用虚函数/纯虚函数来隔离变化。
对于C语言没有这样的语法糖。
而C语言中也有隔离变化
的法宝 ---- 函数指针
。可以当作C语言的虚函数。
扫描二维码关注公众号,回复:
12911589 查看本文章

不止在OOP起大作用,在简单的程序中也可以很好的起到隔离变化的作用。
简单的隔离变化示例:检测风扇两种状态
刚开始的函数:
void Fand_Detect(void)
{
static uint16_t count = 0;
switch (fStatus)
{
case FAN_IS_RUN:
{
if (FAN_DETECT_INPUT() == FAN_STOP)
{
count++;
if (count >= FAN_IS_STOP_COUNT)
{
count = 0;
fStatus = FAN_IS_STOP;
SendErrCode(CCU_FAN_IS_STOP);
}
}
else
{
count = 0;
}
break;
}
case FAN_IS_STOP:
{
if (FAN_DETECT_INPUT() == FAN_RUN)
{
count++;
if (count >= FAN_IS_RUN_COUNT)
{
count = 0;
fStatus = FAN_IS_RUN;
SendErrCode(CCU_FAN_IS_RUN);
}
}
else
{
count = 0;
}
break;
}
}
}
可以看出上述程序两种状态下的检测代码是类似,变化的地方有if (FAN_DETECT_INPUT() == FAN_RUN)
,fStatus = FAN_IS_STOP;
SendErrCode(CCU_FAN_IS_STOP)
;
使用函数指针隔离变化
,修改程序如下:
static void _FanIsStopCallback(void)
{
fStatus = FAN_STOP_STATE;
printf("Fan Is Stop\r\n");
SendErrCode(CCU_FAN_IS_STOP); //send error code that fan is stop
}
static void _FanIsRunCallback(void)
{
fStatus = FAN_RUN_STATE;
printf("Fan Is Run\r\n");
SendRestoreCode(CCU_FAN_IS_RUN); //send error code that fan is run
}
static void _Fan_Detect(enum FanLevel level, uint16_t maxCount, void (*callback)(void))
{
static uint16_t count = 0;
if (FAN_DETECT_INPUT() == level)
{
count++;
if (count >= maxCount)
{
count = 0;
if (callback)
callback();
}
}
else
{
count = 0;
}
}
void Fand_Detect(void)
{
static uint16_t count = 0;
switch (fStatus)
{
case FAN_RUN_STATE:
{
_Fan_Detect(FAN_STOP, FAN_IS_STOP_COUNT, _FanIsStopCallback);
break;
}
case FAN_STOP_STATE:
{
_Fan_Detect(FAN_RUN, FAN_IS_RUN_COUNT, _FanIsRunCallback);
break;
}
}
}
避免了写两段类似的重复的程序。
OOPC方式重写fan detect
抽象fan:
struct AbstractFan {
uint16_t count;
uint8_t fStatus;
int (*detect)(struct AbstractFan *thiz, uint8_t *status);
void (*stop)(struct AbstractFan *thiz);
void (*run)(struct AbstractFan *thiz);
};
static inline void _AbFan_Detect(struct AbstractFan *thiz, uint8_t level, uint16_t maxCount)
{
uint8_t status;
if (thiz->detect)
thiz->detect(thiz, &status);
if (status == level)
{
if (thiz->count++ >= maxCount)
{
thiz->count = 0;
if (level== FAN_RUN)
{
if (thiz->run)
thiz->run(thiz);
thiz->fStatus = FAN_RUN_STATE;
}
else
{
if (thiz->stop)
thiz->stop(thiz);
thiz->fStatus = FAN_STOP_STATE;
}
}
}
else
{
thiz->count = 0;
}
}
static inline void AbFan_Detect(struct AbstractFan *thiz)
{
if (thiz == NULL)
return;
switch(thiz->fStatus)
{
case FAN_IS_RUN:
{
_AbFan_Detect(thiz, FAN_STOP, 40);
break;
}
case FAN_IS_STOP:
{
_AbFan_Detect(thiz, FAN_RUN, 10);
break;
}
}
}
static inline void AbFan_SetStatus(struct AbstractFan *thiz, uint8_t status)
{
if (thiz == NULL)
return;
thiz->fStatus = status;
}
fan类:
struct fan {
struct AbstractFan *fan;
}
//构建注入依赖的抽象接口
static inline void Fan_Init(struct fan *thiz, struct AbstractFan *f)
{
if (thiz == NULL || f == NULL)
return;
thiz->fan = f;
f->count = 0;
AbFan_SetStatus(f, FAN_IS_RUN);
}
static inline void Fan_Dectect(struct fan *thiz)
{
uint8_t status;
if (thiz == NULL)
return;
AbFan_Detect(thiz->fan);
}
实例化伪代码:
static void _detect(struct AbstractFan *thiz, uint8_t *status)
{
*status = (uint8_t )FAN_DETECT_INPUT();
}
static void _run(struct AbstractFan *thiz)
{
printf("Fan Is Run\r\n");
SendRestoreCode(CCU_FAN_IS_RUN);
}
static void _stop(struct AbstractFan *thiz)
{
printf("Fan Is Stop\r\n");
SendErrCode(CCU_FAN_IS_STOP);
}
static struct AbstractFan abFan = {
.detect = _detect,
.run = _run,
.stop = _stop,
};
static struct fan fan;
void main(void)
{
Hardward_Init();
Fan_Init(&fan, &abFan);
while (1)
{
Fan_Detect(&fan);
}
}
…emm…有点复杂…单片机上还是用函数指针当函数参数的方法隔离好