在C++开发中,我们经常会遇到接口不兼容的问题。想象一下你手头有一个功能完善的第三方库,但它的接口设计与你的现有系统格格不入——就像欧标插头无法直接插入美标插座。这时候,适配器模式(Adapter Pattern)就派上用场了。
适配器模式属于结构型设计模式,它通过创建一个中间层来协调两个不兼容的接口。就像现实生活中的电源转换器,它不改变原有设备的功能,只是让原本无法直接协作的组件能够无缝对接。
在C++中实现适配器模式主要有两种经典方式:
但实际工程中,我们会根据具体场景对经典模式进行各种变体和优化。这些变体往往能更好地解决特定问题,提升代码的灵活性、可维护性和性能。
类适配器通过多重继承来实现接口适配。假设我们有一个现有的OldPrinter类,它有一个printDocument()方法,但我们需要适配到新的PrinterInterface接口:
cpp复制// 旧的打印机接口
class OldPrinter {
public:
void printDocument() {
cout << "Printing using old printer..." << endl;
}
};
// 新的打印机接口
class PrinterInterface {
public:
virtual void print() = 0;
virtual ~PrinterInterface() = default;
};
// 类适配器
class PrinterAdapter : public PrinterInterface, private OldPrinter {
public:
void print() override {
printDocument(); // 调用被适配者的方法
}
};
这种实现方式的优点是:
但缺点也很明显:
对象适配器采用组合方式,更符合"组合优于继承"的原则:
cpp复制class PrinterAdapter : public PrinterInterface {
private:
OldPrinter* oldPrinter;
public:
explicit PrinterAdapter(OldPrinter* printer) : oldPrinter(printer) {}
void print() override {
oldPrinter->printDocument();
}
~PrinterAdapter() {
delete oldPrinter;
}
};
对象适配器的优势:
劣势:
在C++模板的加持下,我们可以创建更灵活的泛型适配器:
cpp复制template <typename T>
class GenericAdapter : public PrinterInterface {
private:
T adaptee;
public:
template <typename... Args>
explicit GenericAdapter(Args&&... args)
: adaptee(std::forward<Args>(args)...) {}
void print() override {
adaptee.printDocument();
}
};
这种变体的优势:
printDocument()方法的类型使用示例:
cpp复制GenericAdapter<OldPrinter> adapter;
adapter.print();
有时我们需要同时适配多个接口:
cpp复制class MultiInterfaceAdapter : public PrinterInterface, public ScannerInterface {
private:
OldDevice oldDevice;
public:
void print() override {
oldDevice.legacyPrint();
}
void scan() override {
oldDevice.legacyScan();
}
};
这种变体在集成老旧设备或库时特别有用,可以一次性提供多个现代化接口。
结合现代C++的智能指针,我们可以创建更安全的适配器:
cpp复制class SmartAdapter : public PrinterInterface {
private:
std::unique_ptr<OldPrinter> printer;
public:
explicit SmartAdapter(std::unique_ptr<OldPrinter>&& p)
: printer(std::move(p)) {}
void print() override {
if (printer) {
printer->printDocument();
}
}
};
这种实现方式:
对于资源消耗大的被适配对象,可以采用延迟初始化:
cpp复制class LazyAdapter : public PrinterInterface {
private:
std::unique_ptr<OldPrinter> printer;
std::mutex mtx;
void ensureInitialized() {
std::lock_guard<std::mutex> lock(mtx);
if (!printer) {
printer = std::make_unique<OldPrinter>();
}
}
public:
void print() override {
ensureInitialized();
printer->printDocument();
}
};
这种变体特别适合:
我们可以创建适配器工厂来统一管理适配器的创建:
cpp复制class AdapterFactory {
public:
static std::unique_ptr<PrinterInterface> createPrinterAdapter(PrinterType type) {
switch (type) {
case PrinterType::Legacy:
return std::make_unique<PrinterAdapter>(new OldPrinter());
case PrinterType::Modern:
return std::make_unique<ModernPrinter>();
default:
throw std::invalid_argument("Unknown printer type");
}
}
};
这种组合方式:
我们可以让适配器在运行时选择不同的适配策略:
cpp复制class StrategyAdapter : public PrinterInterface {
private:
std::function<void()> printStrategy;
public:
void setStrategy(std::function<void()> strategy) {
printStrategy = std::move(strategy);
}
void print() override {
if (printStrategy) {
printStrategy();
}
}
};
使用示例:
cpp复制StrategyAdapter adapter;
OldPrinter oldPrinter;
NewPrinter newPrinter;
// 运行时切换策略
adapter.setStrategy([&oldPrinter] { oldPrinter.printDocument(); });
adapter.print();
adapter.setStrategy([&newPrinter] { newPrinter.print(); });
adapter.print();
C++标准库中广泛使用了适配器思想,例如:
std::stack和std::queue都是容器适配器std::reverse_iterator)std::bind)理解这些标准库适配器的实现可以帮助我们更好地设计自己的适配器。
适配器模式虽然灵活,但也带来一定的性能开销,特别是在高性能场景下需要考虑:
传统的适配器实现通常依赖虚函数,这会带来:
解决方案:
final关键字标记不需要进一步重写的虚函数对象适配器需要额外的内存存储被适配对象引用,在内存敏感场景下需要考虑:
适配器层可能破坏数据局部性,影响缓存性能。优化方法:
在设计适配器接口时,应该遵循:
适配器代码需要特别注意测试:
随着C++标准的发展,适配器模式也有新的实现方式:
cpp复制class FunctionAdapter {
private:
std::function<void()> func;
public:
template <typename F>
explicit FunctionAdapter(F&& f) : func(std::forward<F>(f)) {}
void execute() {
if (func) func();
}
};
这种适配器可以适配任何可调用对象,非常灵活。
cpp复制OldPrinter printer;
auto adapter = [&printer] { printer.printDocument(); };
adapter(); // 调用适配后的接口
lambda表达式提供了一种零成本的适配器实现方式。
C++20引入了concept,可以更好地约束泛型适配器:
cpp复制template <typename T>
concept Printable = requires(T t) {
{ t.printDocument() } -> std::same_as<void>;
};
template <Printable T>
class ConceptAdapter : public PrinterInterface {
private:
T adaptee;
public:
void print() override {
adaptee.printDocument();
}
};
这种适配器在编译期就能确保类型安全。