设计模式 | 适配器模式

适配器模式(Adapter Pattern) 是结构型设计模式中的连接器大师,它允许不兼容接口的类能够协同工作。本文将深入探索适配器模式的核心思想、实现技巧以及在C++中的高效实践,解决现实开发中的接口兼容性问题。

为什么需要适配器模式

在软件开发中,我们经常遇到接口不兼容的情况:

  • 遗留系统与现代框架的集成

  • 第三方库与自有系统的对接

  • 不同子系统之间的数据交换

  • API升级导致的接口变更

直接修改已有代码通常不可行:

  • 第三方库无法修改

  • 旧系统代码难以重构

  • 新老系统需要并行运行

  • 修改可能引入新错误

适配器模式通过创建中间转换层解决了这些问题,成为系统集成的关键桥梁。

适配器模式的核心概念

模式结构解析

[客户端] --> [目标接口]↑[适配器] ---> [被适配者]

关键角色定义

  1. 目标接口(Target)

    • 客户端期望的接口

  2. 被适配者(Adaptee)

    • 需要适配的现有组件

  3. 适配器(Adapter)

    • 转换接口的中间件

    • 实现目标接口

    • 包装被适配者

C++实现:电源适配器系统

让我们通过电源适配器系统展示适配器模式的实际应用:

#include <iostream>
#include <memory>
#include <cmath>
#include <iomanip>// ================ 目标接口:现代电源标准 ================
class ModernPowerOutlet {
public:virtual ~ModernPowerOutlet() = default;// 输出标准USB-C电源virtual void supplyPower() const = 0;// 获取电源规格virtual void getSpecs() const {std::cout << "标准: USB-C PD 3.1\n";std::cout << "电压: 5V-48V\n";std::cout << "功率: 最高240W\n";}
};// ================ 被适配者:旧式电源接口 ================
class LegacyPowerOutlet {
public:// 旧式交流电源输出void outputACPower(int voltage, int current) const {std::cout << "输出交流电: " << voltage << "V, " << current << "A\n";}// 获取旧式电源规格void showLegacySpecs() const {std::cout << "=== 旧式电源规格 ===\n";std::cout << "类型: 交流电\n";std::cout << "电压: 110V/220V\n";std::cout << "频率: 50/60Hz\n";}
};// ================ 类适配器(多重继承) ================
class PowerClassAdapter : public ModernPowerOutlet, private LegacyPowerOutlet {
public:void supplyPower() const override {std::cout << "转换交流到直流...\n";// 调用被适配者的方法outputACPower(220, 5); std::cout << "输出: USB-C 20V/5A (100W)\n";}void getSpecs() const override {ModernPowerOutlet::getSpecs();std::cout << "适配器类型: 类适配器\n";showLegacySpecs();}
};// ================ 对象适配器(组合方式) ================
class PowerObjectAdapter : public ModernPowerOutlet {
public:// 通过构造函数注入被适配对象explicit PowerObjectAdapter(LegacyPowerOutlet* outlet) : legacyOutlet_(outlet) {}void supplyPower() const override {std::cout << "转换交流到直流...\n";// 调用被适配者的方法legacyOutlet_->outputACPower(220, 5);std::cout << "输出: USB-C 20V/5A (100W)\n";}void getSpecs() const override {ModernPowerOutlet::getSpecs();std::cout << "适配器类型: 对象适配器\n";legacyOutlet_->showLegacySpecs();}private:LegacyPowerOutlet* legacyOutlet_;
};// ================ 客户端代码 ================
void chargeDevice(ModernPowerOutlet* outlet) {std::cout << "\n=== 开始充电 ===\n";outlet->getSpecs();outlet->supplyPower();std::cout << "设备充电中...\n";std::cout << "=== 充电完成 ===\n";
}int main() {// 创建被适配对象LegacyPowerOutlet legacyOutlet;// 使用类适配器std::cout << "===== 使用类适配器 =====\n";PowerClassAdapter classAdapter;chargeDevice(&classAdapter);// 使用对象适配器std::cout << "\n\n===== 使用对象适配器 =====\n";PowerObjectAdapter objectAdapter(&legacyOutlet);chargeDevice(&objectAdapter);return 0;
}

适配器模式的两种实现方式

1. 类适配器(继承方式)

class ClassAdapter : public Target, private Adaptee {
public:void request() override {// 转换并调用被适配者的方法specificRequest();}
};

特点

