文章目录
-
-
-
- 一、适配器模式的定义与核心价值
- [二、C 语言实现适配器模式的核心思路](#二、C 语言实现适配器模式的核心思路)
- [三、5 个实例](#三、5 个实例)
-
- [实例 1:基础适配器(电压转换)](#实例 1:基础适配器(电压转换))
- [实例 2:数据格式适配器(CSV 转 JSON)](#实例 2:数据格式适配器(CSV 转 JSON))
- [实例 3:函数接口适配器(旧版 API 适配新版)](#实例 3:函数接口适配器(旧版 API 适配新版))
- [实例 4:硬件接口适配器(SPI 设备适配 I2C 控制器)](#实例 4:硬件接口适配器(SPI 设备适配 I2C 控制器))
- [实例 5:Linux 内核风格适配器(系统调用适配)](#实例 5:Linux 内核风格适配器(系统调用适配))
- [四、Linux 内核中的适配器模式应用](#四、Linux 内核中的适配器模式应用)
- 五、实现注意事项
- 六、补充说明
-
-
一、适配器模式的定义与核心价值
适配器模式(Adapter Pattern)是一种结构型设计模式,其核心是将一个类的接口转换成客户端期望的另一种接口,使原本因接口不兼容而无法协同工作的类能够一起工作。
存在的意义:在系统开发中,经常需要整合已有模块(如第三方库、 legacy 代码)或不同版本的接口,而这些模块的接口往往与当前系统的期望接口不匹配。适配器模式通过 "适配" 过程屏蔽接口差异,无需修改原有模块即可使其融入新系统。
解决的问题:
- 已有模块接口与当前系统接口不兼容,无法直接调用;
- 需复用第三方库或遗留代码,但不想修改其源码;
- 系统升级时,新旧接口并存需平滑过渡。
二、C 语言实现适配器模式的核心思路

C 语言无类,但可通过结构体封装适配逻辑 +函数指针转换接口实现适配器模式:
- 定义目标接口(Target):客户端期望的接口格式(如函数指针结构体);
- 明确适配者(Adaptee):需要被适配的已有接口(如第三方库函数、旧版接口);
- 实现适配器(Adapter):结构体包含适配者的引用,通过函数指针实现目标接口,内部调用适配者的接口并进行参数 / 返回值转换。
三、5 个实例
实例 1:基础适配器(电压转换)
模拟 "两脚插头(适配者)" 通过 "电源适配器" 转换为 "三脚插座(目标接口)"。
c
#include <stdio.h>
// 目标接口:三脚插座(客户端期望的接口)
typedef struct {
void (*plug_3p)(void); // 三脚插头插入
} ThreePinSocket;
// 适配者:两脚插头(已有接口,与目标不兼容)
typedef struct {
void (*plug_2p)(void); // 两脚插头插入
} TwoPinPlug;
// 具体适配者:旧版设备(只有两脚插头)
void old_device_plug() {
printf("Old device (2-pin) is plugged in\n");
}
TwoPinPlug old_plug = {.plug_2p = old_device_plug};
// 适配器:将两脚插头转换为三脚接口
typedef struct {
ThreePinSocket target; // 实现目标接口
TwoPinPlug* adaptee; // 持有适配者
} PlugAdapter;
// 适配器实现目标接口:内部调用适配者
void adapter_plug_3p() {
// 假设适配器内部完成电压/引脚转换
printf("Adapter converting 2-pin to 3-pin...\n");
old_plug.plug_2p(); // 调用适配者接口
}
// 创建适配器
PlugAdapter* create_adapter(TwoPinPlug* adaptee) {
static PlugAdapter adapter;
adapter.adaptee = adaptee;
adapter.target.plug_3p = adapter_plug_3p;
return &adapter;
}
// 客户端:只认识三脚插座接口
void client_use(ThreePinSocket* socket) {
socket->plug_3p();
}
int main() {
// 适配者(两脚插头)通过适配器转换为目标接口(三脚)
PlugAdapter* adapter = create_adapter(&old_plug);
// 客户端使用目标接口,无需知道适配者的存在
client_use(&adapter->target);
return 0;
}
以上代码运行结果
Adapter converting 2-pin to 3-pin...
Old device (2-pin) is plugged in
以上代码形成UML图,可得

即:适配器PlugAdapter实现了目标接口ThreePinSocket,内部调用适配者TwoPinPlug的接口,使旧设备能在新插座上使用,无需修改旧设备代码。
实例 2:数据格式适配器(CSV 转 JSON)
将 CSV 格式数据(适配者)转换为系统期望的 JSON 格式(目标接口)。
c
#include <stdio.h>
#include <string.h>
// 目标接口:JSON数据处理器(系统期望的接口)
typedef struct {
void (*process_json)(const char* json);
} JsonProcessor;
// 适配者:CSV数据读取器(已有接口,格式不兼容)
typedef struct {
void (*read_csv)(const char* csv, char* out_rows[10], int* row_count);
} CsvReader;
// 具体适配者:第三方CSV库
void third_party_csv_read(const char* csv, char* out_rows[10], int* row_count) {
// 模拟解析CSV(简单分割)
*row_count = 0;
char* csv_copy = strdup(csv);
char* row = strtok(csv_copy, "\n");
while (row && *row_count < 10) {
out_rows[*row_count] = strdup(row);
(*row_count)++;
row = strtok(NULL, "\n");
}
free(csv_copy);
printf("Third-party CSV reader parsed %d rows\n", *row_count);
}
CsvReader csv_reader = {.read_csv = third_party_csv_read};
// 适配器:将CSV转换为JSON接口
typedef struct {
JsonProcessor target; // 实现目标接口
CsvReader* adaptee; // 持有适配者
} CsvToJsonAdapter;
// 适配器实现目标接口:CSV→JSON转换
void adapter_process_json(const char* csv_data) {
// 1. 调用适配者读取CSV
char* rows[10];
int row_count = 0;
csv_reader.read_csv(csv_data, rows, &row_count);
// 2. 转换为JSON格式(简单模拟)
char json[512] = "{\"rows\": [";
for (int i = 0; i < row_count; i++) {
strcat(json, "{\"data\": \"");
strcat(json, rows[i]);
strcat(json, "\"}");
if (i != row_count - 1) strcat(json, ", ");
free(rows[i]);
}
strcat(json, "]}");
// 3. 按目标接口处理JSON
printf("Processed JSON: %s\n", json);
}
// 创建适配器
CsvToJsonAdapter* create_csv_adapter(CsvReader* adaptee) {
static CsvToJsonAdapter adapter;
adapter.adaptee = adaptee;
adapter.target.process_json = adapter_process_json;
return &adapter;
}
// 客户端:只处理JSON格式
void client_handle_json(JsonProcessor* processor, const char* data) {
processor->process_json(data);
}
int main() {
CsvToJsonAdapter* adapter = create_csv_adapter(&csv_reader);
const char* csv = "name,age\nAlice,30\nBob,25";
// 客户端传入CSV数据,通过适配器自动转换为JSON处理
client_handle_json(&adapter->target, csv);
return 0;
}
以上代码运行得到
Third-party CSV reader parsed 3 rows
Processed JSON: {"rows": [{"data": "name,age"}, {"data": "Alice,30"}, {"data": "Bob,25"}]}
以上代码的UML简图为

即适配器CsvToJsonAdapter将第三方 CSV 库的接口转换为系统期望的 JSON 处理接口,客户端无需修改即可处理 CSV 数据。
实例 3:函数接口适配器(旧版 API 适配新版)
将旧版库的函数接口(适配者)转换为新版 API(目标接口),实现平滑过渡。
c
#include <stdio.h>
#include <stdlib.h>
// 目标接口:新版API(参数为指针和长度)
typedef struct {
int (*new_read)(void* buf, size_t len);
} NewAPI;
// 适配者:旧版API(参数为字符数组和最大长度,返回实际长度)
int old_read(char* buf, int max_len) {
static const char* data = "old data";
int len = strlen(data);
len = len > max_len ? max_len : len;
memcpy(buf, data, len);
printf("Old API read %d bytes\n", len);
return len;
}
// 适配器:将旧版API转换为新版接口
typedef struct {
NewAPI target; // 实现新版接口
} OldToNewAdapter;
// 适配器实现新版接口:参数转换
int adapter_new_read(void* buf, size_t len) {
// 旧版API的max_len是int类型,需转换参数类型
return old_read((char*)buf, (int)len);
}
OldToNewAdapter* create_api_adapter() {
static OldToNewAdapter adapter;
adapter.target.new_read = adapter_new_read;
return &adapter;
}
// 客户端:只使用新版API
void client_use_new_api(NewAPI* api) {
char buf[100];
int len = api->new_read(buf, sizeof(buf));
printf("Client received: %.*s\n", len, buf);
}
int main() {
OldToNewAdapter* adapter = create_api_adapter();
// 客户端调用新版API,实际使用的是旧版实现
client_use_new_api(&adapter->target);
return 0;
}
以上代码输出
Old API read 8 bytes
Client received: old data
其对应的UML可简写为

即适配器解决了新旧 API 参数类型不兼容的问题(size_t vs int),使旧版库能被期望新版 API 的客户端直接使用。
实例 4:硬件接口适配器(SPI 设备适配 I2C 控制器)
模拟嵌入式系统中,SPI 设备(适配者)通过适配器在 I2C 控制器(目标接口)上工作。
c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
// 目标接口:I2C控制器接口(带ctx上下文)
typedef struct {
void (*i2c_write)(void* ctx, uint8_t addr, const uint8_t* data, int len);
void (*i2c_read)(void* ctx, uint8_t addr, uint8_t* data, int len);
} I2CController;
// 适配者:SPI设备接口
typedef struct {
void (*spi_transfer)(const uint8_t* tx, uint8_t* rx, int len);
} SPIDevice;
// 具体适配者:SPI传感器(修正rx空指针判断)
void sensor_spi_transfer(const uint8_t* tx, uint8_t* rx, int len) {
printf("SPI sensor received cmd: ");
for (int i = 0; i < len; i++) printf("%02X ", tx[i]);
printf("\n");
// 仅当rx非NULL时才写入接收数据(避免空指针访问)
if (rx != NULL) {
for (int i = 0; i < len; i++) {
rx[i] = tx[i] + 0x10; // 模拟SPI响应数据
}
// 调试:打印SPI响应
printf("SPI sensor sent resp: ");
for (int i = 0; i < len; i++) printf("%02X ", rx[i]);
printf("\n");
}
}
SPIDevice spi_sensor = {.spi_transfer = sensor_spi_transfer};
// 适配器:SPI转I2C
typedef struct {
I2CController target; // 实现I2C接口
SPIDevice* adaptee; // 持有SPI设备
uint8_t i2c_addr; // 模拟I2C地址
} SpiToI2cAdapter;
// 适配I2C写操作(无越界风险,直接透传)
void adapter_i2c_write(void* ctx, uint8_t addr, const uint8_t* data, int len) {
SpiToI2cAdapter* adapter = (SpiToI2cAdapter*)ctx;
printf("Adapter converting I2C write (addr 0x%02X) to SPI...\n", addr);
// SPI写:tx长度 = 1(I2C地址) + len(I2C数据),rx为NULL
uint8_t tx[len + 1];
tx[0] = addr; // 第1字节:I2C地址
memcpy(tx + 1, data, len); // 后续字节:I2C写入数据
adapter->adaptee->spi_transfer(tx, NULL, len + 1);
}
// 适配I2C读操作(用临时缓冲区避免越界)
void adapter_i2c_read(void* ctx, uint8_t addr, uint8_t* data, int len) {
SpiToI2cAdapter* adapter = (SpiToI2cAdapter*)ctx;
printf("Adapter converting I2C read (addr 0x%02X) to SPI...\n", addr);
// 1. 临时缓冲区:容纳1字节命令响应 + len字节目标数据(避免越界)
uint8_t temp_rx[len + 1];
uint8_t tx[1] = {addr | 0x80}; // 读命令:I2C地址 | 读标记(0x80)
// 2. SPI传输:tx=读命令,rx=临时缓冲区,长度=1+len(安全)
adapter->adaptee->spi_transfer(tx, temp_rx, len + 1);
// 3. 提取目标数据(跳过第1字节命令响应),拷贝到客户端缓冲区
memcpy(data, temp_rx + 1, len);
}
// 创建适配器
SpiToI2cAdapter* create_spi_adapter(SPIDevice* adaptee, uint8_t i2c_addr) {
static SpiToI2cAdapter adapter;
adapter.adaptee = adaptee;
adapter.i2c_addr = i2c_addr;
adapter.target.i2c_write = adapter_i2c_write;
adapter.target.i2c_read = adapter_i2c_read;
return &adapter;
}
// 客户端:操作I2C接口
void client_use_i2c(I2CController* i2c, void* ctx, uint8_t addr) {
uint8_t tx_data[] = {0x01, 0x02}; // I2C写入数据(2字节)
i2c->i2c_write(ctx, addr, tx_data, sizeof(tx_data));
uint8_t rx_data[2]; // I2C读取缓冲区(2字节,匹配需求)
i2c->i2c_read(ctx, addr, rx_data, sizeof(rx_data));
printf("Client I2C read: %02X %02X\n", rx_data[0], rx_data[1]);
}
int main() {
SpiToI2cAdapter* adapter = create_spi_adapter(&spi_sensor, 0x48);
client_use_i2c(&adapter->target, adapter, adapter->i2c_addr);
return 0;
}
以上代码运行结果
==>
Adapter converting I2C write (addr 0x48) to SPI...
SPI sensor received cmd: 48 01 02
Adapter converting I2C read (addr 0x48) to SPI...
SPI sensor received cmd: C8 00 1B
SPI sensor sent resp: D8 10 2B
Client I2C read: 10 2B
它对应的UML为

即适配器SpiToI2cAdapter将 SPI 设备的读写接口转换为 I2C 控制器接口,使仅支持 I2C 的系统能驱动 SPI 硬件。
实例 5:Linux 内核风格适配器(系统调用适配)
模拟 Linux 内核中,不同文件系统的read接口(适配者)通过 VFS 适配器转换为统一的系统调用接口(目标接口)。
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 目标接口:VFS统一文件操作接口(系统调用依赖的接口)
typedef struct {
ssize_t (*vfs_read)(void* file, char* buf, size_t count, loff_t* pos);
} VFSFileOps;
// 适配者1:ext4文件系统的read接口(参数格式不同)
ssize_t ext4_read(const char* path, char* buf, int len, long* offset) {
static const char* data = "ext4 file content";
int read_len = strlen(data) - *offset;
read_len = read_len > len ? len : read_len;
if (read_len > 0) {
memcpy(buf, data + *offset, read_len);
*offset += read_len;
}
printf("ext4_read: path=%s, len=%d\n", path, read_len);
return read_len;
}
// 适配者2:tmpfs文件系统的read接口(参数格式不同)
ssize_t tmpfs_read(void* inode, char* buf, size_t count, off_t pos) {
static const char* data = "tmpfs in-memory data";
ssize_t read_len = strlen(data) - pos;
read_len = read_len > count ? count : read_len;
if (read_len > 0) memcpy(buf, data + pos, read_len);
printf("tmpfs_read: inode=%p, pos=%lld\n", inode, (long long)pos);
return read_len;
}
// 适配器:ext4适配VFS接口
typedef struct {
VFSFileOps vfs_ops; // 实现VFS接口
const char* path; // ext4需要的路径参数
long offset; // ext4的偏移量类型
} Ext4Adapter;
ssize_t ext4_vfs_read(void* file, char* buf, size_t count, loff_t* pos) {
Ext4Adapter* adapter = (Ext4Adapter*)file;
// 转换参数类型(loff_t→long)
adapter->offset = (long)*pos;
ssize_t len = ext4_read(adapter->path, buf, (int)count, &adapter->offset);
*pos = adapter->offset; // 同步偏移量
return len;
}
Ext4Adapter* create_ext4_adapter(const char* path) {
Ext4Adapter* adapter = malloc(sizeof(Ext4Adapter));
adapter->path = path;
adapter->offset = 0;
adapter->vfs_ops.vfs_read = ext4_vfs_read;
return adapter;
}
// 适配器:tmpfs适配VFS接口
typedef struct {
VFSFileOps vfs_ops;
void* inode; // tmpfs需要的inode
off_t pos; // tmpfs的偏移量类型
} TmpfsAdapter;
ssize_t tmpfs_vfs_read(void* file, char* buf, size_t count, loff_t* pos) {
TmpfsAdapter* adapter = (TmpfsAdapter*)file;
adapter->pos = *pos;
ssize_t len = tmpfs_read(adapter->inode, buf, count, adapter->pos);
*pos = adapter->pos + len;
return len;
}
TmpfsAdapter* create_tmpfs_adapter(void* inode) {
TmpfsAdapter* adapter = malloc(sizeof(TmpfsAdapter));
adapter->inode = inode;
adapter->pos = 0;
adapter->vfs_ops.vfs_read = tmpfs_vfs_read;
return adapter;
}
// 客户端:系统调用read(只认识VFS接口)
ssize_t sys_read(VFSFileOps* ops, void* file, char* buf, size_t count, loff_t* pos) {
return ops->vfs_read(file, buf, count, pos);
}
int main() {
// 适配ext4文件
Ext4Adapter* ext4_file = create_ext4_adapter("/ext4/file.txt");
char buf[100];
loff_t pos = 0;
ssize_t len = sys_read(&ext4_file->vfs_ops, ext4_file, buf, sizeof(buf), &pos);
printf("sys_read from ext4: %.*s\n", (int)len, buf);
// 适配tmpfs文件
void* inode = (void*)0x123456; // 模拟inode
TmpfsAdapter* tmpfs_file = create_tmpfs_adapter(inode);
pos = 0;
len = sys_read(&tmpfs_file->vfs_ops, tmpfs_file, buf, sizeof(buf), &pos);
printf("sys_read from tmpfs: %.*s\n", (int)len, buf);
free(ext4_file);
free(tmpfs_file);
return 0;
}
以上代码调用结果
ext4_read: path=/ext4/file.txt, len=17
sys_read from ext4: ext4 file content
tmpfs_read: inode=0x123456, pos=0
sys_read from tmpfs: tmpfs in-memory data
函数的调用顺序为
sys_read-->vfs_read-->tmpfs_vfs_read(ext4_vfs_read)-->tmpfs_read(ext4_read)
对应的UML图为

即模拟 Linux 内核 VFS 的适配器逻辑,不同文件系统的read接口(ext4_read、tmpfs_read)通过适配器转换为统一的 VFS 接口,使系统调用read无需关心底层文件系统的差异。
四、Linux 内核中的适配器模式应用
- VFS(虚拟文件系统) :VFS 是最典型的适配器模式应用,它为不同文件系统(ext4、btrfs、tmpfs 等)定义统一接口(
struct file_operations),每个文件系统通过适配器实现该接口,使系统调用(如read、write)能无缝操作任何文件系统。 - 设备驱动适配层 :如
usb-storage驱动,将 USB 设备的接口适配为 SCSI 协议接口,使 USB 存储设备能被 SCSI 子系统识别和管理。 - 网络协议适配(如
compat层) :内核的compat机制为 32 位应用程序适配 64 位内核接口,通过适配器转换数据结构(如struct stat)和系统调用参数,确保 32 位程序在 64 位系统上正常运行。 - 输入子系统(input) :输入设备(键盘、触摸屏、游戏杆)的硬件接口各异,输入子系统通过
struct input_dev适配器将不同设备的事件转换为统一的输入事件(如EV_KEY、EV_ABS),供用户态程序(如 Xorg)使用。 - 电源管理适配(PM) :不同硬件设备的电源管理接口(如 ACPI、Device Tree)通过电源管理适配器统一为
struct dev_pm_ops接口,使内核电源管理框架能一致地操作所有设备。
五、实现注意事项
- 明确接口边界:清晰划分目标接口(客户端期望)和适配者接口(已有实现),避免接口模糊导致适配逻辑复杂。
- 最小适配原则:仅适配必要的接口和参数,避免过度封装(如无需转换的参数直接透传)。
- 参数转换安全 :处理不同类型的参数(如
int与size_t、指针与句柄)时,需确保类型转换安全(如范围检查、对齐处理),避免溢出或内存错误。 - 错误码统一:适配者和目标接口的错误码可能不同,需在适配器中统一错误码(如将第三方库的错误码映射为系统标准错误码)。
- 避免过度适配:若适配逻辑过于复杂(如需模拟大量未实现的接口),可能是设计不合理,应考虑重构而非强行适配。
六、补充说明
- 适配器模式与桥接模式的区别:适配器模式是解决已有接口不兼容问题 (事后补救),桥接模式是设计时分离抽象与实现(事前规划)。
- 类适配器与对象适配器:C 语言中因无继承,只能实现对象适配器(通过持有适配者指针),这也是最灵活的方式(支持动态切换适配者)。
- 适配器的局限性:适配器会引入额外的转换开销,对性能敏感的场景(如内核高频路径)需优化适配逻辑(如直接内联转换代码)。
通过适配器模式,C 语言程序(尤其是 Linux 内核)能够高效整合异构接口和模块,在不修改原有代码的前提下实现系统兼容与扩展,是系统集成和演进的关键技术。
|---------------------|
| 点击下面关注,获取最新最全分享,不迷路 |