本文深入解析JavaScript中的适配器设计模式,通过微信公众号与支付宝SDK集成、jQuery事件监听兼容及Node.js回调转Promise等实际案例,展示如何将复杂的底层差异封装在统一接口之后。适配器模式能有效隐藏脏代码,提升业务逻辑清晰度与代码可维护性,是处理多平台接口差异的有效手段。
适配器模式概述
适配器设计模式在JavaScript开发中应用广泛,无论是处理跨浏览器兼容性问题,还是整合多个第三方SDK的调用,都能见到它的身影。
在日常开发实践中,开发者往往会在不经意间写出符合某种设计模式的代码。毕竟,设计模式是资深开发者从日常工作中总结提炼出的、旨在提升开发效率的模板,它们源于实际开发场景。
而在JavaScript领域,适配器模式显得尤为常见。
根据维基百科的定义:
在软件工程中,适配器模式是一种软件设计模式,允许从另一个接口使用现有类的接口。它通常用于使现有的类与其他类一起工作,而无需修改其源代码。
生活中的适配器
生活中的例子
生活中最常见的例子是电源插头适配器。由于世界各国插座标准各异,若需根据各国标准购买对应插头,不仅浪费钱财,而且不切实际;若自行携带插座并破坏墙体重新接线,更是不可行。
因此,插头适配器应运而生,它充当某种插头与另一种插座之间的中转设备,实现转换功能。

