适配器设计模式
定义
一种结构型设计模式,用于将一个类的接口转换成另一个客户端所期望的接口。 它允许不兼容的接口协同工作,使得原本由于接口不匹配而无法合作的类可以一起工作。
参与者
- 目标(Target):定义了客户端所期望的接口,适配器将目标接口转换为适配者的接口来满足客户端需求。
- 适配者(Adaptee):被适配的类或接口,其接口与目标接口不匹配。
- 适配器(Adapter):通过实现目标接口并持有适配者的实例来完成接口转换。适配器将客户端发出的请求转发给适配者,并将适配者的响应转换为符合目标接口的形式返回给客户端。
原理
适配器设计模式的核心思想是通过一个中间层(适配器) 来连接客户端和适配者,实现接口之间的转换。 这样,客户端就可以使用适配器提供的统一接口与适配者进行交互,而不需要关心适配者的具体实现细节。
示例
// 目标接口
interface Target {
request(): void;
}
// 适配者类
class Adaptee {
specificRequest(): void {
console.log("Adaptee's specific request");
}
}
// 适配器类
class Adapter implements Target {
private adaptee: Adaptee;
constructor(adaptee: Adaptee) {
this.adaptee = adaptee;
}
request(): void {
console.log("Adapter's request");
this.adaptee.specificRequest();
}
}
// 客户端代码
const adaptee = new Adaptee();
const adapter = new Adapter(adaptee);
adapter.request();
- 在上述示例中,我们有一个目标接口
Target,其中定义了客户端所期望的方法request()。 - 然后,创建一个适配者类
Adaptee,它具有一个不兼容的方法specificRequest()。 - 接下来,创建一个适配器类
Adapter,它实现了目标接口Target。 - 在适配器类内部,将适配者类的实例作为私有属性进行持有,并通过适配器的
request()方法将请求转发给适配者的specificRequest()方法。 - 最后,在客户端代码中创建适配者对象和适配器对象,并调用适配器的
request()方法来触发适配过程。 - 当客户端调用适配器的
request()方法时,适配器会将请求转发给适配者的specificRequest()方法,并在适配过程中进行必要的转换,以满足客户端所期望的接口。
对比桥接设计模式
实际上如果将适配者类中的方法specificRequest改名为request(即和适配者方法同名)就变成了桥接模式。虽然两者都是对已有的接口做文章,但是,桥接设计模式强调的是接口和实现的分离,而适配器设计模式强调的是接口的转换;于是前者接口名相同,而后者接口名必定不同。
对比外观设计模式
外观设计模式强调的是子系统的封装,并提供一个新的接口;适配器设计模式强调的是适配已有的接口。使用适配器设计模式的初衷是为了兼容不同的对象,这和对象本身复杂与否没有关系,就算这个对象再简单,如果接口和现有系统不兼容也是需要使用适配器转换的,反之,就算这个对象再复杂,但是与已有系统兼容,那也不用适配。此外,还有一点需要注意:通常情况下适配器设计模式遵守接口隔离原则,而外观本质上为了封装子系统的复杂性,一般是不遵守接口隔离原则的。
应用场景
在软件开发中,适配器可以用于将旧的接口适配为新的接口,以便与现代化的框架或库进行兼容。
- 浏览器兼容性:适配器模式常被用于处理不同浏览器之间的兼容性问题。通过创建适配器来封装对特定浏览器的特殊处理,使得代码可以在不同浏览器上正常运行。
- AJAX 请求:在使用 AJAX 进行数据请求时,可以使用适配器模式来统一不同后端接口的调用方式。适配器可以将不同的后端 API 转换为统一的 AJAX 请求接口,以便客户端代码可以使用相同的方式进行调用。
- 数据转换和格式化:适配器模式可以用于对数据进行转换和格式化。例如,将一个对象的属性映射到另一个对象的属性,或者将不同数据源返回的数据进行统一的格式化处理。
- 第三方库集成:当需要集成第三方库时,如果其接口与现有代码不匹配,可以使用适配器模式来进行对接。适配器可将第三方库的接口转换为现有代码所期望的接口。
- 日志记录器适配:在浏览器中,不同的日志记录库可能有不同的接口。适配器模式可以用于将这些不同的日志记录库统一为相同的接口,以方便代码中的日志记录。
广义的适配器设计模式
上面介绍的适配器设计模式强调了接口名称的转换,实际上,适配器设计模式在前端开发中的应用远不止如此:
- 适配框架:以发送网络请求为例,假如之前使用的是axios库,并且对其进行了封装,封装之后的请求方法名为http,发送get请求使用http.get方法;许久之后,axios已经很长时间没有维护了,需要换一个新的库fetch,那么如何在保证原来封装好的http.get方法有效的情况下,从axios迁移到fetch上去呢,这个时候就需要使用适配器设计模式来适配不同的框架了--
适配器设计模式常用于对第三方库的封装上。 - 适配参数:如果一个接口X的入参是一个数组:
['zs', 18, 'male],显然很难记住入参的顺序为:姓名、年龄、性别。这个时候使用适配器设计模式将入参设计成一个对象:option = {name: 'zs', age: 18, sex: 'male'}然后传给X的是option这个对象而不是原来的数组。此时,就不需要记住参数的顺序了。再优化一下,提供默认值:option = object.assign({name: 'unknown', age: -1, sex: 'unknown'}, {name: 'zs', age: 18, sex: 'male'}) - 适配数据:这个最常见了,后端返回给前端的数据格式常常存在:
数据完整但是格式不对的情况,这个时候需要对后端传回来的数据格式化,这个过程也可以看成是适配过程。