设计模式之适配器模式在 C 语言中的应用(含 Linux 内核实例)

文章目录

        • 一、适配器模式的定义与核心价值
        • [二、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 语言无类,但可通过结构体封装适配逻辑 +函数指针转换接口实现适配器模式:

  1. 定义目标接口(Target):客户端期望的接口格式(如函数指针结构体);
  2. 明确适配者(Adaptee):需要被适配的已有接口(如第三方库函数、旧版接口);
  3. 实现适配器(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_readtmpfs_read)通过适配器转换为统一的 VFS 接口,使系统调用read无需关心底层文件系统的差异。

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

通过适配器模式,C 语言程序(尤其是 Linux 内核)能够高效整合异构接口和模块,在不修改原有代码的前提下实现系统兼容与扩展,是系统集成和演进的关键技术。

|---------------------|
| 点击下面关注,获取最新最全分享,不迷路 |

相关推荐
MobotStone43 分钟前
大数据:我们是否在犯一个大错误?
设计模式·架构
!停2 小时前
函数递归的应用
c语言
7***n753 小时前
前端设计模式详解
前端·设计模式·状态模式
兵bing3 小时前
设计模式-装饰器模式
设计模式·装饰器模式
feng_you_ying_li4 小时前
Detailed explanation of being processing
c语言
玖剹4 小时前
递归练习题(四)
c语言·数据结构·c++·算法·leetcode·深度优先·深度优先遍历
雨中飘荡的记忆5 小时前
深入理解设计模式之适配器模式
java·设计模式
雨中飘荡的记忆5 小时前
深入理解设计模式之装饰者模式
java·设计模式
老鼠只爱大米6 小时前
Java设计模式之外观模式(Facade)详解
java·设计模式·外观模式·facade·java设计模式