大话设计模式C语言之单例模式

单例模式

单例模式 (SingletonPattern) 是一种创建型设计模式,让你能够保证一个类只有一个实例,并提供一个访问该实例的全局节点。

案例代码

对象单例

单例日志类接口 logger.h

单例(Singleton)类声明了一个名为 get­Instance获取实例的静态方法来返回其所属类的一个相同实例。单例的构造函数必须对客户端(Client)代码隐藏。调用获取实例方法必须是获取单例对象的唯一方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef LOGGER_H
#define LOGGER_H

typedef struct Logger Logger;

/* 显式初始化(嵌入式推荐) */
void logger_init(void);

/* 获取单例 */
Logger *logger_instance(void);

/* 输出日志 */
void logger_log(Logger *self, const char *msg);

#endif
单例日志类实现 logger.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <stddef.h>
#include "logger.h"
#include "logger_port.h"

/* ============================
私有结构体
============================ */
struct Logger {
unsigned char initialized;
};

/* ============================
静态单例实例
============================ */
static struct Logger g_logger;

/* ============================
初始化
============================ */
void logger_init(void)
{
if (g_logger.initialized) {
return;
}

// logger_port_uart_init();
g_logger.initialized = 1;
}

/* ============================
获取单例
============================ */
Logger *logger_instance(void)
{
return &g_logger;
}

/* ============================
输出日志
============================ */
void logger_log(Logger *self, const char *msg)
{
if (!self || !self->initialized || !msg) {
return;
}

logger_port_enter_critical();

logger_port_uart_write(msg);
logger_port_uart_write("\r\n");

logger_port_exit_critical();
}

硬件抽象

抽象接口 logger_port.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef LOGGER_PORT_H
#define LOGGER_PORT_H

/* 串口初始化 */
void logger_port_uart_init(void);

/* 串口发送字符串 */
void logger_port_uart_write(const char *str);

/* 临界区(可选) */
void logger_port_enter_critical(void);
void logger_port_exit_critical(void);

#endif

具体抽象 logger_port.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include "logger_port.h"

/* 模拟 UART */
void logger_port_uart_init(void)
{
/* 硬件 UART 初始化 */
}

void logger_port_uart_write(const char *str)
{
while (*str) {
putchar(*str++); // 替换为 HAL_UART_Transmit
}
}

void logger_port_enter_critical(void)
{
/* 裸机可禁中断 */
/* __disable_irq(); */
}

void logger_port_exit_critical(void)
{
/* __enable_irq(); */
}

测试客户端代码 main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "logger.h"

int main(void)
{
logger_init();

Logger *log1 = logger_instance();
Logger *log2 = logger_instance();

if (log1 == log2) {
logger_log(log1, "Singleton OK");
}

logger_log(log1, "System Booted");

while (1);
return 0;
}

总结

   单例模式同时解决了两个问题,所以违反了单一职责原则:
1.保证一个类只有一个实例。为什么会有人想要控制一个类所拥有的实例数量?最常见的原因是控制某些共享资源(例如数据库或文件)的访问权限。它的运作方式是这样的:如果你创建了一个对象,同时过一会儿后你决定再创建一个新对象,此时你会获得之前已创建的对象,而不是一个新对象。普通构造函数无法实现上述行为,因为构造函数的设计决定了它必须总是返回一个新对象。
2.为该实例提供一个全局访问节点。还记得你用过的那些存储重要对象的全局变量吗?它们在使用上十分方便但同时也非常不安全,因为任何代码都有可能覆盖掉那些变量的内容,从而引发程序崩溃。和全局变量一样,单例模式也允许在程序的任何地方访问特定对象。但是它可以保护该实例不被其他代码覆盖。还有你不会希望解决同一个问题的代码分散在程序各处的。因此更好的方式是将其放在同一个类中,特别是当其他代码已经依赖这个类时更应该如此。
单例模式已经变得非常流行,以至于人们会将只解决上文描述中任意一个问题的东西称为单例。