05.C++设计模式-适配器模式

1. 模式定义

适配器模式(Adapter Pattern)是一种结构型设计模式,它允许不兼容的接口之间能够一起工作。适配器通过包装一个现有的类,将其接口转换为目标接口期望的形式。

2. 核心角色

  • 目标接口(Target):客户端期望使用的接口
  • 适配者(Adaptee):需要被适配的现有类,具有不兼容的接口
  • 适配器(Adapter):实现目标接口,内部持有适配者对象,完成接口转换
  • 客户端(Client):使用目标接口的类

3. 工作流程

调用
实现
持有并调用
转换调用
客户端 Client
目标接口 Target

request
适配器 Adapter

request
适配者 Adaptee

specificRequest

4. 实现方式

4.1 类适配器(使用多重继承)

<<interface>>
Target
+request()
Adaptee
+specificRequest()
Adapter
+request()

4.2 对象适配器(使用组合)

持有
<<interface>>
Target
+request()
Adaptee
+specificRequest()
Adapter
-adaptee: Adaptee
+request()
request() {

adaptee.specificRequest()

}

5. C++代码示例

场景:电源适配器(不同电压标准)
cpp 复制代码
#include <iostream>
#include <memory>
#include <string>

// ========== 目标接口:目标设备期望的接口 ==========
// 中国标准插座(220V)
class ChineseSocket {
public:
    virtual ~ChineseSocket() = default;
    virtual void charge_220v() const {
        std::cout << "使用220V电压充电" << std::endl;
    }
};

// ========== 适配者:需要被适配的类 ==========
// 美国标准插座(110V)
class USASocket {
public:
    void charge_110v() const {
        std::cout << "使用110V电压充电" << std::endl;
    }
};

// 日本标准插座(100V)
class JapanSocket {
public:
    void charge_100v() const {
        std::cout << "使用100V电压充电" << std::endl;
    }
};

// ========== 对象适配器(推荐方式) ==========
// 美国到中国的适配器
class USAAdapter : public ChineseSocket {
private:
    std::unique_ptr<USASocket> usa_socket_;
    
public:
    USAAdapter(std::unique_ptr<USASocket> socket) 
        : usa_socket_(std::move(socket)) {}
    
    void charge_220v() const override {
        std::cout << "[适配器] 将110V转换为220V: ";
        usa_socket_->charge_110v();
    }
};

// 日本到中国的适配器
class JapanAdapter : public ChineseSocket {
private:
    std::unique_ptr<JapanSocket> japan_socket_;
    
public:
    JapanAdapter(std::unique_ptr<JapanSocket> socket) 
        : japan_socket_(std::move(socket)) {}
    
    void charge_220v() const override {
        std::cout << "[适配器] 将100V转换为220V: ";
        japan_socket_->charge_100v();
    }
};

// ========== 类适配器(使用多重继承,C++特有) ==========
// 注意:多重继承在某些情况下可能导致歧义,谨慎使用
class MultiInheritAdapter : public ChineseSocket, private USASocket {
public:
    void charge_220v() const override {
        std::cout << "[类适配器] 将110V转换为220V: ";
        charge_110v();  // 调用基类的USASocket方法
    }
};

// ========== 客户端代码 ==========
class Client {
public:
    void use_chinese_socket(const ChineseSocket& socket) {
        socket.charge_220v();
    }
};

// ========== 实际应用场景:日志系统适配 ==========
// 场景2:不同的日志库接口适配

// 目标接口:统一的日志接口
class ILogger {
public:
    virtual ~ILogger() = default;
    virtual void log(const std::string& message) = 0;
    virtual void error(const std::string& message) = 0;
};

// 第三方日志库A(不兼容的接口)
class ThirdPartyLoggerA {
public:
    void write_log(const std::string& msg) {
        std::cout << "[LogA] " << msg << std::endl;
    }
    
    void write_error(const std::string& err) {
        std::cout << "[LogA ERROR] " << err << std::endl;
    }
};

// 第三方日志库B(不兼容的接口)
class ThirdPartyLoggerB {
public:
    void output(const std::string& level, const std::string& message) {
        std::cout << "[LogB-" << level << "] " << message << std::endl;
    }
};

// 适配器:适配LoggerA到标准接口
class LoggerAAdapter : public ILogger {
private:
    std::unique_ptr<ThirdPartyLoggerA> logger_;
    
public:
    LoggerAAdapter(std::unique_ptr<ThirdPartyLoggerA> logger) 
        : logger_(std::move(logger)) {}
    
    void log(const std::string& message) override {
        logger_->write_log(message);
    }
    
    void error(const std::string& message) override {
        logger_->write_error(message);
    }
};

// 适配器:适配LoggerB到标准接口
class LoggerBAdapter : public ILogger {
private:
    std::unique_ptr<ThirdPartyLoggerB> logger_;
    
public:
    LoggerBAdapter(std::unique_ptr<ThirdPartyLoggerB> logger) 
        : logger_(std::move(logger)) {}
    
    void log(const std::string& message) override {
        logger_->output("INFO", message);
    }
    
    void error(const std::string& message) override {
        logger_->output("ERROR", message);
    }
};