  • 使用多重继承

  • 适配器继承目标接口和被适配者

  • 静态绑定,编译时确定

  • 可能违反单一职责原则

2. 对象适配器(组合方式)

class ObjectAdapter : public Target {
public:ObjectAdapter(Adaptee* adaptee) : adaptee_(adaptee) {}void request() override {// 转换并调用被适配者的方法adaptee_->specificRequest();}private:Adaptee* adaptee_;
};

特点

  • 使用对象组合

  • 更灵活,支持运行时适配

  • 符合合成复用原则

  • 可以适配多个对象

  • 现代C++更推荐的方式

适配器模式的高级应用

1. 双向适配器

class BiDirectionalAdapter : public NewInterface, public OldInterface {
public:BiDirectionalAdapter(OldSystem* old, NewSystem* newSys) : oldSystem_(old), newSystem_(newSys) {}// 实现新接口void newMethod() override {oldSystem_->legacyMethod();}// 实现旧接口void legacyMethod() override {newSystem_->newMethod();}private:OldSystem* oldSystem_;NewSystem* newSystem_;
};

2. 参数适配器

class FunctionAdapter {
public:using NewSignature = void(int, double);explicit FunctionAdapter(std::function<void(std::string, float)> oldFunc): oldFunc_(oldFunc) {}void operator()(int a, double b) {// 转换参数并调用旧函数oldFunc_(std::to_string(a), static_cast<float>(b));}private:std::function<void(std::string, float)> oldFunc_;
};// 使用
void legacyPrint(std::string s, float f) {std::cout << s << " : " << f << "\n";
}int main() {FunctionAdapter adapter(legacyPrint);adapter(42, 3.14); // 自动转换参数类型
}

3. STL中的适配器

#include <vector>
#include <stack>
#include <queue>
#include <functional>// 容器适配器示例
std::stack<int> s;              // deque适配为栈
std::queue<double> q;           // deque适配为队列
std::priority_queue<int> pq;    // vector适配为优先队列// 函数对象适配器
auto negate = std::negate<int>();
auto plus10 = std::bind(std::plus<int>(), std::placeholders::_1, 10);
auto isEven = [](int x) { return x % 2 == 0; };
auto isOdd = std::not1(std::function<bool(int)>(isEven));

适配器模式的现实应用场景

1. 图形渲染接口适配

// OpenGL接口
class OpenGLRenderer {
public:void glClearColor(float r, float g, float b, float a) {std::cout << "OpenGL: 设置清除颜色 (" << r << ", " << g << ", " << b << ", " << a << ")\n";}// 其他OpenGL方法...
};// Vulkan接口适配器
class VulkanToOpenGLAdapter : public OpenGLRenderer {
public:void vkCmdClearColor(float r, float g, float b, float a) {// 将Vulkan命令转换为OpenGL调用glClearColor(r, g, b, a);}// 其他适配方法...
};

2. 支付系统集成

// 统一支付接口
class PaymentProcessor {
public:virtual void processPayment(double amount, const std::string& currency) = 0;
};// PayPal适配器
class PayPalAdapter : public PaymentProcessor {
public:PayPalAdapter(PayPalService* paypal) : paypal_(paypal) {}void processPayment(double amount, const std::string& currency) override {// 转换到PayPal要求的格式paypal_->sendPayment(amount * 100, currency + "_MICRO");}private:PayPalService* paypal_;
};// Stripe适配器
class StripeAdapter : public PaymentProcessor {
public:void processPayment(double amount, const std::string& currency) override {stripe::Charge::create({{"amount", static_cast<int>(amount * 100)},{"currency", currency}});}
};

3. 传感器数据标准化

// 统一传感器接口
class Sensor {
public:virtual double read() const = 0;virtual std::string getUnit() const = 0;
};// 温度传感器适配器
class TemperatureAdapter : public Sensor {
public:TemperatureAdapter(LegacyThermometer* thermo) : thermo_(thermo) {}double read() const override {// 华氏度转摄氏度return (thermo_->getFahrenheit() - 32) * 5/9;}std::string getUnit() const override {return "°C";}private:LegacyThermometer* thermo_;
};// 压力传感器适配器
class PressureAdapter : public Sensor {
public:PressureAdapter(BarometricSensor* sensor) : sensor_(sensor) {}double read() const override {// mmHg转hPareturn sensor_->getmmHg() * 1.33322;}std::string getUnit() const override {return "hPa";}private:BarometricSensor* sensor_;
};

适配器模式的五大优势

