简单介绍
适配器模式是一种结构型设计模式 | 它能使接口不兼容的对象能够相互合作。(是适配各种不同接口的一个中间件)
基础理解
举个例子:当你引用了一个第三方数据分析库,但这个库的接口只能兼容JSON 格式的数据。但你需要它能接收核心类处理后产生的 XML 文件。
而且因为这个库不是你写的,所以你不能对其源码进行修改成接收XML格式的文件。
解决:这时候你就可以创建一个适配器。能够在转换对象的接口,将XML 格式转换为JSON 格式。你甚至可以创建一个双向适配器来实现双向转换调用来实现不通过接口对象之间的合作。
适配器不仅可以转换不同格式的数据, 其还有助于采用不同接口的对象之间的合作。
UML图
-
客户端 (Client) 是包含当前程序业务逻辑的类。
-
客户端接口 (Client Interface) 描述了其他类与客户端代码合作时必须遵循的协议。
-
服务 (Service) 中有一些功能类 (通常来自第三方或遗留系统)。 客户端与其接口不兼容, 因此无法直接调用其功能。
-
适配器 (Adapter) 是一个可以同时与客户端和服务交互的类: 它在实现客户端接口的同时封装了服务对象。
客户端代码只需通过接口与适配器交互即可, 无需与具体的适配器类耦合。
类适配器:适配器同时继承两个对象的接口(仅能在支持多重继承的编程语言中实现 c++)
类适配器只是继承了客户端接口和服务 的行为。将适配功能用重写的方式完成
实现方式
确保至少有两个类的接口不兼容:
- 一个无法修改 (通常是第三方、 遗留系统或者存在众多已有依赖的类) 的功能性服务类。
- 一个或多个将受益于使用服务类的客户端类。
#include <iostream>
#include <stdlib.h>
// 双端队列(已有实现的类和接口,第三方类等等)
class Deque
{
public:
void push_back(int x) { std::cout << "Deque push_back" << std::endl; }
void push_front(int x) { std::cout << "Deque push_front" << std::endl; }
void pop_back() { std::cout << "Deque pop_back" << std::endl; }
void pop_front() { std::cout << "Deque pop_front" << std::endl; }
};
//声明客户端接口, 描述客户端如何与服务交互。
// 顺序容器(客户端期望的类和接口)
class Sequence
{
public:
virtual void push(int x) = 0;
virtual void pop() = 0;
};
// 栈(adapter)
class Stack : public Sequence
{
public:
Stack(Deque *deque) : deque_(deque) {}
//依次实现适配器类客户端接口的所有方法。 适配器会将实际工作委派给服务对象, 自身只负责接口或数据格式的转换。
void push(int x) { deque_->push_back(x); }
void pop() { deque_->pop_back(); }
private:
Deque *deque_;
/* 双端队列
在适配器类中添加一个成员变量用于保存对于服务对象的引用。
通常情况下会通过构造函数对该成员变量进行初始化,
但有时在调用其方法时将该变量传递给适配器会更方便。*/
};
// 队列(adapter)
class Queue : public Sequence
{
public:
void push(int x) { deque.push_back(x); }
void pop() { deque.pop_front(); }
private:
Deque deque; // 双端队列
};
int main()
{
Deque *deque = new Deque();
//客户端必须通过客户端接口使用适配器。 这样一来, 你就可以在不影响客户端代码的情况下修改或扩展适配器。
Sequence *s1 = new Stack(deque); // 客户期望的stack
Sequence *s2 = new Queue(); // 客户期望的queue
s1->push(1);
s1->pop();
s2->push(1);
s2->pop();
delete s1;
delete s2;
system("pause");
return 0;
}
适合场景
- 可作为代码与遗留类、 第三方类或提供怪异接口的类之间的转换器。
- 假设有一个继承体系,其中有一个基类,以及多个子类 ,它们都有一些共同的方法,但不是所有子类都有额外的一些方法。现在您希望为这些额外的方法提供统一的接口给客户端使用。
创建一个适配器类 Adapter,实现客户端所期望的接口,然后将 基类的实例作为适配器类的成员变量。在适配器类中,您可以根据需要调用子类中的方法,并实现额外的方法。与装饰模式类似
与其他模式的关系
-
桥接模式通常会于开发前期进行设计, 使你能够将程序的各个部分独立开来以便开发。 另一方面, 适配器模式通常在已有程序中使用, 让相互不兼容的类能很好地合作。
-
适配器可以修改已有对象的接口, 装饰模式则能在不改变对象接口的前提下强化对象功能。
-
适配器能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰则能为对象提供加强的接口。
-
外观模式为现有对象定义了一个新接口, 适配器则会试图运用已有的接口。 适配器通常只封装一个对象, 外观通常会作用于整个对象子系统上。
桥接、 状态模式和策略模式 (在某种程度上包括适配器) 模式的接口非常相似。 实际上, 它们都基于组合模式——将工作委派给其他对象, 不过也各自解决了不同的问题。
模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。
优缺点
优点 | 缺点 |
---|---|
单一职责原则你可以将接口或数据转换代码从程序主要业务逻辑中分离。 | 代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其 |
开闭原则。 只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。 |