适配器模式(Adapter)是结构型设计模式的一种,它的核心作用是将一个类的接口转换成客户端期望的另一个接口,使原本因接口不兼容而无法协作的类能够一起工作。这种模式类似于现实生活中的“转换器”(如电源适配器、USB转接头)。
一、核心角色与设计思想
适配器模式通过以下3个核心角色实现接口转换:
角色名称 | 核心职责 |
---|---|
目标接口(Target) | 客户端期望的接口,定义了客户端可以使用的方法。 |
适配者(Adaptee) | 已存在的、但接口与目标接口不兼容的类(需要被适配的类)。 |
适配器(Adapter) | 实现目标接口,并内部包含适配者的实例,通过转换调用适配者的方法,完成接口适配。 |
核心思想:不修改现有类(适配者)和客户端代码,通过引入适配器类,将适配者的接口转换为目标接口,实现两者的兼容。
二、实现方式
适配器模式有两种主要实现方式,根据适配器与适配者的关系不同区分:
1. 类适配器(继承实现)
适配器通过继承适配者类并实现目标接口,将适配者的方法转换为目标接口方法。
#include <iostream>#include <string>// 1. 目标接口(客户端期望的接口)class Target {public:virtual void request(const std::string& data) const = 0;virtual ~Target() = default;};// 2. 适配者(已存在的、接口不兼容的类)class Adaptee {public:// 适配者的方法与目标接口方法名/参数不同void specificRequest(const std::string& data, int format) const {std::cout << "适配者处理数据:" << data<< "(格式:" << format << ")" << std::endl;}};// 3. 类适配器(继承适配者 + 实现目标接口)class ClassAdapter : public Target, private Adaptee {public:// 实现目标接口方法,内部调用适配者的方法void request(const std::string& data) const override {// 转换参数并调用适配者的specificRequestspecificRequest(data, 1); // 假设格式1是客户端需要的格式}};// 客户端代码:只依赖目标接口void clientCode(const Target* target) {target->request("测试数据");}int main() {std::cout << "直接使用适配者(接口不兼容,无法调用)" << std::endl;// Adaptee adaptee;// adaptee.request("数据"); // 编译错误:Adaptee没有request方法std::cout << "\n使用类适配器:" << std::endl;Target* adapter = new ClassAdapter();clientCode(adapter); // 客户端通过适配器调用适配者delete adapter;return 0;}
2. 对象适配器(组合实现)
适配器通过实现目标接口并组合适配者对象(而非继承),完成接口转换。这是更常用的实现方式,符合“组合优于继承”原则。
#include <iostream>#include <string>// 1. 目标接口(与类适配器相同)class Target {public:virtual void request(const std::string& data) const = 0;virtual ~Target() = default;};// 2. 适配者(与类适配器相同)class Adaptee {public:void specificRequest(const std::string& data, int format) const {std::cout << "适配者处理数据:" << data<< "(格式:" << format << ")" << std::endl;}};// 3. 对象适配器(实现目标接口 + 组合适配者对象)class ObjectAdapter : public Target {private:Adaptee* adaptee; // 持有适配者对象的指针(组合关系)public:// 构造函数接收适配者对象ObjectAdapter(Adaptee* a) : adaptee(a) {}// 实现目标接口方法,通过适配者对象调用其方法void request(const std::string& data) const override {if (adaptee) {adaptee->specificRequest(data, 2); // 转换参数}}};// 客户端代码(与类适配器相同)void clientCode(const Target* target) {target->request("测试数据");}int main() {std::cout << "使用对象适配器:" << std::endl;Adaptee* adaptee = new Adaptee();Target* adapter = new ObjectAdapter(adaptee);clientCode(adapter); // 客户端通过适配器调用适配者// 注意释放顺序:先释放适配器,再释放适配者delete adapter;delete adaptee;return 0;}
三、代码解析
目标接口(Target):定义客户端需要的方法
request(const std::string&)
,是客户端与适配器交互的标准。适配者(Adaptee):存在一个
specificRequest
方法,但参数(多了int format
)和名称与目标接口不兼容,无法被客户端直接调用。适配器的作用:
- 类适配器通过继承
Adaptee
和实现Target
,在request
中调用父类的specificRequest
,完成参数转换。 - 对象适配器通过持有
Adaptee
指针,在request
中调用其specificRequest
,同样完成转换。
- 类适配器通过继承
客户端:仅依赖
Target
接口,无需知道Adaptee
的存在,实现了解耦。
四、两种实现方式的对比
对比维度 | 类适配器(继承) | 对象适配器(组合) |
---|---|---|
实现方式 | 继承适配者类,实现目标接口 | 实现目标接口,持有适配者对象 |
灵活性 | 只能适配特定适配者,无法适配其子类 | 可适配适配者及其子类,更灵活 |
代码耦合 | 与适配者类强耦合(继承关系) | 与适配者弱耦合(组合关系) |
方法重写 | 可重写适配者的方法 | 无法重写适配者的方法(需通过其他方式) |
适用场景 | 适配者类稳定,且不需要适配其子类 | 适配者可能变化,或需要适配多种子类 |
推荐优先使用对象适配器,因为组合比继承更灵活,且符合面向对象设计原则。
五、适用场景与优势
适用场景
- 集成 legacy 代码:当需要使用旧系统中的类,但它的接口与新系统不兼容时(如旧数据库驱动适配新ORM框架)。
- 复用第三方库:第三方库的接口不符合项目规范,通过适配器转换后再使用。
- 统一接口风格:将多个功能相似但接口不同的类,通过适配器统一为相同接口(如不同日志库统一为项目的日志接口)。
核心优势
- 兼容性:解决接口不兼容问题,使原本无法协作的类可以一起工作。
- 复用性:无需修改现有类(适配者)即可复用,保护现有代码。
- 透明性:客户端只需与目标接口交互,无需知道适配者的存在。
- 灵活性:通过更换不同适配器,可在不修改客户端的情况下切换适配者。
六、与其他模式的区别
模式 | 核心差异点 |
---|---|
适配器 | 转换现有接口,解决“接口不兼容”问题,不改变原有功能。 |
装饰器 | 不改变接口,为对象增加新功能,强调功能扩展。 |
外观(Facade) | 为复杂系统提供简化接口,关注“简化使用”,不涉及接口转换。 |
桥接(Bridge) | 分离抽象与实现,使两者可独立变化,解决“多维度变化”问题。 |
七、实践建议
- 优先对象适配器:除非必须继承适配者(如需要重写其方法),否则优先使用组合实现的对象适配器。
- 明确目标接口:设计清晰的
Target
接口,确保它能满足客户端需求。 - 适配多个适配者:一个适配器可适配多个相关的适配者(如同时适配数据读取和数据解析类)。
- 避免过度使用:不要为了适配而适配,若能直接修改接口,应优先修改而非引入适配器。
适配器模式是系统集成和代码复用的重要工具,尤其在维护 legacy 系统或整合第三方库时能显著降低耦合。其核心价值在于“在不破坏现有代码的前提下,实现接口兼容”,是结构型模式中解决“接口不匹配”问题的最佳方案。