适配器模式(Adapter Pattern)是《Head First设计模式》中介绍的经典结构型设计模式之一。它的核心思想就像现实生活中的电源适配器——让原本不兼容的接口能够协同工作。在软件开发中,我们经常会遇到需要整合新旧系统、对接第三方库或者统一不同模块接口的情况,这时候适配器模式就能大显身手。
我在实际项目中第一次深刻理解适配器模式的价值,是在对接一个第三方支付系统时。我们的系统设计使用的是统一的支付接口,但新接入的支付宝SDK却有着完全不同的方法签名和调用方式。这时候通过实现一个支付适配器,既保持了系统架构的整洁,又避免了大规模重构。
适配器模式主要分为两种实现方式:
典型的适配器模式包含以下角色:
用C++代码表示基本结构:
cpp复制// 目标接口
class Target {
public:
virtual ~Target() = default;
virtual void request() = 0;
};
// 适配者
class Adaptee {
public:
void specificRequest() {
// 原有的具体实现
}
};
// 适配器(对象适配器方式)
class Adapter : public Target {
private:
Adaptee* adaptee;
public:
Adapter(Adaptee* a) : adaptee(a) {}
void request() override {
adaptee->specificRequest();
}
};
在C++中,我们可以选择不同的实现方式:
| 特性 | 类适配器 | 对象适配器 |
|---|---|---|
| 实现方式 | 多重继承 | 组合 |
| 灵活性 | 较低(编译时绑定) | 较高(运行时可替换适配者) |
| 耦合度 | 较高(直接继承适配者) | 较低(通过指针/引用关联) |
| 适用场景 | 适配者接口简单且稳定 | 适配者接口复杂或可能变化 |
| C++实现难度 | 需要支持多重继承 | 更符合现代C++设计理念 |
提示:现代C++开发中更推荐使用对象适配器,它更符合组合优于继承的原则,也更容易进行单元测试。
使用C++11及以上版本可以实现更安全的适配器:
cpp复制#include <memory>
// 使用智能指针的适配器实现
class ModernAdapter : public Target {
private:
std::unique_ptr<Adaptee> adaptee;
public:
ModernAdapter(std::unique_ptr<Adaptee>&& a)
: adaptee(std::move(a)) {}
void request() override {
if(adaptee) {
adaptee->specificRequest();
}
}
};
关键改进点:
unique_ptr管理资源,避免内存泄漏对于需要适配多种类型的情况,可以使用模板:
cpp复制template<typename T>
class GenericAdapter : public Target {
private:
T adaptee;
public:
GenericAdapter(T&& a) : adaptee(std::forward<T>(a)) {}
void request() override {
adaptee.specificRequest();
}
};
使用示例:
cpp复制Adaptee adaptee;
GenericAdapter<Adaptee> adapter(std::move(adaptee));
我曾参与过一个将旧版日志系统整合到新架构中的项目。旧版日志接口是这样的:
cpp复制class LegacyLogger {
public:
void writeToLog(const char* msg, int priority);
};
而新系统要求统一的日志接口:
cpp复制class NewLogger {
public:
virtual void log(const std::string& message, LogLevel level) = 0;
};
适配器实现:
cpp复制class LoggerAdapter : public NewLogger {
private:
LegacyLogger legacyLogger;
LogLevel convertPriority(int oldPriority) {
// 优先级转换逻辑
}
public:
void log(const std::string& message, LogLevel level) override {
int oldPriority = /* 反向转换逻辑 */;
legacyLogger.writeToLog(message.c_str(), oldPriority);
}
};
另一个典型场景是适配不同图形库。比如在游戏开发中,可能需要在DirectX和OpenGL之间切换:
cpp复制// 统一的渲染接口
class Renderer {
public:
virtual void drawMesh(const Mesh& mesh) = 0;
};
// OpenGL实现
class OpenGLRenderer : public Renderer {
// 实现细节...
};
// DirectX适配器
class DirectXAdapter : public Renderer {
private:
DirectXRenderer dxRenderer;
public:
void drawMesh(const Mesh& mesh) override {
// 将Mesh转换为DirectX可识别的格式并渲染
DXMesh dxMesh = convertMesh(mesh);
dxRenderer.renderDXMesh(dxMesh);
}
};
适配器模式会引入一定的间接调用开销,在性能敏感的场景需要注意:
虚函数开销:每个适配的调用都需要经过虚函数表查找
对象创建开销:频繁创建/销毁适配器对象可能影响性能
内存占用:适配器会增加额外的内存使用
CRTP优化示例:
cpp复制template<typename T>
class CRTPAdapter : public Target {
public:
void request() override {
static_cast<T*>(this)->specificRequest();
}
};
class OptimizedAdaptee : public CRTPAdapter<OptimizedAdaptee> {
public:
void specificRequest() {
// 具体实现
}
};
适配器模式的测试需要特别关注接口转换的正确性:
单元测试:验证适配器是否正确转换了所有方法调用
集成测试:验证适配器在完整系统中的行为
Google Test示例:
cpp复制TEST(AdapterTest, BasicConversion) {
MockAdaptee adaptee;
EXPECT_CALL(adaptee, specificRequest()).Times(1);
Adapter adapter(&adaptee);
adapter.request(); // 应该调用adaptee.specificRequest()
}
TEST(AdapterTest, NullAdaptee) {
Adapter adapter(nullptr);
EXPECT_NO_THROW(adapter.request()); // 应该安全处理空指针
}
问题:适配者接口与目标接口差异较大,简单包装无法满足需求。
解决方案:
问题:有时需要双向转换(A→B和B→A)。
解决方案:
cpp复制class BidirectionalAdapter : public TargetA, public TargetB {
private:
Adaptee* adaptee;
public:
// 实现两个目标接口的方法...
};
问题:项目中适配器类过多,难以维护。
解决方案:
与外观模式:
与装饰器模式:
与桥接模式:
cpp复制class FunctionAdapter : public Target {
private:
std::function<void()> adaptedFunc;
public:
FunctionAdapter(std::function<void()> f) : adaptedFunc(f) {}
void request() override {
if(adaptedFunc) adaptedFunc();
}
};
cpp复制class MoveableAdapter : public Target {
private:
Adaptee adaptee; // 直接包含而非指针
public:
MoveableAdapter(Adaptee&& a) : adaptee(std::move(a)) {}
// 实现移动构造函数
MoveableAdapter(MoveableAdapter&& other) noexcept
: adaptee(std::move(other.adaptee)) {}
};
cpp复制class AnyAdapter : public Target {
private:
struct Concept {
virtual ~Concept() = default;
virtual void invoke() = 0;
};
template<typename T>
struct Model : Concept {
T adaptee;
Model(T&& a) : adaptee(std::forward<T>(a)) {}
void invoke() override { adaptee.specificRequest(); }
};
std::unique_ptr<Concept> concept;
public:
template<typename T>
AnyAdapter(T&& adaptee)
: concept(std::make_unique<Model<T>>(std::forward<T>(adaptee))) {}
void request() override { concept->invoke(); }
};
何时使用适配器模式:
何时避免使用:
C++特定建议:
在实际项目中,我发现适配器模式最适合用于对接第三方库和整合遗留代码。但要注意避免过度使用——如果发现自己在写大量适配器,可能是系统接口设计需要重新审视的信号。