  1. 接口兼容性

    // 旧接口
    void legacyPrint(int x, float y);// 新接口适配器
    class PrinterAdapter {
    public:void print(double a, double b) {legacyPrint(static_cast<int>(a), static_cast<float>(b));}
    };
  2. 代码复用

    // 复用现有实现
    class NewServiceAdapter : public NewServiceInterface {
    public:NewServiceAdapter(OldService* service) : service_(service) {}void newMethod() override {service_->oldMethod(); // 复用旧实现}
    };
  3. 解耦系统

    // 客户端只依赖抽象接口
    void clientCode(DataProcessor* processor) {processor->process(data);
    }// 适配不同实现
    clientCode(new XMLProcessorAdapter(xmlParser));
    clientCode(new JSONProcessorAdapter(jsonParser));
  4. 增量迁移

    // 逐步替换旧系统
    class HybridSystem : public NewSystem {
    public:HybridSystem(OldSystem* old) : oldAdapter_(old) {}void newOperation() override {if (useOldSystem) {oldAdapter_.legacyOperation();} else {NewSystem::newOperation();}}
    };
  5. 多平台支持

    // 跨平台文件系统适配
    class FileSystemAdapter {
    public:#ifdef _WIN32WindowsFileSys winFS;#elif __APPLE__MacFileSys macFS;#elif __linux__LinuxFileSys linuxFS;#endifstd::string readFile(const std::string& path) {#ifdef _WIN32return winFS.readWinFile(path);#elif __APPLE__return macFS.readMacFile(path);// ...#endif}
    };

适配器模式的最佳实践

1. 优先使用对象适配器

// 更灵活,支持运行时配置
class FlexibleAdapter : public Target {
public:FlexibleAdapter(Adaptee* adaptee) : adaptee_(adaptee) {}// 实现目标接口...private:Adaptee* adaptee_; // 组合优于继承
};

2. 使用智能指针管理资源

class SafeAdapter : public Target {
public:SafeAdapter(std::shared_ptr<Adaptee> adaptee) : adaptee_(std::move(adaptee)) {}// 实现目标接口...private:std::shared_ptr<Adaptee> adaptee_;
};// 使用
auto adaptee = std::make_shared<LegacyComponent>();
SafeAdapter adapter(adaptee);

3. 保持适配器轻量级

// 只包含必要的转换逻辑
class MinimalAdapter : public NewInterface {
public:MinimalAdapter(OldComponent* comp) : comp_(comp) {}void newMethod() override {// 只做必要转换comp_->oldMethod();}// 不实现不需要的方法
};

4. 适配器命名规范

// 清晰表达适配关系
class LegacyToModernAdapter   // 旧系统到新系统
class XMLToJSONAdapter       // XML到JSON转换
class FahrenheitToCelsiusAdapter // 温度单位转换
class PayPalPaymentAdapter   // PayPal支付适配

适配器模式与其他模式的关系

模式关系区别
装饰器模式都使用包装装饰器添加功能,适配器转换接口
外观模式都简化接口外观定义新接口,适配器复用现有接口
桥接模式都解耦抽象与实现桥接预先设计,适配器事后补救
代理模式都使用间接访问代理控制访问,适配器转换接口

组合使用示例

// 适配器 + 工厂模式
class PaymentAdapterFactory {
public:static PaymentProcessor* createAdapter(PaymentType type) {switch (type) {case PAYPAL: return new PayPalAdapter(new PayPalService);case STRIPE: return new StripeAdapter;case CRYPTO: return new CryptoPaymentAdapter;default: throw std::invalid_argument("不支持的支付类型");}}
};// 客户端代码
auto processor = PaymentAdapterFactory::createAdapter(PAYPAL);
processor->processPayment(99.99, "USD");

应用案例

1. 数据库访问层适配

// 统一数据库接口
class Database {
public:virtual void execute(const std::string& query) = 0;
};// MySQL适配器
class MySQLAdapter : public Database {
public:MySQLAdapter(MySQLConn* conn) : conn_(conn) {}void execute(const std::string& query) override {conn_->mysql_exec(query.c_str());}
};// PostgreSQL适配器
class PostgresAdapter : public Database {
public:void execute(const std::string& query) override {PGresult* res = PQexec(conn_, query.c_str());// 处理结果...}
};// SQLite适配器
class SQLiteAdapter : public Database {
public:void execute(const std::string& query) override {sqlite3_exec(db_, query.c_str(), nullptr, nullptr, nullptr);}
};

2. 日志系统集成

// 统一日志接口
class Logger {
public:virtual void log(LogLevel level, const std::string& message) = 0;
};// Log4cpp适配器
class Log4cppAdapter : public Logger {
public:Log4cppAdapter(log4cpp::Category* cat) : category_(cat) {}void log(LogLevel level, const std::string& msg) override {switch (level) {case DEBUG: category_->debug(msg); break;case INFO: category_->info(msg); break;case WARN: category_->warn(msg); break;case ERROR: category_->error(msg); break;}}
};// Boost.Log适配器
class BoostLogAdapter : public Logger {
public:void log(LogLevel level, const std::string& msg) override {switch (level) {case DEBUG: BOOST_LOG_TRIVIAL(debug) << msg; break;case INFO: BOOST_LOG_TRIVIAL(info) << msg; break;case WARN: BOOST_LOG_TRIVIAL(warning) << msg; break;case ERROR: BOOST_LOG_TRIVIAL(error) << msg; break;}}
};

3. 跨平台UI框架

// 统一UI组件接口
class UIButton {
public:virtual void render(int x, int y, int width, int height) = 0;
};// Windows按钮适配器
class WinButtonAdapter : public UIButton {
public:void render(int x, int y, int w, int h) override {HWND button = CreateWindow("BUTTON", "Click", WS_VISIBLE | WS_CHILD,x, y, w, h, parent, NULL, NULL, NULL);}
};// macOS按钮适配器
class MacButtonAdapter : public UIButton {
public:void render(int x, int y, int w, int h) override {NSButton* button = [[NSButton alloc] initWithFrame:NSMakeRect(x, y, w, h)];[button setTitle:@"Click"];[parent addSubview:button];}
};// Linux按钮适配器
class LinuxButtonAdapter : public UIButton {
public:void render(int x, int y, int w, int h) override {GtkWidget* button = gtk_button_new_with_label("Click");gtk_fixed_put(GTK_FIXED(container), button, x, y);gtk_widget_set_size_request(button, w, h);}
};

适配器模式的挑战与解决方案

挑战解决方案
接口差异过大使用双向适配器或中间抽象层
适配器过多导致混乱使用工厂模式管理适配器创建
性能开销优化转换逻辑,缓存常用结果
链式适配问题避免多层嵌套,使用组合适配器

性能优化示例

class CachingAdapter : public Target {
public:void request() override {if (!cached) {result = adaptee_->computeExpensiveOperation();cached = true;}// 使用缓存结果processResult(result);}
};

总结

适配器模式是解决接口不兼容问题的终极武器,它通过:

