【C语言】如何优雅地进行嵌入式C开发?(万字总结)

发布于:2022-11-28 ⋅ 阅读:(501) ⋅ 点赞:(0)

相信很多做嵌入式的朋友,做过一些项目后,会有一个瓶颈期,就是明明熟悉了C语言的所有语法,但拿到一个项目不知道如何下手,或者明明知道一个项目该怎么做,去设计代码时,却总会遇到一些逻辑上自相矛盾的地方。这时想要提升自己的代码能力,却又不知道学什么,心想C语言不就那么点东西吗?

如题:什么是好的嵌入式C代码? 其实语言只是一种工具,重要的是思想!在本文中,我将从代码结构和代码逻辑上来阐述。代码结构上将阐述分层设计的要点;逻辑上将从状态机、查表法、信号槽设计模式(23种)面向对象(封装、继承、多态)防御性编程等思想,来解决我们经常遇到的逻辑冲突、减少重复代码、增加代码的观赏性和可读性。

如果当你听过很多大道理,依然写不好C代码,就来读读这篇文章吧!!!

一、照妖镜:分层设计

分层设计是代码的照妖镜,我们通常可以通过它一眼看出一份代码的好坏。

嵌入式程序主要分为:物理层、功能接口层、应用层。
先看一个典型的分层设计模型:

  • PHY(物理层)
    • UART
    • ADC
    • GPIO
    • ……
  • USER(功能接口层)
    • MODBUS
    • TEMP
    • LED
    • ……
  • APP(应用层)
    • CTRL
    • ……

通常一个项目涉及多人开发,每人负责不同层级。层级之间面临着前期接口的不透明和频繁更改,合并代码时难免存在冲突;代码合并后,在底层更改了接口名,必须人为地通知上层去调用,随着接口增多,合并代码的压力也越大。那么我们如何无视这些未知的冲突,进行快乐的编程呢?也就是说我们可以根本不关心对方接口的名字和实现方式,我们只需要在前期约定格式(对于一个函数接口,所谓的格式即:参数和返回值),各自去实现和调用即可。

1、使用 Static 关键字

来将函数变量限制在本文件内,避免命名冲突

2、使用回调函数

①、回调函数的作用

  • 在.c文件之间优雅的传递数据,而不用全局变量共享数据
    拿STM32为例,像外部中断信号、串口、ADC、按键等输入信号,他们的输入时间节点是未知的,我们不想轮询,芯片 模组厂商一般会采用中断来通知应用层,我们只需要在应用层编写回调函数(底层调用了一个没有实现的和回调函数同类 型的处理函数A),然后调用底层的注册函数把回调函数注册到底层(底层的注册函数就是把回调函数指针作为入参,A就可以指向我们在应用层编写的回调函数了),比如我们熟知的串口中断回调函数
  • 封装代码的利器
    在上一条中可以看出:我们不需要告诉底层处理函数的名字,然后让底层调用我们去处理数据;也不需要知道底层函数的名字,然后调用它获取数据。我们只要在应用层编写数据处理回调函数,然后把函数指针注册到底层(因为使用了指针,回调函数的名字可以随便取,但入参和返回值类型的格式要符合要求,否则没法和底层关联),底层数据到来时就会自动处理,这之间没有对底层做任何操作,成功解耦。

②、回调函数的应用

我们在编写驱动时,可以模仿这些芯片厂商,比如编写一个ADC驱动:

ADC.h
#ifndef _APP_ADC_H_
#define _APP_ADC_H_

typedef enum{
  ADC1,
  ADC2,
}ADC_ID_TYPEDEF;

typedef void (*adcGatherCallBack)(ADC_ID_TYPEDEF adc, uint32_t value);
void adc_callback_register(adcGatherCallBack adcCB);

#endif

注意:函数指针和注册函数都是在底层编写

ADC.c
static adcGatherCallBack adcGatherCB; 

void adc_callback_register(adcGatherCallBack adcCB)
{
    adcGatherCB = adcCB;
}