代码中的适配器
在代码中的体现
转向编程领域,个人理解如下:
将那些你不愿意看见的脏代码藏起来,你就可以说这是一个适配器
接入多个第三方SDK
接入多个第三方SDK
举一个日常开发中的例子:在进行微信公众号开发时,用到了微信的支付模块。经过长时间的联调,终于跑通了整个流程。正当准备开心打包上线代码时,得到了一个新需求: 我们需要接入支付宝公众号的SDK,也要有支付的流程
为了复用代码,我们可能会在脚本中写下这样的逻辑:
if (platform === 'wechat') {
wx.pay(config)
} else if (platform === 'alipay') {
alipay.pay(config)
}
// 做一些后续的逻辑处理
但通常情况下,各厂商SDK提供的接口调用方式存在差异。尽管有时文档可能引用同一份,致敬友商。
因此,针对上述代码可能是这样的:
// 并不是真实的参数配置,仅仅举例使用
const config = {
price: 10,
goodsId: 1
}
// 还有可能返回值的处理方式也不相同
if (platform === 'wechat') {
config.appId = 'XXX'
config.secretKey = 'XXX'
wx.pay(config).then((err, data) => {
if (err) // error
// success
})
} else if (platform === 'alipay') {
config.token = 'XXX'
alipay.pay(config, data => {
// success
}, err => {
// error
})
}
目前来看,代码接口还算清晰,只要写好注释,这还不是一个太糟糕的代码。
然而生活总是充满意外,我们又接到需求需要添加QQ的SDK、美团的SDK、小米的SDK,或者某些银行的SDK。
此时你的代码可能是这样的:
switch (platform) {
case 'wechat':
// 微信的处理逻辑
break
case 'QQ':
// QQ的处理逻辑
break
case 'alipay':
// 支付宝的处理逻辑
break
case 'meituan':
// 美团的处理逻辑
break
case 'xiaomi':
// 小米的处理逻辑
break
}
这已不是简单注释能够弥补的问题。随着代码越来越难维护,各种SDK千奇百怪的调用方式,如果其他人也要做类似的需求,还需重新编写此类代码,无疑浪费资源。
因此,为了保证业务逻辑清晰,同时避免后人重复踩坑,我们会将其拆分为一个公共函数:
找到其中某一个SDK的调用方式或者一个我们约定好的规则作为基准。
我们来告诉调用方,你要怎么怎么做,你能怎样获取返回数据,然后我们在函数内部进行这些各种肮脏的判断:
function pay ({
price,
goodsId
}) {
return new Promise((resolve, reject) => {
const config = {}
switch (platform) {
case 'wechat':
// 微信的处理逻辑
config.price = price
config.goodsId = goodsId
config.appId = 'XXX'
config.secretKey = 'XXX'
wx.pay(config).then((err, data) => {
if (err) return reject(err)
resolve(data)
})
break
case 'QQ':
// QQ的处理逻辑
config.price = price * 100
config.gid = goodsId
config.appId = 'XXX'
config.secretKey = 'XXX'
config.success = resolve
config.error = reject
qq.pay(config)
break
case 'alipay':
// 支付宝的处理逻辑
config.payment = price
config.id = goodsId
config.token = 'XXX'
alipay.pay(config, resolve, reject)
break
}
})
}
这样无论我们在什么环境下,只要我们的适配器支持,就可以按照我们约定好的通用规则进行调用,而具体执行的是什么SDK,则是适配器需要关心的事情:
// run anywhere
await pay({
price: 10,
goodsId: 1
})
对于SDK提供方,仅仅需要知道自己所需要的一些参数,然后按照自己的方式进行数据返回。
对于SDK调用方,仅仅需要我们约定好的通用的参数,以及按照约定的方式进行监听回调处理。
整合多个第三方SDK的任务就交由适配器来做,然后我们将适配器的代码压缩、混淆,放在一个看不见的角落里去,这样的代码逻辑就会变得很清晰了 :)。
适配器大致就是这样的作用,有一点一定要明确,适配器不是银弹,__那些繁琐的代码始终是存在的,只不过你在写业务的时候看不到它罢了__,眼不见心不烦。
其他应用场景
一些其他的例子
个人觉得,jQuery中就有很多适配器的例子,包括最基础的$('selector').on,这个不就是一个很明显的适配器模式么?
一步步的进行降级,并且抹平了一些浏览器之间的差异,让我们可以通过简单的on来进行在主流浏览器中进行事件监听:
// 一个简单的伪代码示例
function on (target, event, callback) {
if (target.addEventListener) {
// 标准的监听事件方式
target.addEventListener(event, callback)
} else if (target.attachEvent) {
// IE低版本的监听方式
target.attachEvent(event, callback)
} else {
// 一些低版本的浏览器监听事件方式
target[`on${event}`] = callback
}
}
或者在Node中的这样的例子更是常见,因为早年是没有Promise的,所以大多数的异步由callback来完成,且有一个约定好的规则,Error-first callback:
const fs = require('fs')
fs.readFile('test.txt', (err, data) => {
if (err) // 处理异常
// 处理正确结果
})
而我们的新功能都采用了async/await的方式来进行,当我们需要复用一些老项目中的功能时,直接去修改老项目的代码肯定是不可行的。 这样的兼容处理需要调用方来做,所以为了让逻辑代码看起来不是太混乱,我们可能会将这样的回调转换为Promise的版本方便我们进行调用:
const fs = require('fs')
function readFile (fileName) {
return new Promise((resolve, reject) => {
fs.readFile(fileName, (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
await readFile('test.txt')
因为前边也提到了,这种Error-first callback是一个约定好的形式,所以我们可以很轻松的实现一个通用的适配器:
function promisify(func) {
return (...args) => new Promise((resolve, reject) => {
func(...args, (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
然后在使用前进行对应的转换就可以用我们预期的方式来执行代码:
const fs = require('fs')
const readFile = promisify(fs.readFile)
await readFile('test.txt')
在Node8中,官方已经实现了类似这样的工具函数:util.promisify
总结
小结
个人观点:所有的设计模式都不是凭空想象出来的,肯定是在开发的过程中,总结提炼出的一些高效的方法,这也就意味着,可能你并不需要在刚开始的时候就去生啃这些各种命名高大上的设计模式。
因为书中所说的场景可能并不全面,也可能针对某些语言,会存在更好的解决办法,所以生搬硬套可能并不会写出有灵魂的代码 :)。
以上所述是小编给大家介绍的适配器在JavaScript中的体现,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对本站网站的支持!