目录
一、问题背景
适配器模式:解决接口不兼容问题的桥梁
在生活中,我们经常会遇到这样的场景:假设你买了一台新款笔记本电脑,它只有 USB-C 接口,但你手头的设备(如鼠标、U 盘、显示器)都是传统的 USB-A 接口。为了让这些设备能够在新电脑上正常工作,你有两种选择:
(1)直接替换:将所有旧设备换成支持 USB-C 的新设备。这种方式虽然直接,但成本高且不现实。
(2)使用适配器:购买一个 USB-C 转 USB-A 的适配器,将旧设备连接到新电脑上。适配器就像一个中间人,将 USB-A 接口转换为 USB-C 接口,让新旧设备能够协同工作。
适配器模式的作用类似于这个 USB 适配器。它能够将一个类的接口转换为客户端期望的接口,从而解决接口不兼容的问题。
类似的问题在软件开发中也十分常见。例如,为了加快开发进度,我们可能会引入第三方库,但这个库的接口与应用程序中设计的接口不一致。为了让这些不兼容的类能够协同工作,适配器模式(Adapter Pattern)应运而生。它能够将一个类(第三方库)的接口转换为客户期望的接口。
二、适配器模式的两种类型
适配器模式分为两种类型,分别对应上述生活中的两种解决方案:
(1)类适配器模式:通过继承的方式复用现有接口。
(2)对象适配器模式:通过组合的方式复用现有接口。
模式结构
适配器模式的典型结构如图所示:
Adapter Pattern(对象模式)结构图
类适配器模式:适配器通过继承目标接口和现有接口,将两者结合起来。
对象适配器模式:适配器通过组合现有接口的实例,实现目标接口的功能。
三、实现代码
以下是适配器模式的完整实现代码(使用C++编写)。
类适配器模式
代码片段 1:Adapter.h
// Adapter.h
#ifndef _ADAPTER_H_ // 防止头文件被多次包含
#define _ADAPTER_H_
// 目标接口:定义客户端期望的接口
class Target {
public:
Target() = default; // 默认构造函数
virtual ~Target() = default; // 虚析构函数,确保派生类正确释放资源
virtual void Request() = 0; // 纯虚函数,定义目标接口
};
// 被适配者:已存在的类,其接口与目标接口不兼容
class Adaptee {
public:
Adaptee() = default; // 默认构造函数
~Adaptee() = default; // 默认析构函数
void SpecificRequest(); // 具体请求方法,与目标接口不兼容
};
// 类适配器:通过继承 Target 和 Adaptee 实现接口适配
class Adapter : public Target, private Adaptee {
public:
Adapter() = default; // 默认构造函数
~Adapter() override = default; // 默认析构函数
void Request() override; // 实现目标接口,委托给 Adaptee 的具体方法
};
#endif //~_ADAPTER_H_
代码片段 2:Adapter.cpp
// Adapter.cpp
#include "Adapter.h"
#include <iostream>
// Target 的默认实现
Target::Target() = default;
Target::~Target() = default;
// Adaptee 的默认实现
Adaptee::Adaptee() = default;
Adaptee::~Adaptee() = default;
// Adaptee 的具体请求方法
void Adaptee::SpecificRequest() {
std::cout << "Adaptee::SpecificRequest" << std::endl;
}
// Adapter 的默认实现
Adapter::Adapter() = default;
Adapter::~Adapter() = default;
// Adapter 的 Request 实现:委托给 Adaptee 的 SpecificRequest
void Adapter::Request() {
this->SpecificRequest(); // 调用 Adaptee 的方法
}
对象适配器模式
代码片段 1:Adapter.h
// Adapter.h
#ifndef _ADAPTER_H_ // 防止头文件被多次包含
#define _ADAPTER_H_
// 目标接口:定义客户端期望的接口
class Target {
public:
Target() = default; // 默认构造函数
virtual ~Target() = default; // 虚析构函数,确保派生类正确释放资源
virtual void Request() = 0; // 纯虚函数,定义目标接口
};
// 被适配者:已存在的类,其接口与目标接口不兼容
class Adaptee {
public:
Adaptee() = default; // 默认构造函数
~Adaptee() = default; // 默认析构函数
void SpecificRequest(); // 具体请求方法,与目标接口不兼容
};
// 对象适配器:通过组合 Adaptee 实现接口适配
class Adapter : public Target {
public:
explicit Adapter(Adaptee* ade); // 显式构造函数,接受 Adaptee 对象指针
~Adapter() override; // 析构函数
void Request() override; // 实现目标接口,委托给 Adaptee 的具体方法
private:
Adaptee* _ade; // 指向 Adaptee 对象的指针
};
#endif //~_ADAPTER_H_
代码片段 2:Adapter.cpp
// Adapter.cpp
#include "Adapter.h"
#include <iostream>
// Target 的默认实现
Target::Target() = default;
Target::~Target() = default;
// Adaptee 的默认实现
Adaptee::Adaptee() = default;
Adaptee::~Adaptee() = default;
// Adaptee 的具体请求方法
void Adaptee::SpecificRequest() {
std::cout << "Adaptee::SpecificRequest" << std::endl;
}
// Adapter 的构造函数:初始化 Adaptee 指针
Adapter::Adapter(Adaptee* ade) : _ade(ade) {}
// Adapter 的析构函数
Adapter::~Adapter() {
delete _ade; // 释放 Adaptee 对象
}
// Adapter 的 Request 实现:委托给 Adaptee 的 SpecificRequest
void Adapter::Request() {
_ade->SpecificRequest(); // 调用 Adaptee 的方法
}
代码片段 3:main.cpp
// main.cpp
#include "Adapter.h"
#include <iostream>
int main(int argc, char* argv[]) {
// 创建被适配者对象
Adaptee* ade = new Adaptee();
// 创建适配器对象,并传入被适配者对象
Target* adt = new Adapter(ade);
// 调用目标接口,实际执行的是 Adaptee 的方法
adt->Request();
// 释放资源
delete adt; // adt 的析构函数会释放 ade
return 0;
}
代码说明
适配器模式的实现相对简单,但其中有两个重要概念需要明确:
(1)接口继承:子类通过继承获得父类的接口。
(2)实现继承:子类通过继承获得父类的实现。
在类适配器模式中,适配器通过`private`继承`Adaptee`获得实现继承的效果,同时通过`public`继承`Target`获得接口继承的效果。而在对象适配器模式中,适配器通过组合`Adaptee`的实例来实现目标接口的功能。
四、总结讨论
适配器模式的核心在于解决接口不兼容的问题。它通过以下两种方式实现:
类适配器模式:通过多重继承实现接口适配。
对象适配器模式:通过对象组合实现接口适配。
在C++中,`public`继承既是接口继承又是实现继承,而`private`继承则仅用于实现继承。通过纯抽象基类可以模拟接口继承,但其效果不如Java中的`interface`纯粹。
适配器模式不仅能够解决接口不兼容的问题,还能够提高代码的复用性和系统的灵活性,是面向对象设计中不可或缺的重要模式。
以下是适配器模式的主要优缺点:
优点
(1)解决接口不兼容问题:
适配器模式的核心目标是让两个不兼容的接口能够协同工作,从而避免对现有代码的修改。
(2)提高代码复用性:
通过适配器,可以复用已有的类或第三方库,而无需修改其源代码。
(3)符合开闭原则:
适配器模式在不修改现有代码的情况下扩展系统功能,符合“对扩展开放,对修改关闭”的开闭原则。
(4)灵活性和可扩展性:
可以轻松添加新的适配器来支持新的接口或功能,而不会影响现有的适配器或客户端代码。
(5)解耦:
适配器模式将客户端与被适配者解耦,客户端只需要依赖目标接口,而不需要了解被适配者的具体实现。
(6)支持多种适配方式:
适配器模式分为类适配器和对象适配器两种实现方式,开发者可以根据需求选择合适的方式。
缺点
(1)增加复杂性:
引入适配器会增加类的数量,导致系统结构变得更加复杂,特别是在需要适配多个类时。
(2)过度使用会导致代码难以理解:
如果系统中大量使用适配器模式,可能会导致代码的可读性和可维护性下降,因为需要跟踪多个适配器和被适配者之间的关系。
(3)性能开销:
在对象适配器模式中,由于需要通过组合调用被适配者的方法,可能会引入额外的间接调用,导致一定的性能开销。
(4)不适合大规模重构:
如果系统中存在大量的接口不兼容问题,使用适配器模式可能会导致适配器类过多,此时可能需要考虑更彻底的重构方案。
(5)可能违背单一职责原则:
适配器类既需要实现目标接口,又需要调用被适配者的方法,可能会承担过多的职责,违背单一职责原则。
适用场景
适配器模式在以下场景中特别有用:
(1)集成第三方库:当需要使用第三方库,但其接口与现有系统不兼容时。
(2)复用旧代码:在系统升级或重构时,需要复用旧的类或模块,但其接口与新系统不匹配。
(3)统一接口:当需要为多个具有不同接口的类提供统一的接口时。
(4)解耦:当需要将客户端与具体的实现类解耦,以降低系统的耦合度。