嵌入式C语言设计模式 --- 单例模式

时间:2024-10-02 15:05:21

1 - 什么是单例模式?

单例模式(Singleton Pattern)是软件程序设计中最简单的设计模式之一,单例模式在设计模式分类中,是属于创建型模式,它提供了一种创建对象(实例,变量)的最佳实践方式。

顾名思义,单例(也叫单件),也就是在整个程序设计中,只存在一个单一的类(C语言中叫“类型”,通常用结构体类型表示),这个类负责创建一个对象(C语言中叫“定义变量”,通常是指结构体变量

并且,通过这个类创建的对象,在整个程序中,有且只有一个,同时,这个类提供了一种访问这个类对象的唯一方式,并且可以对类对象直接访问,而不需要再重新创建。

使用单例模式设计的类,有以下特征:

(1)单例类,有且只有一个类对象(实例)

(2)单例类必须提供一个方法(函数),用来创建属于自己的唯一类对象。

(3)其他对象都可以访问这个单例类创建的唯一类对象。

嵌入式C语言中,简单概括就是,先创建一个结构体类型,通过这个结构体类型定义出来的结构体变量,在整个程序中,有且只有一个,其他函数可以通过特定的方式(比如指针),获得这个结构体变量。

单例,通俗来说,就是有且只有一个,并且全局可访问。(在多线程场合,要注意线程安全)

2 – 什么情况下要用单例模式?

在需要保证程序全局只能有一个实例(也就是结构体变量)的时候,需要用到单例模式。

单例模式很好地解决了同一个类型的结构体变量被反复创建,也就是被反复malloc,反复malloc会造成内存资源的浪费。

当你想控制整个程序里面同一个类型的结构体变量数目,从而节省系统资源的时候,单例模式就是一种很好的实践方案。

举个例子:

对于某颗芯片的某个串口外设,比如STM32的UART1,整个芯片都只有一个UART1,在多线程的业务场景下,可能会存在多个线程同时使用UART1外设的情况。

如果每个线程在使用UART1外设时,都对其进行初始化,然后可能会同时调用读和写函数,这样就会造成资源竞争和抢夺,多次重复初始化,也会造成内存资源的浪费。

单例模式就是为了解决这个问题而存在的。

虽然有多个线程,但每个线程在获取串口操作句柄的时候,都需要先申请互斥锁,申请成功后,再获取串口句柄对象,这个对象在整个应用程序里面,是唯一的,然后就可以使用这个唯一的句柄,去操作实际的串口外设了。

3 – 如何使用单例模式?

在嵌入式软件中,要使用单例模式,最大的关键在于,我们在创建一个结构体指针变量的时候,需要先判断这个结构体指针变量是否已经存在

也就是说,要先判断这个结构体指针变量是否已经获得了内存空间,如果是,则直接返回该内存空间的指针。如果不是,则用malloc创建,并返回创建后的内存指针。

通常使用一个函数调用malloc来为该结构体指针变量分配内存空间(当然,也可以用静态内存的方式进行创建,而不调用malloc)。

以下是具体的代码示例。

1、先创建一个singleton_pattern.h头文件,并在这个头文件声明一个串口操作类,这个类包含了串口设备的属性和操作方法,如下图所示。

 2、然后在singleton_pattern.c文件中,实现了串口操作方法,主要是串口设备的初始化,串口设备的读操作和写操作。限于篇幅,这三个函数都只实现了打印功能,开发者可以根据实际项目情况重写这三个函数。

 3、单例模式在使用的时候,可以通过一个函数来获取这个唯一的实例对象,如果这个对象没有被创建,则进行创建。如果对象已经被创建,则直接返回对象的指针,如下图所示。

 4、在使用的时候,可以直接调用get_uart_handler来获取对象实例,获取后就可以通过这个实例来操作串口设备了,无论线程里面调用多少次,获取到的对象实例都是同一个东西,在程序里面,它是唯一的,如下图所示。

 5、需要注意的是,由于这个对象实例在全局是唯一的,多个线程访问的时候,会存在资源竞争的问题,因此,在使用这个对象实例的时候,要使用互斥锁进行线程安全处理。

6、如果确定在程序的生命周期里面,不再使用这个对象实例,可以对其进行销毁操作,如下图所示。

4 – 怎样验证单例模式?

由于我们是在嵌入式系统中验证单例模式,所以,本例程的代码,都是在嵌入式开发板中进行运行并输出结果进行验证。

(代码是使用标准C语言进行编写,也可以在PC端进行验证,只需更换对应的编译器即可)

开发板是使用NXP i.MX6ULL开发板,并且已经根据开发板官方提供的文档资料,搭建好开发环境。

把编译出来的程序下载到开发板里面,执行结果如下图所示。

本文代码下载链接:

c_design_pattern: 嵌入式 C 语言设计模式代码仓库

或在公众号回复【设计模式源码