设计模式在C中的应用:单例模式、工厂模式、观察者模式的C实现
说实话,很多C程序员一听到「设计模式」就觉得这是Java、C++那帮人的事。我早年也这么想,直到在一个嵌入式项目里被代码维护逼疯——嗯,那时候我才意识到,设计模式本质上就是一些成熟的代码组织套路,跟语言无关。
C语言虽然没有class关键字,但结构体+函数指针的组合,足以让我们实现大部分经典模式。今天我就挑三个最常用的——单例、工厂、观察者,聊聊它们在C里的落地方式。
单例模式:全局唯一的「那个家伙」
单例模式说白了就是:整个程序生命周期里,某个类型的对象只存在一个实例。我在项目中遇到过最典型的场景是日志模块、配置管理器、硬件寄存器映射——这些东西你搞出多个副本,反而会出乱子。
C里实现单例,最直接的方式就是静态局部变量。来看代码:
#include <stdio.h>
#include <stdlib.h>
typedef struct Logger {
void (*log)(const char* msg);
int level;
} Logger;
static Logger* instance = NULL;
static void log_console(const char* msg) {
printf("[LOG] %s\n", msg);
}
Logger* get_logger_instance() {
if (instance == NULL) {
instance = (Logger*)malloc(sizeof(Logger));
if (instance) {
instance->log = log_console;
instance->level = 1;
}
}
return instance;
}
void destroy_logger() {
if (instance) {
free(instance);
instance = NULL;
}
}
你可能会问:多线程环境下怎么办?加锁呗。但C标准库没有内置的线程安全机制,我一般用平台相关的互斥量(pthread_mutex或Windows的CRITICAL_SECTION)包一层。不过要注意——双重检查锁定在C里不一定安全,因为编译器可能重排指令。我建议直接用加锁版本,性能损失通常可以接受。
工厂模式:把「造东西」的逻辑藏起来
工厂模式解决的是「创建对象」的耦合问题。你想想看,如果代码里到处都是if (type == A) createA() else if (type == B) createB(),那新增一种类型就得改一堆地方。工厂模式就是把这些判断集中到一个地方。
我在一个通信协议解析项目里用过这个模式。不同的报文类型需要不同的解析器,用工厂模式之后,新增协议类型只需要注册一个创建函数,主流程完全不用动。
C里实现工厂,核心是函数指针数组或注册表:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Device {
void (*init)(void);
void (*send)(const char* data);
} Device;
// 两种设备的具体实现
static void uart_init() { printf("UART init\n"); }
static void uart_send(const char* d) { printf("UART send: %s\n", d); }
static void spi_init() { printf("SPI init\n"); }
static void spi_send(const char* d) { printf("SPI send: %s\n", d); }
// 工厂函数
Device* create_device(const char* type) {
Device* dev = (Device*)malloc(sizeof(Device));
if (!dev) return NULL;
if (strcmp(type, "uart") == 0) {
dev->init = uart_init;
dev->send = uart_send;
} else if (strcmp(type, "spi") == 0) {
dev->init = spi_init;
dev->send = spi_send;
} else {
free(dev);
return NULL;
}
return dev;
}
如果类型特别多,if-else链会变得很长。这时候可以用注册表模式——把类型名和创建函数指针存在一个表里,用哈希或二分查找。这样新增类型只需要往表里加一条记录,代码更干净。
观察者模式:一喊多应的事件机制
观察者模式,也叫发布-订阅模式。一个对象状态变了,通知所有依赖它的对象。GUI里的按钮点击、传感器数据更新、状态机状态切换——这些场景都适合用观察者。
我记得在一个智能家居项目里,温度传感器每秒钟上报一次数据。需要更新显示、检查是否超温、记录日志。如果用轮询方式,每个模块都得自己去读传感器,耦合太紧。后来改成观察者模式:传感器作为主题,显示模块、报警模块、日志模块作为观察者注册进去。传感器一有数据,自动通知所有观察者。
C里的实现思路:主题维护一个观察者链表,每个观察者有一个回调函数指针。
#include <stdio.h>
#include <stdlib.h>
#define MAX_OBSERVERS 10
typedef void (*ObserverCallback)(int data);
typedef struct {
ObserverCallback callbacks[MAX_OBSERVERS];
int count;
} Subject;
void subject_init(Subject* sub) {
sub->count = 0;
}
int subject_attach(Subject* sub, ObserverCallback cb) {
if (sub->count >= MAX_OBSERVERS) return -1;
sub->callbacks[sub->count++] = cb;
return 0;
}
void subject_notify(Subject* sub, int data) {
for (int i = 0; i < sub->count; i++) {
if (sub->callbacks[i]) {
sub->callbacks[i](data);
}
}
}
// 观察者示例
void display_update(int val) { printf("Display: %d\n", val); }
void alarm_check(int val) {
if (val > 50) printf("ALARM! Temp too high: %d\n", val);
}
int main() {
Subject temp_sensor;
subject_init(&temp_sensor);
subject_attach(&temp_sensor, display_update);
subject_attach(&temp_sensor, alarm_check);
subject_notify(&temp_sensor, 45);
subject_notify(&temp_sensor, 55);
return 0;
}
观察者模式还有一个变种:带优先级。有些观察者需要先处理(比如紧急报警),有些可以后处理(比如日志记录)。实现上就是在插入时按优先级排序,通知时按顺序调用。
三种模式对比
| 模式 | 核心思想 | C实现关键 | 典型场景 |
|---|---|---|---|
| 单例模式 | 一个类只有一个实例 | 静态局部变量 + 访问函数 | 日志、配置、硬件寄存器 |
| 工厂模式 | 将对象创建逻辑封装 | 函数指针数组 / 注册表 | 协议解析、设备驱动 |
| 观察者模式 | 一对多的依赖通知 | 回调函数链表 | 事件驱动、传感器数据 |
知识体系图
下面这张图展示了三种模式在C语言中的定位和关系:
这三种模式在C里实现,说白了就是结构体+函数指针的灵活运用。单例管「唯一」,工厂管「创建」,观察者管「通知」。它们不是银弹,但用对了地方,代码的可维护性和可扩展性会明显提升。
我个人建议:别为了用模式而用模式。如果一个模块只有两三个函数,硬套单例反而多余。模式是工具,不是教条。你在实际项目中遇到重复出现的结构问题时,自然就会想到它们。