C语言设计模式——简单工厂模式

Posted 穿越临界点

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言设计模式——简单工厂模式相关的知识,希望对你有一定的参考价值。

在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的垃圾信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。


设计模式是学习和使用面向对象编程不可绕过的一个里程碑,学习设计模式有助于深入理解和灵活使用面向对象思想,设计出更高质量的代码。

1 软件质量

好的软件就像活字印刷或者乐高积木一样,有下述优点。

优点说明
可维护维护主要针对修改来说。可以只改需要改的地方,而不影响其他模块。一个标准就是改动量越小越好,最好是只改数据,不该程序。
可扩展扩展主要是指新增。如果要求新增功能简单,就要求提前预留了接口,或者接口是通用的。
可复用一个模块可以在不同的场景使用多次,而不需要重新造轮子。
灵活性好模块可以灵活排列,以组成不同的架构模式。

所有这些优点背后的逻辑是 高内聚,低耦合

2 简单工厂模式思路分析

简单工厂就是用来生产对象的,也就是做实例化的工作。由于面向对象编程中处处都是对象,所以简单工厂也会被设计为一个类,但是生产车间直接用静态方法就可以了(省去了实例化,实例化层数太多容易把人搞蒙)。

为什么需要简单工厂来生产对象呢,让调用者自己生产自己使用不行么?当然可以,但遇到这种情况——生产过程复杂——时就必须要开一间简单工厂来专门生产这些对象。

那什么叫生产过程复杂呢?复杂就代表着分支多,造成分支多的一个原因就是需求经常性的增加——有可能是业务需求(来自顶层的变化),也有可能是新增单板(来自底层的变化)需要软件兼容。如果代码架构设计的够好的话,一定是可扩展的,这就意味着接口基本是固定死的,而接口的实现可以根据业务需求不同,实现不同。连接这种不变与变最直接的桥梁就是 多态 ,亦即 面向接口编程

根据不同的条件或需求进行不同分支的实例化,而顶层调用时是固定的接口——多态——靠这种思想和技术实现逻辑隔离。而这部分复杂工作放在单独的一个对象中去做封装性会更好,这样就需要开一间简单工厂来专门做这件事。

3 UML图

简单工厂的UML如下图所示:

简单工厂类 +create() 功能模块抽象类 +val1 +val2 +absFunc1() +absFunc2() 功能模块实现类1 -values +absFunc1Realize() +absFunc2Realize() 功能模块实现类2 -values +absFunc1Realize() +absFunc2Realize()

Tips:由于markdown中嵌入mermaid对类图的支持不太友好,这里的类图表达的并不精确。后面找到合适的绘图工具再进行改进。


4 C语言实现

C语言虽然不是面向对象语言,但是可以支持 封装、继承和多态 ,因此,C语言是支持面向对象编程的。只不过在形式上不那么”正式“而已,但可以做到”形散神聚“。

4.1 功能模块抽象类

抽象类可以理解为抽象接口,在C语言中,类可以使用结构体来定义。C语言的封装性实际上要比面向对象语言要好,因为类中可以只呈现public方法和属性,其他的封装在实现中。

下面是一个spi模块的抽象接口定义。

/* spi 抽象接口父类定义头文件 */
/* 函数指针类型定义 */
typedef s32 (*spi_init_p)(u8 u8SpiId, STRU_SPI_PARA struSpiPara);
typedef s32 (*spi_byte_write_p)(u8 u8SpiId, u8 u8Cs, u16 u16Len, u8 *pu8Data);
typedef s32 (*spi_byte_read_p)(u8 u8SpiId, u8 u8Cs, u16 u16WLen, 
                               u8 *pu8WData, u16 u16RLen, u8 *pu8RData);

/* 类定义 */ 
typedef struct 
{
/* PUBLIC FUNCTION */
    spi_init_p fn_init; /*初始化函数指针*/
    spi_byte_write_p fn_byte_write; /*写函数指针*/
    spi_byte_read_p fn_byte_read; /*读函数指针*/
/* PUBLIC VARIABLE */
    u32 u32EndOfFuncFlag; /*函数指针结束标志*/
    u8  u8SpiBusId;
}STRU_SPI_CLASS;

4.2 功能模块实现类

在C语言中进行 继承 是比较麻烦的,而且比较危险。因为C语言中没有继承关键字,在实现时需要保证子类的结构体成员位置严格和父类的结构体成员位置保持一致。所以,可以放弃这种形式上的东西,转而将精力集中在设计可扩展性更强的接口父类上收效会更好一些。

由于单板或芯片差异,spi真实的实现会根据项目不同而不同,甚至差异很大。这就需要在父类基础上派生出两个或多个子类。每个子类独自占用一个源文件,当然如果规模比较小的情况下也可以将方法实现放在同一个源文件中。这里再强调一下,形式可以不拘泥一格,但是背后的思路一定要保证清晰。

如果在C语言中简化继承的实现,那么构造函数(因为C语言封装的类中只含有公有属性,且公有属性一般比较少,所以这里先不讨论析构函数的实现)就可以只保留一份,直接放到简单工厂类中实现。而各个子类中只需要关注父类中各个虚拟接口(方法)的各自实现。

4.3 简单工厂类

简单工厂类的功能就是根据某一个或几个指标在实例化阶段选取哪个子类作为父类真正的实现。父类的接口虽然不变,但是各个子类实现是有差异的,使用父类方法的模块在代码一行不动的情况下就可以实现不同的功能或适配不同的单板——这就是多态的好处。

下述代码是spi简单工厂类中create方法的实现,由于简单工厂的主要作用就是生产,因此create一般就是静态方法,因此,在C语言中虽然还称它是一个类,但是实际没必要再给简单工厂穿这件制服了。对于已经熟悉厂长的人,在他没有穿制服时同样认得他。

/* 简单工厂类 */
s32 spi_create(void)
{
    s32 ret = OSP_OK;
    u32 boardType = 0;
    
    /* 指针赋值(父类默认) */
    SPI.fn_init = &rru_spi_init;

    /* 根据设备型号不同来进行不同产线的生产 */
    INFOCFG.fn_type_get(&boardType);
    switch(boardType)
    {
        case N78_BOARD:
            SPI.fn_init = &spi_init_special;
            SPI.fn_byte_read = &spi_byte_read_qoriq_n78;
            SPI.fn_byte_write = &spi_byte_write_qoriq_n78;
            break;
        case N79_BOARD:
            SPI.fn_byte_read = &spi_byte_read_n79;
            SPI.fn_byte_write = &spi_byte_write_n79;
            break;

        default:
            perr("%s: Invalid Board Type = 0x%x.\\n", __FUNCTION__, boardType);
    }
  
    /* 空函数指针统一处理 */
    ret = detect_null_func_pointer(&SPI, FUNC_NUM_OF(STRU_SPI_CLASS));

    /* 对象内的全局变量初始化 */
	SPI.u8SpiBusId = 1;
    
    return ret;
}

恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~

以上是关于C语言设计模式——简单工厂模式的主要内容,如果未能解决你的问题,请参考以下文章

嵌入式C语言设计模式 --- 简单工厂模式

嵌入式C语言设计模式 --- 关于工厂模式的总结

嵌入式C语言设计模式 --- 关于工厂模式的总结

C语言设计模式--简单工厂模式

C语言设计模式——简单工厂模式

C语言设计模式——简单工厂模式