一、Adapter模式是个啥?
适配器模式在软件开发的江湖里,可以说用途是非常广的。下面几个经典的场景来说明适配器模式的使用。
场景一
场景二
假如你正在开发一款股票市场监测程序, 它会从不同来源下载 XML 格式的股票数据, 然后向用户呈现出美观的图表。
在开发过程中, 你决定在程序中整合一个第三方智能分析函数库。 但是遇到了一个问题, 那就是分析函数库只兼容 JSON 格式的数据。
整合分析函数库之前的程序结构
你无法 “直接” 使用分析函数库, 因为它所需的输入数据格式与你的程序不兼容。
你可以修改程序库来支持 XML。 但是, 这可能需要修改部分依赖该程序库的现有代码。 甚至还有更糟糕的情况, 你可能根本没有程序库的源代码, 从而无法对其进行修改。
你可以创建一个适配器。 这是一个特殊的对象, 能够转换对象接口, 使其能与其他对象进行交互。
适配器模式通过封装对象将复杂的转换过程隐藏于幕后。 被封装的对象甚至察觉不到适配器的存在。 例如, 你可以使用一个将所有数据转换为英制单位 (如英尺和英里) 的适配器封装运行于米和千米单位制中的对象。
适配器不仅可以转换不同格式的数据, 其还有助于采用不同接口的对象之间的合作。 它的运作方式如下:
适配器实现与其中一个现有对象兼容的接口。
现有对象可以使用该接口安全地调用适配器方法。
适配器方法被调用后将以另一个对象兼容的格式和顺序将请求传递给该对象。
有时你甚至可以创建一个双向适配器来实现双向转换调用。
二、适配器模式代码示例
#include <iostream>
#include <string>
#include <sal.h>
using namespace std;
// 需要适配的接口
class Target {
public:
virtual void request() = 0;
~Target(){
std::cout << __FUNCTION__ << std::endl;
}
};
// 目标接口的实现
class ConcreteTarget : public Target {
public:
void request() override {
std::cout << "ConcreteTarget::request() called." << std::endl;
}
};
// 被适配的接口
class Adaptee {
public:
virtual void adapteeRequest() = 0;
};
// 被适配接口的实现
class ConcreteAdaptee : public Adaptee {
public:
void adapteeRequest() override {
std::cout << "ConcreteAdaptee::adapteeRequest() called." << std::endl;
}
};
// 适配器
class Adapter : public ConcreteTarget {
public:
Adapter( _In_ Adaptee* adaptee) : m_adaptee(adaptee) {}
void request() override {
m_adaptee->adapteeRequest();
}
private:
shared_ptr<Adaptee> m_adaptee;
};
int main() {
/// 使用适配器
shared_ptr<Target> client0(new Adapter(new ConcreteAdaptee));
client0->request(); // 输出:ConcreteAdaptee::adapteeRequest() called
/// 使用非适配器
shared_ptr<Target> client1(new ConcreteTarget);
client1->request(); // 输出:ConcreteTarget::request() called.
return 0;
}
三、适用的场景
当你希望使用某个类, 但是其接口与其他代码不兼容时, 可以使用适配器类。
适配器模式允许你创建一个中间层类, 其可作为代码与遗留类、 第三方类或提供怪异接口的类之间的转换器。
如果您需要复用这样一些类, 他们处于同一个继承体系, 并且他们又有了额外的一些共同的方法, 但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性。
你可以扩展每个子类, 将缺少的功能添加到新的子类中。 但是, 你必须在所有新子类中重复添加这些代码, 这样会使得代码有坏味道。
将缺失功能添加到一个适配器类中是一种优雅得多的解决方案。 然后你可以将缺少功能的对象封装在适配器中, 从而动态地获取所需功能。 如要这一点正常运作, 目标类必须要有通用接口, 适配器的成员变量应当遵循该通用接口。 这种方式同装饰模式非常相似。
四、参考
《适配器模式》 https://refactoringguru.cn/design-patterns/adapter