void adc_task(void)
{
    while (1) {
        uint32_t adc_reading = 0;
        for (int i = 0; i < NO_OF_SAMPLES; i++) {
            adc_reading += adc1_get_raw((adc1_channel_t)channel);
        }
        adc_reading /= NO_OF_SAMPLES;
        uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_chars);

        adc_channel = ADC1;
        adcGatherCB(adc_channel,voltage);//底层当成已有函数使用,在应用层可灵活定义回调函数实现具体操作

        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

注意:adcGatherCB(adc_channel,voltage);是一个adcGatherCallBack 类型的指针,不需要去实现,只需要将ADC通道和值传入,待应用层将回调函数注册进来,这个指针就指向回调函数,实现回调函数的实现。

APP.c(应用层)
/*ADC回调函数*/
static void adcCallBack(ADC_ID_TYPEDEF adc_channel,uint32_t value)
{
    //电压值处理
    switch(adc_channel)
    {
        case ADC1:
            //显示电池电压
            printf("Channel: ADC%d\tVoltage: %dmV\n", adc_channel, value);
        break;

        case ADC2:
            //显示VBUS电压
        break;

        default:
        break;
    }
}

/*注册所有回调*/
static void app_callback_register()
{
    adc_callback_register(adcCallBack);
}

/*主逻辑初始化*/
void app_init(void)
{
    app_callback_register();
}

二、三板斧:常用机制

学会这三板斧,已经能解决很多常见问题,当大佬看到你代码时,会想:嗯,这小伙子还有点东西,但不多,是个可塑之才。

1、查表法

typedef struct
{
    char *help_list;
    uint8 permission;                       //操作权限。0:支持读写    1:只读
    gpio_port gpio_port_idx;
    uint8 pin_index;
    Ifx_P *port;
    uint8 default_state;
}GPIO_LIST;

GPIO_LIST gpio_listor[] =
{//   help_list             permission   port_idx   pin     Ifx_P *   default_state
    {"MCU_LED_EN(RW)\t",        GPIO_RW,    port13,   0,   &MODULE_P13     ,0       },    //MCU_RUN_LED
    {"MCU_CAN0_STB(RW)",        GPIO_RW,    port01,   4,   &MODULE_P01     ,0       },    //CAN0_STB
    {"MCU_CAN1_STB(RW)",        GPIO_RW,    port01,   6,   &MODULE_P01     ,0       },    //CAN1_STB
    {"MCU_CAN2_STB(RW)",        GPIO_RW,    port01,   7,   &MODULE_P01     ,0       },    //CAN2_STB
    {"MCU_CAN3_STB(RW)",        GPIO_RW,    port00,   9,   &MODULE_P00     ,0       },    //CAN3_STB
    {"MCU_CAN4_STB(RW)",        GPIO_RW,    port34,   3,   &MODULE_P34     ,0       },    //CAN4_STB
    {"MCU_ETH_EN(RW)\t",        GPIO_RW,    port12,   0,   &MODULE_P12     ,0       },    //ETH_PHY_EN
    {"MCU_PHY_RST(RW)\t",       GPIO_RW,    port11,   14,  &MODULE_P11     ,0       },    //ETH_PHY_RST
    {"J2A_9296_EN1(RW)",        GPIO_RW,    port22,   7,   &MODULE_P22     ,1       },    //CAM_EN1
    {"J2A_9296_EN2(RW)",        GPIO_RW,    port22,   8,   &MODULE_P22     ,1       },    //CAM_EN2
    {"J2A_9296_EN3(RW)",        GPIO_RW,    port22,   9,   &MODULE_P22     ,1       },    //CAM_EN3
    {"CAME_OFF(RW)\t",          GPIO_RW,    port33,   2,   &MODULE_P33     ,0       },    //CAM_POW
};
#define GPIO_MAX_NUM    sizeof(gpio_listor)/sizeof(GPIO_LIST)

void init_gpio(void)
{
    for(uint8 i = 0; i < GPIO_MAX_NUM; i++)
    {
        if(gpio_listor[i].permission == GPIO_RW)
        {
            IfxPort_setPinMode(gpio_listor[i].port, gpio_listor[i].pin_index, IfxPort_Mode_outputPushPullGeneral);
            IfxPort_setPinState(gpio_listor[i].port, gpio_listor[i].pin_index, gpio_listor[i].default_state);
        }
    }
}

2、状态机

不是一看到switch/case就是状态机,要有状态的切换。
假如有如下状态关系:
在这里插入图片描述

代码参考网上:

//定义枚举类型STATE_t表示状态机状态:
typedef enum{
	STATE1 = 0,
	STATE2,
	STATE3,
	STATE4,
}STATE_t; 
//定义ACTION_MAP_t结构体类型,表示状态机状态属性:
typedef void (*STATE_ACTION)(void);	
typedef struct ACTION_MAP
{
	STATE_t 		stStateID;
	STATE_ACTION 	EnterAct;	
	STATE_ACTION 	RunningAct;	
	STATE_ACTION 	ExitAct;
}ACTION_MAP_t;
//建立“动作”表:
void state1_entry(void)
{
	printf("state1_entry\n");
}
void state1_do(void)
{
	printf("state1_do\n");
}
void state1_exit(void)
{
	printf("state1_exit\n");
}

void state2_entry(void)
{
	printf("state2_entry\n");
}
void state2_do(void)
{
	printf("state2_do\n");
}
void state2_exit(void)
{
	printf("state1_exit\n");
}

void state3_entry(void)
{
	printf("state3_entry\n");
}
void state3_do(void)
{
	printf("state3_do\n");
}
void state3_exit(void)
{
	printf("state3_exit\n");
}

void state4_entry(void)
{
	printf("state4_entry\n");
}
void state4_do(void)
{
	printf("state4_do\n");
}
void state4_exit(void)
{
	printf("state4_exit\n");
}
ACTION_MAP_t actionMap[] = 
{
	{STATE1,	state1_entry,	state1_do,	state1_exit},
	{STATE2,	state2_entry,	state2_do,	state2_exit},
	{STATE3,	state3_entry,	state3_do,	state3_exit},
	{STATE4,	state4_entry,	state4_do,	state4_exit},
};
//定义枚举类型EVENT_t表示事件:
typedef enum
{
	EVENT1 = 0,
	EVENT2,
	EVENT3,
	EVENT4,
	EVENT5,
	
	EVENT_MAP_END
}EVENT_t;
//定义EVENT_MAP_t结构体类型,表示事件表属性:
typedef struct EVENT_MAP
{
	EVENT_t	stEventID;
	STATE_t stCurState;
	STATE_t stNextState;
}EVENT_MAP_t;
//根据状态机流程图建立事件表:
EVENT_MAP_t eventMap[] = 
{
	{EVENT1,	STATE1,	STATE2},
	{EVENT2,	STATE2,	STATE3},	
	{EVENT3,	STATE3,	STATE4},
	{EVENT4,	STATE4,	STATE1},
	{EVENT5,	STATE1,	STATE4},

	{EVENT_MAP_END,	0,	0},		
};
//定义状态机结构体类型:
typedef struct FSM
{
	STATE_t stCurState;
	STATE_t stNextState;
	ACTION_MAP_t *pActionMap;
	EVENT_MAP_t *pEventMap;
}FSM_t;
//定义状态机注册函数:
void fsm_init(FSM_t* pFsm,EVENT_MAP_t* pEventMap,ACTION_MAP_t *pActionMap)
{
	pFsm->stCurState = 0;
	pFsm->stNextState = EVENT_MAP_END;
	pFsm->pEventMap = pEventMap;
	pFsm->pActionMap = pActionMap;
}
//定义状态机转换函数:
void fsm_state_transfer(FSM_t* pFsm, EVENT_t stEventID)
{
	uint8_t i = 0;
	
	for(i=0; pFsm->pEventMap[i].stEventID<EVENT_MAP_END; i++)
	{
		if((stEventID == pFsm->pEventMap[i].stEventID) 
			&& (pFsm->stCurState == pFsm->pEventMap[i].stCurState))
		{
			pFsm->stNextState = pFsm->pEventMap[i].stNextState;
			
			return;
		}
	}	
}
//定义动作执行函数:
void action_perfrom(FSM_t* pFsm)
{
	if(EVENT_MAP_END != pFsm->stNextState)
	{
		pFsm->pActionMap[pFsm->stCurState].ExitAct();
		pFsm->pActionMap[pFsm->stNextState].EnterAct();
		
		pFsm->stCurState = pFsm->stNextState;
		pFsm->stNextState = EVENT_MAP_END;
	}
	else
	{
		pFsm->pActionMap[pFsm->stCurState].RunningAct();
	}
}
//测试
int main(void)
{
	int i = 0;		
	FSM_t stFsmWeather;	//定义状态机
	
	fsm_init(&stFsmWeather,eventMap,actionMap);	//注册状态机
	
	while(1)
	{
		usleep(10);
		printf("i = %d\n",i++);
		
		action_perfrom(&stFsmWeather);
		
		//利用i产生EVENT1~EVENT5
		if(0 == (i%11))
		{
			fsm_state_transfer(&stFsmWeather,EVENT1);
		}
		
		if(0 == (i%31))
		{
			fsm_state_transfer(&stFsmWeather,EVENT2);
		}
		
		if(0 == (i%74))
		{
			fsm_state_transfer(&stFsmWeather,EVENT3);
		}
		
		if(0 == (i%13))
		{
			fsm_state_transfer(&stFsmWeather,EVENT4);
		}	
		
		if(0 == (i%19))
		{
			fsm_state_transfer(&stFsmWeather,EVENT5);
		}
	}
	
	return 0;
}

3、信号槽

信号槽的概念来自QT,我们可以用C语言模拟信号槽的机制。

代码参考网上:

#ifndef _SIMPLE_SIGNAL_SOLTS_H_
#define _SIMPLE_SIGNAL_SOLTS_H_
#include "string.h"

typedef void (*SIMPLE_SIGNAL)(void *signal, void *pArg);
typedef void (*SIMPLE_SOLTS) (void *pArg);

#define SIMPLE_SOLTS_T(FuncName)   void(FuncName)(void *pArg)

#define SIMPLE_EMIT(signal, arg)  if (signal != NULL)signal(&signal, arg)

#define SIMPLE_SIGNAL_SOLTS_MAX_SOLTS       10      //一个信号最多连接槽的数量
#define SIMPLE_SIGNAL_SOLTS_MAX_SIGNAL      10      //信号最多数量


ErrorStatus SimpleSignalSolts_Connect(SIMPLE_SIGNAL *signal, SIMPLE_SOLTS solts);

#endif
#include "SimpleSignalSolts.h"
#include <string.h>

//信号结构
typedef struct
{
    void            *signleID;    //信号的指针的指针,保存信号的指针,根据指针的地址确定是否是唯一
    SIMPLE_SOLTS    soltsTable[SIMPLE_SIGNAL_SOLTS_MAX_SOLTS];
    uint8_t         soltsCount;
}SIMPLE_SIGNAL_T;
//信号表结构
typedef struct
{
    SIMPLE_SIGNAL_T signalTable[SIMPLE_SIGNAL_SOLTS_MAX_SIGNAL];
    uint8_t         signalCount;
}SIMPLE_SIGNAL_SOLTS_HANDLE_T;

SIMPLE_SIGNAL_SOLTS_HANDLE_T handle = 
{
    .signalCount = 0,
};

static void SimpleSignalSolts_Signal(void *signal, void *pArg)
{
    uint8_t i, j;
    SIMPLE_SOLTS solts;
    for (i = 0; i < handle.signalCount; i++) //查找是否是同一个信号
    {
        if (handle.signalTable[i].signleID == signal)  //这里注意,signleID保存的是指针的地址,
        {
            for (j = 0; j < handle.signalTable[i].soltsCount; j++)
            {
                 solts = handle.signalTable[i].soltsTable[j];
                 if (solts != NULL)
                 {
                     solts(pArg);
                 }
            }
        }
    }
}

/******************************************************************
  * @函数说明:   连接信号与槽
  * @输入参数:   SIMPLE_SIGNAL *singnal 信号的指针(指针的指针) 
                  SIMPLE_SOLTS solts     槽
  * @输出参数:   无
  * @返回参数:   ErrorStatus 
******************************************************************/
ErrorStatus SimpleSignalSolts_Connect(SIMPLE_SIGNAL *signal, SIMPLE_SOLTS solts)
{
    if (signal == NULL || solts == NULL)    //查空
    {
        return ERROR;
    }

    uint8_t i;

    if (handle.signalCount > SIMPLE_SIGNAL_SOLTS_MAX_SIGNAL)    //错误
    {
        handle.signalCount = 0;
    }

    for (i = 0; i < handle.signalCount; i++) //查找是否是同一个信号
    {
        if (handle.signalTable[i].signleID == signal)  //这里注意,signleID保存的是指针的地址,
        {
            if (handle.signalTable[i].soltsCount > SIMPLE_SIGNAL_SOLTS_MAX_SOLTS)
            {
                handle.signalTable[i].soltsCount = 0;
            }

            if (handle.signalTable[i].soltsCount == SIMPLE_SIGNAL_SOLTS_MAX_SOLTS) //满了
            {
                return ERROR;
            }
            else
            {
                handle.signalTable[i].soltsTable[handle.signalTable[i].soltsCount++] = solts; //保存槽
                return SUCCESS; //结束
            }
        }
    }

    if (handle.signalCount == SIMPLE_SIGNAL_SOLTS_MAX_SIGNAL)   //满了
    {
        return ERROR;
    }
    else
    {
        handle.signalTable[handle.signalCount].signleID = signal; //保存新的信号
        handle.signalTable[handle.signalCount].soltsTable[0] = solts; //保存槽
        handle.signalTable[handle.signalCount++].soltsCount = 1;
        *signal = SimpleSignalSolts_Signal;
        return SUCCESS;
    }
}
/******************************************************************
  * @函数说明:   断开信号槽
  * @输入参数:   SIMPLE_SIGNAL *singnal 信号的指针(指针的指针) 
                  SIMPLE_SOLTS solts     槽
  * @输出参数:   无
  * @返回参数:   ErrorStatus 
******************************************************************/
ErrorStatus SimpleSignalSolts_Disconnect(SIMPLE_SIGNAL *signal, SIMPLE_SOLTS solts)
{
    if (signal == NULL || solts == NULL || handle.signalCount == 0)    //查空
    {
        return ERROR;
    }
    uint8_t i, j;

    for (i = 0; i < handle.signalCount; i++) //查找是否是同一个信号
    {
        if (handle.signalTable[i].signleID == signal)  //这里注意,signleID保存的是指针的地址,
        {
            for (j = 0; j < handle.signalTable[i].soltsCount; j++)
            {
                 if (handle.signalTable[i].soltsTable[j] == solts)  //找到槽  移除
                 {
                     memcpy(&handle.signalTable[i].soltsTable[j], &handle.signalTable[i].soltsTable[j +1], 
                                (handle.signalTable[i].soltsCount - j - 1) * sizeof(SIMPLE_SOLTS));
                     handle.signalTable[i].soltsCount--;
                 }
            }
            if (handle.signalTable[i].soltsCount == 0)  //此信号没有连接槽了
            {   
                memcpy(&handle.signalTable[i], &handle.signalTable[i + 1], (handle.signalCount - i - 1) * sizeof(SIMPLE_SIGNAL_T));
                handle.signalCount--;
            }
            return SUCCESS;
        }
    }
    return ERROR;
}

三:十八般武艺:设计模式

设计模式

1995年,GOF(Gang of Four) 合著《设计模式》一书。通过书名知道,这本书是写给面向对象语言的,书中以C++作为示例程序,但它的思想不局限于某种语言。虽然过去了27年,新的语言、特性频繁出现,设计模式本身也发生了变化,但对于C这样一个缺少框架、第三方库的语言来讲,设计模式的思想简直是雪中送炭。

设计模式,其实可以理解为:一套可以被反复套用的软件架构。 设计模式对函数指针的使用层出不穷,因为函数指针相当于把函数作为变量操作,我们可以对函数做一切我们对变量做的事,熟练使用函数指针,就可以弄懂设计模式的大半。

设计模式的六大原则

  • Single Responsibility Principle:单一职责原则
  • Open Closed Principle:开闭原则
  • Liskov Substitution Principle:里氏替换原则
  • Law of Demeter:迪米特法则
  • Interface Segregation Principle:接口隔离原则
  • List item Dependence Inversion Principle:依赖倒置原则

把这六个原则的首字母联合起来(两个 L 算做一个)就是 SOLID (solid,稳定的),其代表的含义就是这六个原则结合使用的好处:建立稳定、灵活、健壮的设计。

23种经典设计模式

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

+++创建型模式+++

1、单例模式

单例模式,顾名思义,当重复使用同一个对象(结构体)时,程序中只允许存在一个对象(结构体)的实例,这样能避免反复申请内存,减少系统的开销。
例如员工找老板签署文件,需要反复访问老板这个同一对象:

#include <stdio.h>
//定义boss的行为
typedef struct BOSS  
{  
    void (*vfunc)();  
}s_boss;
 
void sign(char * str)
{
  printf("老板给%s签署文件\n",str);
}
//封装boss的创建,统一访问
void* get_boss()  
{  
    static s_boss * boss = NULL;  
   //如果系统已经存在对象,直接返回对象指针
   if(NULL != boss)  
       return boss;  
   //第一次访问时创建对象
   boss = (s_boss *)malloc(sizeof(s_boss));
   //初始化对象行为
   boss->vfunc = sign;  
   return (void*)boss;  
}
void main()
{
  //timo找老板签文件
  s_boss * boss;
  boss = get_boss();
  boss->vfunc("timo");
 
  //gg找老板签文件
  boss = get_boss();
  boss->vfunc("gg");
}

2、工厂模式(2种)

①简单工厂模式,目的在于实现业务和细节的分离。
通俗得讲,不同工厂都可以实现我的各种业务,并且这些业务都是一样的,只是实现的细节不一样,我可以选择不同的工厂去实现,具体怎么实现的业务层不需要知道,只要选择了一个工厂,我就可以调用我想调用的业务。虽然简单工厂模式不完全符合设计模式的原则,但实际使用的最多。

void login_website(char *str)
{
	printf("欢迎登录:%s!\n",str);
}
void enter_jlc()
{
	printf("进入嘉立创旗舰店\n");
}
void bug_jlc_capacity(char *str)
{
	printf("购买嘉立创电容:%s\n",str);
}
void enter_jdb()
{
	printf("进入捷多邦旗舰店\n");
}
void bug_jdb_capacity(char *str)
{
	printf("购买捷多邦电容:%s\n",str);
}

typedef struct shop_interface
{
  void (*enter)();/*进入商城*/
  void (*buy)(const char *str);/*购买物料*/
}SHOP_INSTERFACE,*pSHOP_INSTERFACE;


/*嘉立创商城*/
struct jlc
{
  SHOP_INSTERFACE jlc_interface;
  /*可扩展其他私有属性*/
}
/*捷多邦商城*/
struct jdb
{
  SHOP_INSTERFACE jdb_interface;
  /*可扩展其他私有属性*/
}

pSHOP_INSTERFACE factory(const char *str)
{
  if("jlc" == str)
  {
    struct jlc *jlc_shop = (struct jlc*)malloc(sizeof(struct jlc));
    /*实例化接口*/
    ((pSHOP_INSTERFACE)jlc_shop)->enter = enter_jlc;
    ((pSHOP_INSTERFACE)jlc_shop)->buy = bug_jlc_capacity;
    return (pSHOP_INSTERFACE)jlc_shop;
  }
  else if("jdb" == str)
  {
    struct jdb *jdb_shop = (struct jdb*)malloc(sizeof(struct jdb));
    /*实例化接口*/
    ((SHOP_INSTERFACE*)jdb_shop)->enter = enter_jdb;
    ((SHOP_INSTERFACE*)jdb_shop)->buy = bug_jdb_capacity;
    return (pSHOP_INSTERFACE)jdb_shop;
  }
}

int main(void) {
    
    pSHOP_INSTERFACE shop;
    
    /* 登录淘宝网 */
    login_website("www.taobao.com");
    /*选择嘉立创商店*/
    shop = factory("jlc");
    /*进入嘉立创旗舰店*/
    shop->enter();
    /*购买嘉立创的0805电容*/
    shop->buy("0805");
    
    shop = factory("jdb");
    /*进入捷多邦旗舰店*/
    shop->enter();
    /*购买捷多邦的0603电容*/
    shop->buy("0603");
    
	return 0;
}

②工厂方法模式,只不过是优化了简单工厂模式中if esle的形式,不赘述
③抽象​工厂模式,可以理解为工厂的工厂,提供统一的接口让用户根据不同的需求创建出不同的工厂。

首先定义出不同类型的家具以供生产:

void modern_chair(void)
{
	printf("A modern chair\r\n");
}

void modern_dest(void)
{
    printf("A modern desk\r\n");
}

void victorian_chair(void)
{
    printf("A victorian chair\r\n");
}
void victorian_dest(void)
{
    printf("A victorian desk\r\n");
}

其次定义出工厂要制作的家具:


typedef struct
{
    void (*chair)(void);
}Chair_t;
 
typedef struct
{
    void (*desk)(void);
}Desk_t;

设计出工厂的具体生产内容:

Chair_t* make_modern_chair(void)
{
    Chair_t* pChair = (Chair_t)malloc(sizeof(Chair_t));
    assert(pChair != NULL);
    pChair->chair = modern_chair;
    return pChair;
}

Chair_t* make_victorian_chair(void)
{
    Chair_t* pChair = (Chair_t)malloc(sizeof(Chair_t));
    assert(pChair != NULL);
    pChair->chair = victorian_chair;
    return pChair;
}

Desk_t* make_modern_desk(void)
{
    Desk_t* pDesk = (Dest_t)malloc(sizeof(Dest_t));
    assert(pDesk != NULL);
    pDesk->desk = modern_desk;
    return pDesk;
}

Desk_t* make_victorian_desk(void)
{
    Desk_t* pDesk = (Dest_t)malloc(sizeof(Dest_t));
    assert(pDesk != NULL);
    pDesk->desk = victorian_desk;
    return pDesk;
}

接下来将工厂定义出来:

typedef struct
{
	Chair_t (*make_chair)(void);
    Dest_t (*make_desk)(void);
}Furniture_factory;

根据用户需求创建不同工厂:

Furniture_factory* Creat_furniture_factory(char* type)
{
	Furniture_factory* pFurniture_factory = (Furniture_factory*)malloc(sizeof(Furniture_factory));
    assert(pFurniture_factory != NULL)
    if(memcmp(type, "modern") == 0)//工厂类型
    {
        pFurniture_factory->make_chair = make_modern_chair;//注册方法
        pFurniture_factory->make_desk = make_modern_desk;
    }
    else if(memcmp(type, "victorian") == 0)
    {
        pFurniture_factory->make_chair = make_victorian_chair;
        pFurniture_factory->make_desk = make_victorian_desk;
    }
    else
    {
        printf("type is invalid\r\n");
    }
}

3、建造者模式

建造者模式,主要针对复杂对象,它们拥有多个组成部分,产品的最终形态都是一致的,但是具体组成的每个对象可能是不相同的,建造者模式将复杂对象的表示(抽象)与实现(具象)分离。
如汽车包括车轮、方向盘、发送机等各种部件。而对于大多数用户而言,无须知道这些部件的装配细节,也几乎不会使用单独某个部件,而是使用一辆完整的汽车,可以通过建造者模式对其进行设计与描述,建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
例如使用程序画一个小人,要求有头、身体、手、脚:

#include <stdio.h>
//定义小人构建的抽象接口
typedef struct persion_s  
{  
    void (*build_head)();
    void (*build_body)(); 
    void (*build_hand)(); 
    void (*build_foot)(); 
    void (*build_persion)(struct persion_s* p); 
}persion_t;
 
//定义瘦小人构建的接口
void draw_thin_head(){
...
}
void draw_thin_body(){
...
}
void draw_thin_hand(){
...
}
void draw_thin_foot(){
...
}
 
//定义胖小人构建的接口
void draw_fat_head(){
...
}
void draw_fat_body(){
...
}
void draw_fat_hand(){
...
}
void draw_fat_foot(){
...
}
void build_persion(struct persion_s* p){
	p->build_head();
	p->build_body();
	p->build_hand();
	p->build_foot();
}
//主程序
void main()
{
  //定义构建小人抽象接口
  persion_t* people;
  //利用胖小人或者是瘦小人的具体接口来初始化抽象接口
  people->build_head = draw_thin_head;
  people->build_body = draw_fat_body;
  ...
  //调用抽象接口中的构建小人方法
  people->build_persion(people);
}

【注】工厂模式和创建者模式的区别
创建者模式抽象的是复杂对象的组成,所有子对象的实现都可以具象,属于买零件自己回来组装;工厂模式抽象的是业务流程,不同工厂使用自己的一套子业务,属于直接把机器委托给一个喜欢的工厂加工,用什么零件随他便。

4、原型模式

原型模式,可以复制一个个与原型一样的对象。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define VAR(name, id)   name##id

typedef struct Paper
{
    char* teacher_name;
    int class;
    char* question;
    struct Paper* (*copy)(struct Paper* pPaper);
} paper_t;


/*原型的复制接口*/
paper_t* paper_copy(paper_t* pPaper)
{
    paper_t* copy = (paper_t*)malloc(sizeof(paper_t));
    memcpy(copy, pPaper, sizeof(paper_t));
    return copy;
}


paper_t* clone(paper_t* pPaper)
{
    return pPaper->copy(pPaper);
}

void main(void)
{
    /*定义原型*/
	paper_t origin_paper = {"Li", 5, "1 + 1 = ?", paper_copy};
    
    /*复制10份试卷*/
    for(int i = 1; i <= 10; i++)
    {
        paper_t* VAR(paper, i) = clone(&origin_paper);
        printf("paper_%d => name: %s, class: %d, question: %s\r\n",\
                i, VAR(paper, i)->teacher_name, VAR(paper, i)->class, VAR(paper, i)->question);
    }
}

输出:

paper_1 => name: Li, class: 5, question: 1 + 1 = ?
paper_2 => name: Li, class: 5, question: 1 + 1 = ?
paper_3 => name: Li, class: 5, question: 1 + 1 = ?
paper_4 => name: Li, class: 5, question: 1 + 1 = ?
paper_5 => name: Li, class: 5, question: 1 + 1 = ?
paper_6 => name: Li, class: 5, question: 1 + 1 = ?
paper_7 => name: Li, class: 5, question: 1 + 1 = ?
paper_8 => name: Li, class: 5, question: 1 + 1 = ?
paper_9 => name: Li, class: 5, question: 1 + 1 = ?
paper_10 => name: Li, class: 5, question: 1 + 1 = ?

+++结构型模式+++

1、适配器模式

适配器模式(Adapter Pattern),是作为两个不兼容的接口之间的桥梁。例如,我们的嵌入式单片机需要兼容两种通信模块:4G和NBIOT,用户可以选配。又或是需要兼容两种操作系统:freertos和ucos。对于单个产品来说,运行时只工作在其中一种状态下,那么和工厂模式等不同的是,我们不需要提供选配工厂的接口,我们用宏开关在预编译阶段选配。

#define RTOS_TYPE//选配其中一种模式
 
static void create_freertos_1( void )
{
    printf( "create_freertos_1\n" );
}
 
static void create_freertos_2( void )
{
    printf( "create_freertos_2\n" );
}
 
static void create_ucos( void )
{
    printf( "create_ucos\n" );
}
 
static void run_freertos_1( void )
{
    printf( "run_freertos_1\n" );
}
 
static void run_freertos_2( void )
{
    printf( "run_freertos_2\n" );
}
 
static void run_ucos( void )
{
    printf( "run_ucos\n" );
}
  
typedef struct _OsAdapter{
    void( * create )( void );
    void( * run )( void );
} OsAdapter;
 
void create( void )
{
#ifdef RTOS_TYPE
    create_freertos_1();
    create_freertos_2();
#elif UCOS_TYPE
    create_ucos();
#endif
}
 
void run( void )
{
#ifdef RTOS_TYPE
    run_freertos_1();
    run_freertos_2();
#elif UCOS_TYPE
    run_ucos();
#endif
}
 
int main( void )
{
    OsAdapter os;
    os.create = create;
    os.create();
}

2、装饰器模式

装饰器模式,类似面向对象中的多态。在C中,每个函数是一个单独的过程,这个函数编写之后,我们可以为这个函数添加新的功能或者逻辑而不改动原函数,这就实现了类似装饰器的效果了。其实纯粹就是把函数作为可变参数。

#include <stdio.h>
//2000年写的函数
int add(int num1,int num2)
{
	int sum = num1 + num2;
	return sum;
}
//2000年写的函数
int sub(int num1,int num2)
{
	int sub = num1 - num2;
	return sub;
}
//可以继续拓展其他函数 
//2008年写的函数
int mul(int num1, int num2)
{
	int mul = num1* num2;
	return mul;
}
 
/*
使用装饰器模式 在C语言中可以实现类似多态的效果
可以很方便的进行拓展
这个函数 又被称为架构函数
*/
void wapper(int(*func)(int,int),int num1,int num2)
{
	//类似回调函数嘛,函数前后 可以进行装饰或者其他逻辑处理
	func(num1, num2);
}
 
int main(int argc, char *argv[])
{
	wapper(add, 20, 30);
	wapper(sub, 20, 30);
	wapper(mul, 20, 30);
	return 0;
}

3、代理模式

代理模式,为其他对象提供一种代理以控制对这个对象的访问
关键在于控制
如下面的例子:一个人想要找周杰伦签名或者拍电源或者拍广告必须先经过经纪人而不能直接找周杰伦,而经纪人可以在中间控制一下,比如验证这个人的身份,满足要求才让你见到周杰伦

typedef unsigned int  u32;
typedef unsigned short u16;
typedef unsigned char  u8;

#include "string.h"
#include "stdio.h"
#include "stdlib.h"
//定义接口
typedef struct{
	//签名
    char* (*sign)(void*);
    //拍电影
    void (*MakeFilm)(void*);
    //拍广告
    void (*MakeCommercials)(void*);
}BehaviourInterface_t;

//周杰伦的实际处理
static char* zhoujielunsign(void* v)
{
    printf("sign ok...\n");
    return (char*)"Hello";
}
//周杰伦的实际处理
static void zhoujielunMakeFilm(void* v)
{
    printf("MakeFilm ok...\n");
}
//周杰伦的实际处理
static void zhoujielunMakeCommercials(void* v)
{
    printf("MakeCommercials ok...\n");
}

//实例创建
typedef struct{
	//代理对象相当于经纪人
    BehaviourInterface_t Proxy;
    //实际的对象周杰伦
    BehaviourInterface_t zhoujielun;
    //控制对象的变量
    int age;
    int sex;
    char CompanyName[32];
}BehaviourProxyNew_t;

//经纪的实际处理
static char* Proxysign(void* v)
{
    BehaviourProxyNew_t* p = (BehaviourProxyNew_t*)v;
   //如果小于18岁或者是男的话就不给你签名,经纪人在这里控制了一下
   if(p->age < 18 || p->sex ==1){
       printf("You are too young or you are man,no sign thank you...\n");
       return (char*)"You are too young or you are man,no sign thank you";
   }
   else{
       return p->zhoujielun.sign(v);
   }
   return NULL;
}
//经纪的实际处理
static void ProxyMakeFilm(void* v)
{
    BehaviourProxyNew_t* p = (BehaviourProxyNew_t*)v;
    //如果是小公司的话就不拍,我的艺人就是这么豪横,经纪人在这里控制了一下
    if(strcmp(p->CompanyName,"small")==0){
        printf("sorry you company is so small...\n");
    }
    else if(strcmp(p->CompanyName,"big")==0){
        p->zhoujielun.MakeFilm(v);
    }
}
//经纪的实际处理
static void ProxyCommercials(void* v)
{
   BehaviourProxyNew_t* p = (BehaviourProxyNew_t*)v;
    //如果是小公司的话就不怕,我的艺人就是这么豪横,经纪人在这里控制了一下
    if(strcmp(p->CompanyName,"small")==0){
        printf("sorry you company is so small...\n");
    }
    else if(strcmp(p->CompanyName,"big")==0){
        p->zhoujielun.MakeCommercials(v);
    }
}
//代理创建
static BehaviourProxyNew_t* NewProxy(int age,int sex,const char* CompanyName)
{
    BehaviourProxyNew_t *p = (BehaviourProxyNew_t*)malloc(sizeof(BehaviourProxyNew_t));
    
    //经纪人实现接口 相当于代理对象
    p->Proxy.sign                  = Proxysign;
    p->Proxy.MakeFilm              = ProxyMakeFilm;
    p->Proxy.MakeCommercials       = ProxyCommercials;
    
    //周杰伦实现接口 相对于实际对象
    p->zhoujielun.sign             = zhoujielunsign;
    p->zhoujielun.MakeFilm         = zhoujielunMakeFilm;
    p->zhoujielun.MakeCommercials  = zhoujielunMakeCommercials;
    
    //经纪人独有的控制变量
    p->age        = age;
    p->sex        = sex;
    memcpy(p->CompanyName,CompanyName,strlen(CompanyName));
    
    return p;
}

int main(void)
{
	//第一个代理处理名为hansen的人 男性 27岁 大公司的人
	BehaviourInterface_t *HansenProxy = (BehaviourInterface_t*)NewProxy(27,1,"big");//1:male
	//第二个人名为Maria的人,女性 22岁,小公司的人
	BehaviourInterface_t *MariaProxy  = (BehaviourInterface_t*)NewProxy(22,0,"small");//0:female
	printf("hansenProxy----------------------------------------------\n");
    HansenProxy->sign(HansenProxy);
	HansenProxy->MakeFilm(HansenProxy);
	HansenProxy->MakeCommercials(HansenProxy);
	printf("\n\nMariaProxy-----------------------------------------------\n");
	MariaProxy->sign(MariaProxy);
	MariaProxy->MakeFilm(MariaProxy);
	MariaProxy->MakeCommercials(MariaProxy);	
}

【注意】
①结构体指针向下强转操作:

BehaviourInterface_t *HansenProxy = (BehaviourInterface_t*)NewProxy(27,1,"big");//1:male
BehaviourInterface_t *MariaProxy  = (BehaviourInterface_t*)NewProxy(22,0,"small");//0:female

②结构体指针向上强转操作:

BehaviourProxyNew_t* p = (BehaviourProxyNew_t*)v;

4、外观模式

外观模式(Facade Pattern),隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。

typedef struct _Computer {
    void ( *work )( void );
} Computer;
void work( void )
{
    printf( "work here!\n" );
}
 
typedef struct _Phone {
    void ( *contact )( void );
} Phone;
void contact( void )
{
    printf( "contact here!\n" );
}
 
typedef struct _Factory {
    Computer *computer;
    Phone *phone;
    void ( *create )( struct _Factory *factory );
} Factory;
void create( Factory *factory )
{
    assert( NULL != factory );
    factory->computer->work();
    factory->phone->contact();
}
 
int main( void )
{
    Computer computer = {
        .work = work,
    };
    Phone phone = {
        .contact = contact,
    };
    Factory factory = {
        .create = create,
        .computer = &computer,
        .phone = &phone,
    };
    factory.create( &factory );
}

5、桥接模式

桥接(Bridge),是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。

typedef struct _MeatDumpling  
{  
    void (*make)();  
}MeatDumpling;  
  
typedef struct _NormalDumpling  
{  
    void (*make)();  
}NormalDumpling;  

typedef struct _DumplingReuqest  
{  
    int type;  
    void* pDumpling;     
}DumplingRequest;  

void buy_dumpling(DumplingReuqest* pDumplingRequest)  
{  
    assert(NULL != pDumplingRequest);  
  
    if(MEAT_TYPE == pDumplingRequest->type)  
        return (MeatDumpling*)(pDumplingRequest->pDumpling)->make();  
    else  
        return (NormalDumpling*)(pDumplingRequest->pDumpling)->make();  
}  

这里定义了一个饺子买卖的唯一接口。它的特别支持就在于两个地方,第一是我们定义了饺子的类型type,这个type是可以随便扩充的;第二就是这里的pDumpling是一个void*指针,只有把它和具体的dumpling绑定才会衍生出具体的含义。

6、组合模式

组合模式(Composite Pattern),将对象组合成树形结构来表示“整体-部分”的层次结构,数据结构中的链表、二叉树,其实都可以用组合模式来解释。

typedef struct _NODE
{
    void* pData;
    struct _NODE* left;
    struct _NODE* right;
}NODE;

7、享元模式

享元模式(Flyweight Pattern),主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
享元模式看上去有点玄乎,但是其实也没有那么复杂。我们还是用示例说话。比如说,大家在使用电脑的使用应该少不了使用WORD软件。使用WORD呢, 那就少不了设置模板。什么模板呢,比如说标题的模板,正文的模板等等。这些模板呢,又包括很多的内容。哪些方面呢,比如说字体、标号、字距、行距、大小等等。

typedef struct _Font  
{  
    int type;  
    int sequence;  
    int gap;  
    int lineDistance;  
  
    void (*operate)(struct _Font* pFont);  
  
}Font;  

上面的Font表示了各种Font的模板形式。所以,下面的方法就是定制一个FontFactory的结构。

typedef struct _FontFactory  
{  
    Font** ppFont;  
    int number;  
    int size;  
  
    Font* GetFont(struct _FontFactory* pFontFactory, int type, int sequence, int gap, int lineDistance);  
}FontFactory;  

这里的GetFont即使对当前的Font进行判断,如果Font存在,那么返回;否则创建一个新的Font模式。

Font* GetFont(struct _FontFactory* pFontFactory, int type, int sequence, int gap, int lineDistance)  
{  
    int index;  
    Font* pFont;  
    Font* ppFont;  
  
    if(NULL == pFontFactory)  
        return NULL;  
  
    for(index = 0; index < pFontFactory->number; index++)  
    {  
        if(type != pFontFactory->ppFont[index]->type)  
            continue;  
  
        if(sequence != pFontFactory->ppFont[index]->sequence)  
            continue;  
  
        if(gap != pFontFactory->ppFont[index]->gap)  
            continue;  
  
        if(lineDistance != pFontFactory->ppFont[index]->lineDistance)  
             continue;  
  
        return pFontFactory->ppFont[index];  
    }      
  
    pFont = (Font*)malloc(sizeof(Font));  
    assert(NULL != pFont);  
    pFont->type = type;  
    pFont->sequence = sequence;  
    pFont->gap = gap;  
    pFont->lineDistance = lineDistance;  
  
    if(pFontFactory-> number < pFontFactory->size)  
    {  
        pFontFactory->ppFont[index] = pFont;  
        pFontFactory->number ++;  
        return pFont;  
    }  
  
    ppFont = (Font**)malloc(sizeof(Font*) * pFontFactory->size * 2);  
    assert(NULL != ppFont);  
    memmove(ppFont, pFontFacoty->ppFont, pFontFactory->size);  
    free(pFontFactory->ppFont);  
    pFontFactory->size *= 2;  
    pFontFactory->number ++;  
    ppFontFactory->ppFont = ppFont;  
    return pFont;         
} 

亨元模式和单例模式有点像,但有差别:
1、其实现方式不一样,单例是一个类只有一个唯一的实例,而享元可以有多个实例,只是通过一个共享容器来存储不同的对象。
2、其使用场景不一样,单例是强调减少实例化提升性能,因此一般用于一些需要频繁创建和销毁实例化对象或创建和销毁实例化对象非常消耗资源的类中,如连接池、线程池。而享元则是强调共享相同对象或对象属性,节约内存使用空间。

+++行为型模式+++

1、策略模式

算法可以自由切换,这是策略模式本身定义的,只要实现抽象策略,它就成为策略家族的一个成员,通过封装角色对其进行封装,保证对外提供“可自由切换”的策略。

//抽象的策略角色
typedef struct _Strategy
{
	void (*doSomething)();
}Strategy;
 
void doSomething1()
{
	printf("具体策略1的运算法则");
}
 
void doSomething2()
{
	printf("具体策略2的运算法则");
}
 
//具体策略角色
struct _Strategy ConcreteStrategy1 =
{
	.doSomething = doSomething1,
};
 
struct _Strategy ConcreteStrategy2 =
{
	.doSomething = doSomething2,
};
 
//封装角色
typedef struct _Context
{
  Strategy *pStrategy;
 
}Context;
 
void doAnything(Strategy *pStrategy)
{
	if(NULL == pStrategy)
		return;
	pStrategy->doSomething();
}
 
void client
{
	Context con;
	
  	Strategy *pStrategy1 = &ConcreteStrategy1;
	con.pStrategy = pStrategy1;
	doAnything(con.pStrategy);
	
  	Strategy *pStrategy2 = &ConcreteStrategy2;
	con.pStrategy = pStrategy2;
	doAnything(con.pStrategy);
}

如果没有策略模式,我们想想看会是什么样子?一个策略家族有5个策略算法,一会要 使用A策略,一会要使用B策略,怎么设计呢?使用多重的条件语句?多重条件语句不易维护,而且出错的概率大大增强。使用策略模式后,可以由其他模块决定采用何种策略,策略家族对外提供的访问接口就是封装类,简化了操作,同时避免了条件语句判断。

2、模板方法模式

在嵌入式的应用场景中,管理资源(例如文件、内存)是一件非常麻烦、非常容易出错的事情。因为在分配资源后,还必须释放资源。例如fopen()打开文件后,必须要使用fclose()来关闭文件,而使用malloc申请内存资源后,就必须使用free()函数来释放内存。
在实际开发工作中,稍微对malloc不注意就会导致内存泄漏。而模板方法模式堪称预防这类低级错误的神器!

场景:现在硬盘卡上存放了多部电影,我们需要在电脑上随机读取播放。假设我们动态申请1G的内存空间来存放视频,如果女主是美女,那么正常播放视频,播放完后退出程序。如果女主长相感人,则立马退出程序!

传统做法:

...
int main()
{
...
//申请1G内存
p_movie = malloc(1G);
//读取sd卡视频,并存放在p_movie中
...
//根据电影类型来选择播放方式
if(p_movie == 美女)
{
    ..._/正常播放电影
}
else if (p_movie == 长相感人)
{
    ..._/停止播放电影
    //释放内存
    free(p_movie);
    return -1;
}
//释放内存
free(p_movie);
return 0;
}

在上面的代码实现中,管理内存和使用内存的代码耦合在一起。在每个分支情况里面,必须时刻注意内存的使用和释放情况(比如在本例中,free函数就出现了两次)。随着各种程序中的分支越来越多、越来越庞大,有时候很容易忽略对内存的释放,从而引起内存泄漏。

使用模板方法:

//资源使用代码
int act_movie(char* p)
{
  if(p == 美女)
   {
       ..._/正常播放电影
   }
  else if (p == 长相感人)
   {
       ..._/停止播放电影
       return -1;
   }
   return 0;
}
//定义模板方法函数,负责资源管理
int template(int (*play_movie)(char* p)
{
	 int ret;
	 //申请1G内存
	 p_movie = malloc(1G);
	 //读取sd卡视频,并存放在p_movie中
	 ...
	 //根据电影类型来选择播放方式
	 ret = play_movie(p_movie);
 
	 //释放内存
	 free(p_movie);
	 return ret;
 
}
 
int main()
{
	int ret;
	...
	//调用模板方法函数
	ret = template(act_movie);
	...
	return ret;
}

3、观察者模式(重要)

观察者模式,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。有点类似发布订阅的机制。下面详细说明一下实现步骤:

①建立一个抽象的观察者typedef void (*observer_handle)(void* msg); 。为了满足开闭原则,方便我们对观察者进行扩展,我们维护一个全局的观察者链表Observer_Node* func_head_node[TOPIC_MAX] = {NULL};,这个链表由多个观察者节点组成,数量取决于主题枚举topic_e的数量TOPIC_MAX,我们通知消息通过的就是这个主题。
②那么接下来,我们需要根据抽象观察者写出我们的实际具体观察者void ObsOnOff(void *msg),将具体实际观察者注册到链表中,但这时观察者函数相当于空罐子,我们只是把他摆到院子里的台阶上(链表),等待雨天(通知)来了好接雨水(msg)。
③消息通知时,通过主题查找观察者链表,调用对应的具体的观察者,并将msg传递给它,具体的观察者就可以处理msg了。

所以,所谓的观察者其实是定义好一个没有传入数据的回调函数,他的主要任务仍然是处理数据,只是他需要在通知了具体消息时,来找到对应的函数来处理,前提是我们把主题和观察者绑定在了同一节点上。

#ifndef __MSG_SUBSCRIBE_H_
#define __MSG_SUBSCRIBE_H_

/*消息的类型都在这个枚举里*/
typedef enum
{
    ON_OFF_STATE,
    LIGHTNESS,
    WORK_MODE,
    TOPIC_MAX//方便计数
}topic_e;

/*抽象的观察者*/
typedef void (*observer_handle)(void* msg);
typedef struct
{
    observer_handle obs;
    topic_e topic;
}observer_t;

/*主题订阅(添加观察者)*/
int topic_subscribe(observer_t *observer);
/*消息通知*/
int msg_notify(topic_e topic, void *msg);

#endif /* __MSG_SUBSCRIBE_H_ */
#include <stdio.h>
#include "msg_subscribe.h"

/*观察者节点*/
typedef struct _Link_List
{
    observer_handle obs;
    struct _Link_List* next;
}Observer_Node;

/*全局的观察者链表*/
Observer_Node* func_head_node[TOPIC_MAX] = {NULL};

/*主题订阅(添加注册观察者)*/
int topic_subscribe(observer_t* observer)
{
    assert(observer->topic >= TOPIC_MAX || observer->obs == NULL || observer == NULL);
	
	//创建观察者
    Observer_Node* observer_node = (Observer_Node*)malloc(sizeof(Observer_Node));
    assert(observer_node == NULL)  
    observer_node->obs = observer->obs;
    observer_node->next = NULL;
	
	//指向订阅主题(要观察的主题)对应的节点
    Observer_Node* func_link_tail = func_head_node[observer->topic];
	
	//找到没有被占用的节点
    if(func_link_tail != NULL)
    {
        while(func_link_tail->next != NULL)
        {
            func_link_tail = func_link_tail->next;
        }
    }

	//如果找到了空节点,则注册观察者
    if(func_link_tail == NULL)
    {
        func_head_node[observer->topic] = observer_node;
    }
	//没有空节点,就覆盖下一个
    else
    {
        func_link_tail->next = observer_node;
    }
    return 0;
}

/*消息通知*/
int msg_notify(topic_e topic, void* msg)
{
    assert(topic >= TOPIC_MAX);

    Observer_Node* cur_node = func_head_node[topic];
    printf("Msg notify %d\r\n", topic);

    while(cur_node != NULL)
    {
        assert(cur_node->obs == NULL);
        cur_node->obs(msg);
        cur_node = cur_node->next;
    }
    return 0;
}

/*具体观察者*/
void ObsOnOff(void* msg){
	//消息处理
	printf("观察到的消息:%s\n",msg);
}
observer_t obsOnOff = {
	.obs = ObsOnOff,
	.topic = ON_OFF_STATE,	
}
mian()
{
	topic_subscribe(&obsOnOff);
	msg_notify(ON_OFF_STATE,"开关动作");
}

详细演化过程参考链接:C语言和设计模式-观察者模式

4、迭代器模式

5、责任链模式

在责任链模式中,优先级决定了那个handler会被先调,哪些会被后调用。在扩展特性里,每个handler可以有不处理和处理完之后继续交给下一个handler两种选择。如果该事件最后没有被消费,会有一个异常处理函数。如果责任链上任意一个handler消费了事件,那么就不传给下一个handler,直接结束。

最典型的责任链有linux内核的中断处理机制的纯软件部分和内核网络netfiler的HOOK机制,使用AT指令集时也常用到。

责任链模式,逻辑上和其最相近的一个设计模式为观察者模式。观察者模式和责任链模式的最大的差别在于,事件会被通知到每一个平等的handler,而不是逐级处理,也不存在优先级的说法,也不会出现事件没有处理需要异常函数收尾。

#include <stdio.h>
#include <assert.h>
#include "list.h"

static LIST_HEAD(chain_head);//定义并初始化一个头节点

enum
{
    PASS,
    REFUSE,
};

typedef struct
{
    int money;
    char* files;
} info_t;

typedef int (*request_handle)(info_t* info);

typedef struct
{
    request_handle func;
    ListObj list;
}chain_node_t;


int leader(info_t* info)
{
    assert(info != NULL);
    if(info->money < 1000) //金额小于1000就行
    {
        return PASS;
    }
    return REFUSE;
}


int hr(info_t* info)
{
    assert(info != NULL);
    if(info->money < 2000 && info->files != NULL) //金额小于2000且材料齐全
    {
        return PASS;
    }
    return REFUSE;
}


int boss(info_t* info)
{
    assert(info != NULL);
    if(info->files != NULL) //材料齐全就行,不差钱
    {
        return PASS;
    }
    return REFUSE;
}

//审批流程: leader->hr->boss
chain_node_t req_table[] = { {.func = leader}, {.func = hr}, {.func = boss} };

void main(void)
{
    /*将审批节点添加到责任链上*/
    for(int i = 0; i < sizeof(req_table) / sizeof(req_table[0]); i++)
    {
        list_insert_before(&chain_head, &(req_table[i].list));
    }

    /*报销内容*/
    info_t info = {.money = 900, .files = "files"};

    ListObj *node;
    list_for_each(node, &chain_head)
    {
        chain_node_t* req = list_entry(node, chain_node_t, list);
        //依次审批
        if(req->func(&info) != PASS)
        {
            /*报销失败*/
            printf("Failed\r\n");
            return;
        }
    }
    /*报销成功*/
    printf("Success\r\n");
    return;
}

6、命令模式

命令模式,类似于带函数指针的查表法,每个命令对应一个函数指针。
经典案例:
1、按键处理,每个按键按下得到一个索引(指的就是命令),一个按键对应一个处理函数。按键处理命令模式
2、协议解析(串口,网口,CAN,等等);以串口为例简单说明一下,比如有如下协议:

帧头 命令 数据长度 数据 校验 帧尾
1字节 1字节 2字节 n字节 2字节 1字节
// 当心字节对齐的问题
typedef struct
{
  uint8_t head;
  uint8_t cmd;
  uint16_t length;
  uint8_t data[1];
} package_t;
 
static int parse_temperature(char *buffer)
{
  int value = *(int *)buffer;
  printf("temperature = %d\n", value);
}
static int parse_humidity(char *buffer)
{
  int value = *(int *)buffer;
  printf("humidity = %d\n", value);
}
 
static int parse_illumination(char *buffer)
{
  int value = *(int *)buffer;
  printf("illumination = %d\n", value);
}
 
typedef struct
{
  uint8_t cmd;
  void (* handle)(char *buffer);
} package_entry_t;
 
static const package_entry_t package_items[] =
{
  {0x01, parse_temperature},
  {0x02, parse_humidity},
  {0x03, parse_illumination},
  {0xFF, NULL},
};
 
static uint8_t parse(char *buffer, uint16_t length)
{
  package_t *frame = (package_t *)buffer;
  uint16_t crc = CRCCheck(buffer, length - 3);
  uint8_t tail = buffer[length - 1];
  const package_entry_t *entry;
 
  if((frame->head != xxx) && (tail != xxx) && (crc != (buffer[length - 3]) << 8 | buffer[length - 2]))
  {
    return 0;
  }
 
  for(entry = package_items; entry->handle != NULL; ++entry)
  {
    if(frame->cmd == entry->cmd)
    {
      entry->handle(frame->data);
      break;
    }
  }
 
  return 1;
}

7、备忘录模式

备忘录模式,是为了支持撤销动作而存在的。作为了解。

撤销动作的结构体:

typedef struct _Action
{
    int type;
    void* pData;
    void (*process)(void* pData);
    
 	struct _Action* next;
}Action;

操作者结构体:

typedef struct _Organizer
{
    int number;
    Action* pActionHead;
    Action* (*create)();
    void (*restore)(struct _Organizer* pOrganizer); //撤销函数,参数为该操作者结构体
}Organizer;

撤销动作:

void restore(struct _Organizer* pOrganizer)
{
    Action* pHead;
    assert(NULL != pOrganizer);
 
    pHead = pOrganizer->pActionHead;
    pHead->process(pHead->pData);
    pOrganizer->pActionHead = pHead->next;
    pOrganizer->number --;
    free(pHead);
    return;
}

8、状态模式

状态模式,就是前面提到的状态机。

9、访问者模式

访问者模式,就是不同函数操作同一对象,产生不同的作用。

被访问对象:

typedef struct _Tofu
{
    int type;
    void (*eat)	(struct _Visitor* pVisitor, struct _Tofu* pTofu);
}Tofu;

访问者:

typedef struct _Visitor
{
    int region;
    void (*process)(struct _Tofu* pTofu, struct _Visitor* pVisitor);
}Visitor;
void eat(struct _Visitor* pVisitor, struct _Tofu* pTofu)
{
    assert(NULL != pVisitor && NULL != pTofu);
 
    pVisitor->process(pTofu, pVisitor);
}
void process(struct _Tofu* pTofu, struct _Visitor* pVisitor)
{
    assert(NULL != pTofu && NULL != pVisitor);
 
    if(pTofu->type == SPICY_FOOD && pVisitor->region == WEST ||
        pTofu->type == STRONG_SMELL_FOOD && pVisitor->region == EAST)
    {
        printf("I like this food!\n");
        return;
    }
 
    printf("I hate this food!\n");   
}

10、中介者模式

中介者模式,用于多个独立个体产生大量数据交流的情况,数据都转发给中介者,会大大降低代码交互复杂度。中介者就像媒婆的作用,在男女间传话。

媒婆、人:

struct _Mediator
{
    People* man;
    People* woman; 
};

struct _People
{
    Mediator* pMediator;/* 这里使用不完整声明,struct _Mediator 还没有定义,但编译器可以接受*/  
 
    void (*request)(struct _People* pPeople);
    void (*process)(struct _Peoplle* pPeople);
};
typedef struct _Mediator Mediator;
typedef struct _People People;

【参考链接】结构体相互嵌套使用方法

男方通过媒婆对女方提出要求:

void man_request(struct _People* pPeople)
{
    assert(NULL != pPeople);
 
    pPeople->pMediator->woman->process(pPeople->pMediator->woman);   
}

女方通过媒婆对南方提出要求:

void woman_request(struct _People* pPeople)
{
    assert(NULL != pPeople);
 
    pPeople->pMediator->man->process(pPeople->pMediator->man);
}

11、解释器模式

解释器模式,提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。
我们知道在C语言中,关于变量的定义是这样的:一个不以数字开始的由字母、数字和下划线构成的字符串。这种形式的表达式可以用状态自动机解决,当然也可以用解释器的方式解决。

typedef struct _Interpret
{
    int type;
    void* (*process)(void* pData, int* type, int* result);
 
}Interpret;

这里可以定义成字母的解释器、数字解释器、下划线解释器三种形式。所以,我们可以进一步定义一下process的相关函数。
对于数字解释器来说,它只解释数字,遇到非数字就退出,让别的解释器继续解释。

#define DIGITAL_TYPE 1
#define LETTER_TYPE  2
#define BOTTOM_LINE  3
 
void* digital_process(void* pData, int* type, int* result)
{
    UINT8* str;
    assert(NULL != pData && NULL != type && NULL != result);
 
    str = (UNT8*)pData;
    while (*str >= '0' && *str <= '9')
    {
        str ++;
    } 
 
    if(*str == '\0')
    {
        *result = TRUE;
        return NULL;
    }   
 
    if(*str == '_')
    {
        *result = TRUE;
        *type = BOTTOM_TYPE;
        return str;
    }
 
    if(*str >= 'a' && *str <= 'z' || *str >= 'A' && *str <= 'Z')
    {
        *result = TRUE;
        *type = LETTER_TYPE;
        return str;
    }
 
    *result = FALSE;
    return NULL;            
}    
 
void* letter_process(void* pData, int* type, int* result)
{
    UINT8* str;
    assert(NULL != pData && NULL != type && NULL != result);
 
    str = (UNT8*)pData;
    while (*str >= 'a' && *str <= 'z' || *str >= 'A' && *str <= 'Z')
    {
        str ++;
    } 
 
    if(*str == '\0')
    {
        *result = TRUE;
        return NULL;
    }   
 
    if(*str == '_')
    {
        *result = TRUE;
        *type = BOTTOM_TYPE;
        return str;
    }
 
    if(*str >= '0' && *str <= '9')
    {
        *result = TRUE;
        *type = DIGITAL_TYPE;
        return str;
    }
 
    *result = FALSE;
    return NULL;            
}          
 
void* bottom_process(void* pData, int* type, int* result)
{
    UINT8* str;
    assert(NULL != pData && NULL != type && NULL != result);
 
    str = (UNT8*)pData;
    while ('_' == *str )
    {
        str ++;
    } 
 
    if(*str == '\0')
    {
        *result = TRUE;
        return NULL;
    }   
 
    if(*str >= 'a' && *str <= 'z' || *str >= 'A' && *str <= 'Z')
    {
        *result = TRUE;
        *type = LETTER_TYPE;
        return str;
    }
 
    if(*str >= '0' && *str <= '9')
    {
        *result = TRUE;
        *type = DIGITAL_TYPE;
        return str;
    }
 
    *result = FALSE;
    return NULL;            
} 

四、鸡肋的吃法:面向对象

众所周知,C语言时面向过程的语言,那有没有必要面向对象呢?他确实很好的解决了C的一些痛点,但在一些小型项目中,面向对象这个东西显得臃肿、复杂,如同鸡肋”食之无味,弃之可惜“。所以切记不要为了面向对象而面向对象,更不要为了装杯而面向对象,我们应该酌情使用。

感兴趣可以去了解轻量级的C语言面向对象编程框架lw_oopc
链接:https://github.com/Akagi201/lw_oopc
在这里插入图片描述

我们这里自己简单实现一下面向对象的封装、继承和多态。建议使用现成框架。

1、封装

struct A_private;  
struct A {  
    int a;  
    ...  
    void (*func)(struct A*);  
 
    struct A_private * priv;  
};
/*上面的代码,我们只前向声明结构体 struct A_private ,没人知道它里面具体是什么东西。假如 struct A 对应的头
文件是 A.h ,那么把 A_private 的声明放在 A_p.h 中,A.c 文件包含 A_p.h ,那么在实现 struct A 的函数指针 func 
时如何使用 struct A_private ,客户程序员根本无须知道。


这样做的好处是显而易见的,除了预定义好的接口,客户程序员完全不需要知道实现细节,即便实现经过重构完全重来,
客户程序员也不需要关注,甚至相应的模块连重新编译都不要——因为 A.h 自始至终都没变过。
*/

2、继承

//内存管理类new.h

#ifndef NEW_H

#define NEW_H

void *new (const void *class, ...);

void delete (void *item);

void draw(const void *self);

#endif

//内存管理类的C文件:new.c

#include new.h”

#include “base.h”

void *new (const void *_base, ...)
{

    const struct Base *base = _base;

    void *p = calloc(1, base->size);

    assert(p);

    *(const struct Base **)p = base;

    if (base->ctor)

    {

        va_list ap;

        va_start(ap, _base);

        p = base->ctor(p, &ap);

        va_end(ap);
    }

    return p;
}

void delete (void *self)

{

    const struct Base **cp = self;

    if (self && *cp && (*cp)> dtor)

        self = (*cp)> dtor(self);

    free(self);
}

void draw(const void *self)

{

    const struct Base *const *cp = self;

    assert(self && *cp && (*cp)->draw);

    (*cp)->draw(self);
}

//基类:base.h

#ifndef BASE_H

#define BASE_H

struct Base

{

    size_t size; //类所占空间

    void *(*ctor)(void *self, va_list *app); //构造函数

    void *(*dtor)(void *self); //析构函数

    void (*draw)(const void *self); //作图
};

#endif

// Point头文件(对外提供的接口):point.h

#ifndef POINT_H

#define POINT_H

extern const void *Point; /* 使用方法:new (Point, x, y); */

#endif

// Point内部头文件(外面看不到):point.r

#ifndef POINT_R

#define POINT_R

struct Point

{

    const void *base; //继承,基类指针,放在第一个位置,const是防止修改

    int x, y; //坐标
};

#endif

// Point的C文件:point.c

#include “point.h”

#include new.h”

#include “point.h”

#include “point.r”

static void *Point_ctor(void *_self, va_list *app)

{

    struct Point *self = _self;

    self->x = va_arg(*app, int);

    self->y = va_arg(*app, int);

    return self;
}

static void Point_draw(const void *_self)

{

    const struct Point *self = _self;

    printf(draw(% d, % d), self->x, self->y);
}

static const struct Base _Point = {

    sizeof(struct Point), Point_ctor, 0, Point_draw

};

const void *Point = &_Point;

//测试程序:main.c

#include “point.h”

#include new.h”

int main(int argc, char **argv)

{

    void *p = new (Point, 1, 2);

    draw(p);

    delete (p);
}

3、多态

struct base_class
{
    int a;
    void (*vfunc)(int a);
} void base_class_vfunc(struct base_class *self, int a)
{
    assert(self != NULL);
    assert(slef->vfunc != NULL);
    self->vfunc(a);
}
struct child_class
{
    struct base_class parent;
    int b;
};
void child_class_init(struct child_class *self)
{
    struct base_class *parent;
    parent = (struct_base_class *)self;
    assert(parent != NULL);
    parent->vfunc = child_class_vfunc;
}
static void child_class_vfunc(struct child_class *self, int a)
{
    self->b = a + 10;
}

五、垃圾分类:防御式编程

分层设计或者模块化编程之后,带来另外一个问题,接口的开发者和接口的使用者往往太信任对方,例如我们自定义加法运算接口如下:int add(int a,int b)(int sum = a+b ;return sun;),使用者使用如下:int s = add(65535,1),将必然导致溢出错误。

防御式编程的中心思想是: 子程序不因传入错误数据而被破坏,哪怕是有其他程序产生的错误数据。

1.非法输入

对已形成产品的软件而言,应该做到“垃圾进,什么都不出”、“进来垃圾,出去是出错提示”或“不许垃圾进来”。通常有三种方法来处理进来垃圾的情况。

2.使用断言(assert)

断言可以用于在代码中说明各种假定,澄清各种不希望的情形。但通常断言只是在开发阶段用于帮助查清相互矛盾的假定、预料之外的情况以及传给子程序的错误数据等。在生成产品代码时,不要把断言编译进目标代码,以免降低性能和让用户看到断言报错信息。下面是关于使用断言的一些指导性建议。

  • 用错误处理代码来处理预期会发生的状况,用断言来处理绝不应该发生的状况
  • 避免把需要执行的代码放到断言中:断言只检查变量的值,而不要在断言中运行函数
  • 用断言来注解并验证前条件和后条件:所谓前条件是子程序或类的调用方代码在调用子程序或实例化对象之前要确保为真的属性,后条件是子程序或类在执行结束后要确保为真的属性
  • 对于高健壮性的代码,应该先使用断言再处理错误

网站公告

今日签到

点亮在社区的每一天
去签到