// ========== 主函数演示 ==========
int main() {
    std::cout << "========== 电源适配器示例 ==========" << std::endl;
    
    // 创建客户端
    Client client;
    
    // 直接使用中国插座
    std::cout << "\n1. 直接使用中国插座:" << std::endl;
    ChineseSocket chinese_socket;
    client.use_chinese_socket(chinese_socket);
    
    // 使用美国插座(通过适配器)
    std::cout << "\n2. 使用美国插座(通过适配器):" << std::endl;
    auto usa_socket = std::make_unique<USASocket>();
    USAAdapter usa_adapter(std::move(usa_socket));
    client.use_chinese_socket(usa_adapter);
    
    // 使用日本插座(通过适配器)
    std::cout << "\n3. 使用日本插座(通过适配器):" << std::endl;
    auto japan_socket = std::make_unique<JapanSocket>();
    JapanAdapter japan_adapter(std::move(japan_socket));
    client.use_chinese_socket(japan_adapter);
    
    // 使用多重继承适配器
    std::cout << "\n4. 使用多重继承适配器:" << std::endl;
    MultiInheritAdapter multi_adapter;
    client.use_chinese_socket(multi_adapter);
    
    std::cout << "\n========== 日志系统适配器示例 ==========" << std::endl;
    
    // 使用统一的日志接口
    std::vector<std::unique_ptr<ILogger>> loggers;
    
    // 添加LoggerA
    auto logger_a = std::make_unique<ThirdPartyLoggerA>();
    loggers.push_back(std::make_unique<LoggerAAdapter>(std::move(logger_a)));
    
    // 添加LoggerB
    auto logger_b = std::make_unique<ThirdPartyLoggerB>();
    loggers.push_back(std::make_unique<LoggerBAdapter>(std::move(logger_b)));
    
    // 统一调用日志接口
    for (auto& logger : loggers) {
        logger->log("这是一条普通日志");
        logger->error("这是一条错误日志");
        std::cout << std::endl;
    }
    
    return 0;
}

6. 异同点详细对比表

对比维度 类适配器 对象适配器
实现方式 多重继承(继承Target和Adaptee) 对象组合(继承Target,持有Adaptee)
耦合度 高(编译时绑定,继承关系) 低(运行时绑定,组合关系)
灵活性 低,无法动态改变适配者 高,可动态更换适配者对象
代码复杂度 较高(需要处理多重继承问题) 较低(清晰的组合关系)
内存占用 较小(无额外指针) 较大(需要存储适配者指针)
重写适配者行为 容易(通过虚函数重写) 困难(需要创建适配者子类)
适配多个适配者 容易(多继承多个类) 容易(组合多个对象)
适配适配者子类 自动支持(直接继承) 需要传入具体子类对象
语言支持 需要支持多重继承的语言 几乎所有OOP语言都支持
C++特有考虑 需注意菱形继承问题 无特殊问题

7. 应用场景

适配器模式

应用场景
系统集成
新旧系统对接
第三方库集成
API版本兼容
数据转换
不同格式转换
数据结构适配
单位转换
遗留系统改造
封装遗留代码
平滑升级过渡
保持接口稳定
多平台兼容
不同平台API
跨平台开发
硬件抽象层
框架适配
STL容器适配器
智能指针适配
回调函数适配

典型应用场景详解:
  1. 系统集成

    • 新旧系统API不兼容时
    • 集成第三方服务或库
    • 不同系统间的数据交换
  2. 遗留系统维护

    • 替换或升级部分模块时保持接口不变
    • 封装复杂或混乱的遗留代码
  3. 多平台开发

    • 统一不同操作系统的API差异
    • 适配不同硬件设备的接口
  4. 框架和库的使用

    • STL中的std::stackstd::queue(基于deque适配)
    • 日志框架适配不同的日志库
    • 数据库访问层适配不同数据库

8. 优缺点

适配器模式
优点
缺点
提高复用性

重用现有类
增加透明性

客户端无感知
灵活性高

可动态切换适配器
符合开闭原则

不修改原有代码
增加代码复杂度
过多使用可能混乱

系统结构
C++类适配器

需要多重继承

9. 与其他模式的关系

  • 桥接模式:适配器用于改变接口,桥接用于分离抽象和实现
  • 装饰器模式:适配器改变对象接口,装饰器增强对象功能
  • 外观模式:适配器改变单个对象的接口,外观为子系统提供统一接口
  • 代理模式:适配器改变接口,代理保持相同接口

10. 最佳实践建议

  1. 优先使用对象适配器而非类适配器(避免多重继承的复杂性)
  2. 适配器不应过于复杂,如果转换逻辑复杂,考虑重新设计
  3. 适度使用,不要为了适配而适配,在设计阶段就考虑接口统一
  4. 文档化适配逻辑,清晰说明接口转换的规则

适配器模式是一个实用且常用的设计模式,在保持系统整洁和模块化方面发挥着重要作用。

相关推荐
旖-旎1 分钟前
《LeetCode647 回文子串 || LeetCode 5 最长回文子串》
c++·算法·leetcode·动态规划·哈希算法
Darkwanderor41 分钟前
对Linux的进程控制的研究
linux·运维·c++
云泽8081 小时前
从零吃透 C++ 异常:抛出捕获、栈展开、异常重抛与编码规范详解
开发语言·c++·代码规范
REDcker1 小时前
libdatachannel 快速入门
c++·webrtc·datachannel
second603 小时前
第一部分:快速上手 —— 建立 C++ 基本语法与编程范式
开发语言·c++
郝学胜-神的一滴3 小时前
算法实战:最小k个数——大顶堆的优雅解法
开发语言·数据结构·c++·python·程序人生·算法·排序算法
bksczm3 小时前
linux之线程概念和控制
linux·开发语言·c++
hehelm4 小时前
Linux网络编程—TCP字典翻译系统
linux·开发语言·网络·c++·tcp/ip
hansang_IR4 小时前
【记录】「SCOI2016」三道模拟赛/26.7.12
c++·算法
NG4774 小时前
【软件设计与体系结构】-策略设计模式
java·设计模式·软件工程