  1. 无缝集成:连接不兼容的接口

  2. 代码复用:利用现有实现

  3. 解耦系统:减少组件间依赖

  4. 增量演进:支持系统逐步迁移

  5. 标准化接口:统一多样化实现

使用时机

  • 需要使用现有类但其接口不符合需求

  • 需要创建可复用的类与不相关类协同工作

  • 需要集成多个第三方库提供统一接口

  • 旧系统迁移过程中需要与新系统共存

"适配器模式不是修改齿轮的齿形,而是在齿轮间添加传动装置。它是面向对象设计中解决接口兼容问题的优雅方案。" - 设计模式实践者

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/87109.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/87109.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RTL 级机器人电机控制器的 FPGA 设计

借助Verilog&#xff0c;在FPGA中实现了带编码器的两台电机的电机控制系统的RTL级设计。 介绍 借助硬件描述语言 (HDL) Verilog 和 AMD Vivado 设计套件&#xff0c;在 AMD Spartan-7 FPGA 中实现带编码器的两个电机的控制器系统的 RTL 设计。 在这个项目中&#xff0c;使用了搭…

4_Flink CEP

Flink CEP 1、何为CEP&#xff1f; CEP&#xff0c;全称为复杂事件处理&#xff08;Complex Event Processing&#xff09;&#xff0c;是一种用于实时监测和分析数据流的技术。 CEP详细讲解&#xff1a; CEP是基于动态环境的事件流的分析技术&#xff0c;事件是状态变化&am…

容器基础知识2-K8s 和 Docker 的关系与管理逻辑详解

K8s 和 Docker 的关系与管理逻辑详解 一、先搞懂&#xff1a;Docker 和 K8s 分别是做什么的&#xff1f; Docker&#xff08;容器工具&#xff09;&#xff1a;好比「集装箱工厂」&#xff0c;负责把应用和依赖打包成标准化容器&#xff08;类似集装箱&#xff09;&#xff0…

QT MaintenanceTool 登录无法找到 QtAccount 凭据

亲测有效&#xff1a;QT6 Maintenance Tool 登录问题_qt6 maintenancetool-CSDN博客 将ini这个配置文件移出文件夹后&#xff0c;在切换自己账户登录即可

华为云Flexus+DeepSeek征文|利用华为云一键部署 Dify 平台并接入 DeepSeek 大模型,构建长篇文章生成助手

目录 前言 1 华为云一键部署 Dify 平台 1.1 华为云 Dify 平台介绍 1.2 部署过程介绍 1.3 登录 Dify 平台 2 接入华为云 ModelArts Studio 中的 DeepSeek 大模型 3 构建长篇文章生成助手 3.1 简要介绍长篇文章生成助手 3.2 开始节点 3.3 生成标题和大纲&#xff08;LL…

js的一些基础概念总结

1.变量声明 首先js变量声明有三种&#xff0c;var&#xff0c;const&#xff0c;let&#xff0c;这三种变量声明中我们第一优先使用const&#xff0c;需要改变这个值的时候我们用ley&#xff0c;var是尽量不去使用。 那么我们现在来总结一下三种声明变量的区别。首先是var let …

防 XSS和CSRF 过滤器(Filter)

会话管理存在问题&#xff1a; 1.服务集群部署或者是分布式服务如何实现会话共享 2.会话的不同存储地方的安全性问题 答&#xff1a; 会话共享 可以使用后端集中管理(redis)或者客户端管理 &#xff08;jwt&#xff09;&#xff1b; 存储安全性 这个还真的没有太好的方式&…

鸿蒙容器组件 WaterFlow、FlowItem解析:动态瀑布流布局实践

一、引言&#xff1a;不规则布局的智能化解决方案 在图片社交、电商导购、资讯聚合等现代应用场景中&#xff0c;瀑布流布局以其灵活的空间利用率和自然的视觉流动感成为界面设计的重要选择。鸿蒙提供的 WaterFlow 与 FlowItem 组件&#xff0c;通过智能布局算法与声明式语法&…

概率密度基本概念

概率密度&#xff08;Probability Density&#xff09;是概率论中用于描述随机变量分布的一种方式&#xff0c;特别适用于连续随机变量。它并不是一个概率值&#xff0c;而是表示单位范围内的概率大小或“浓度”。更具体地说&#xff0c;概率密度表示在某个特定值附近&#xff…

10-1 MySQL 索引优化与查询优化

10-1 MySQL 索引优化与查询优化 文章目录 10-1 MySQL 索引优化与查询优化1. 数据准备2. 索引失效案例2.1 索引字段&#xff1a;全值匹配最优2.2 索引字段&#xff1a;最佳左前缀法则2.3 主键插入顺序2.4 索引字段进行了&#xff1a;计算、函数、类型转换(自动或手动)导致索引失…

基于目标驱动的分布式敏捷开发

研究结论 风险对项目目标的影响 时间目标&#xff1a;需求管理不当&#xff08;如需求优先级不明确、多产品负责人需求冲突&#xff09;、架构变更导致的返工、跨站点协调问题&#xff08;如第三方依赖、通信基础设施不足&#xff09;是影响项目时间的主要风险因素。质量目标&…

高通手机跑AI系列之——穿衣试装算法

环境准备 手机 测试手机型号&#xff1a;Redmi K60 Pro 处理器&#xff1a;第二代骁龙8移动--8gen2 运行内存&#xff1a;8.0GB &#xff0c;LPDDR5X-8400&#xff0c;67.0 GB/s 摄像头&#xff1a;前置16MP后置50MP8MP2MP AI算力&#xff1a;NPU 48Tops INT8 &&…

opencv入门(5)图像像素的读写操作和算术运算

文章目录 1 图像遍历与修改1.1 使用数组1.2 使用指针 2 图像的算术运算2.1 一般算术操作2.2 算术API 1 图像遍历与修改 C中支持 数组遍历 和 指针方式遍历 1.1 使用数组 访问使用 image.at(row,col) 进行访问 如果是单通道灰度图&#xff0c;就使用image.at进行读取 如果是三…

Stable Diffusion入门-ControlNet 深入理解-第三课:结构类模型大揭秘——深度、分割与法线贴图

大家好,欢迎回到Stable Diffusion入门-ControlNet 深入理解系列的第三课! 在上一课中,我们深入探讨了 ControlNet 文件的命名规则,以及线条类 ControlNet模型的控制方法。如果你还没有看过第二篇,赶紧点这里补课:Stable Diffusion入门-ControlNet 深入理解 第二课:Contr…

喷油嘴深凹槽内轮廓测量的方法探究 —— 激光频率梳 3D 轮廓测量

引言 喷油嘴作为燃油喷射系统核心部件&#xff0c;其深凹槽内轮廓精度直接影响燃油雾化效果与发动机排放性能。喷油嘴深凹槽具有深径比大&#xff08;可达 30:1&#xff09;、孔径小&#xff08;φ0.5 - 2mm&#xff09;、表面质量要求高&#xff08;Ra≤0.2μm&#xff09;等…

上证ETF50期权交易规则一文详解

50ETF期权&#xff0c;首先这是期权交易&#xff0c;所以50ETF期权有期权交易的所有特征&#xff0c;其次&#xff0c;50ETF期权的标的对象是上证50&#xff0c;所以50ETF&#xff08;认购看涨&#xff09;期权的走势和上证50的走势是一样的。 行权时间&#xff1a; 在行权日当…

Oracle获取执行计划之10046 技术详解

Oracle 的 10046 事件是性能调优中最常用的工具之一&#xff0c;通过跟踪会话的 SQL 执行细节&#xff0c;生成包含执行计划、等待事件、绑定变量等信息的跟踪文件&#xff0c;帮助定位性能瓶颈。以下是技术详解&#xff1a; 一、10046 事件基础 10046 是 Oracle 内部事件&…

Linux 日志监控工具对比:从 syslog 到 ELK 实战指南

更多云服务器知识&#xff0c;尽在hostol.com 你有没有被 Linux 上满屏飞滚的日志整崩溃过&#xff1f;看着 /var/log 目录越来越肥&#xff0c;关键日志像大海捞针一样藏在里面&#xff0c;每次出故障就像拆盲盒&#xff0c;赌你能不能第一眼看出问题。 日志系统&#xff0c…

本地服务器部署后外网怎么访问不了?内网地址映射互联网上无法连接问题的排查

我的网站部署搭建在本地服务器上的&#xff0c;在内网可以正常访问&#xff0c;但是外网无法访问&#xff0c;该怎么排查&#xff1f;局域网内部经过路由器的&#xff0c;有设置了虚拟服务器转发规则&#xff0c;在互联网公网上还是无法访问服务器怎么办&#xff1f;相信很多人…

如何免费正确安装微软的office全家桶

记录一下如何正确安装微软的office全家桶 找到安装包傻瓜式安装 找到安装包 安装包在附件&#xff0c;大家可以自行进行下载 傻瓜式安装 操作一目了然&#xff0c;点你需要的就行了