相关知识

首页 > 相关知识 > 正文

适配器在JavaScript中的体现

2018年09月24日 热度:669 ℃

作者:贾顺名 

适配器设计模式在JavaScript中非常有用,在处理跨浏览器兼容问题、整合多个第三方SDK的调用,都可以看到它的身影。

其实在日常开发中,很多时候会不经意间写出符合某种设计模式的代码,毕竟设计模式就是老前辈们总结提炼出来的一些能够帮助提升开发效率的一些模版,源于日常的开发中。

而适配器其实在JavaScript中应该是比较常见的一种了。

在维基百科中,关于适配器模式的定义为:

在软件工程中,适配器模式是一种软件设计模式,允许从另一个接口使用现有类的接口。它通常用于使现有的类与其他类一起工作,而无需修改其源代码。

生活中的例子

在生活中最常见的就是电源插头的适配器了,世界各国的插座标准各不相同,如果需要根据各国的标准购买对应的电源插头那未免太过于浪费钱财,如果说自己带着插座,把人家墙敲碎,重新接线,也肯定是不现实的。

所以就会有插头的适配器,用来将某种插头转换成另一种插头,在插座和你的电源之间做中转的这个东西,就是适配器。

在代码中的体现

而转向到编程中,我个人是这样理解的:

将那些你不愿意看见的脏代码藏起来,你就可以说这是一个适配器

接入多个第三方SDK

举个日常开发中的例子,我们在做一个微信公众号开发,里边用到了微信的支付模块,经过长时间的联调,终于跑通了整个流程,正当你准备开心的打包上线代码的时候,得到了一个新需求:

为了复用代码,我们可能会在脚本中写下这样的逻辑:

if(platform === 'wechat'){

wx.pay(config)

}elseif(platform === 'alipay'){

alipay.pay(config)

}

// 做一些后续的逻辑处理

但是一般来说,各厂的SDK所提供的接口调用方式都会多多少少有些区别,虽说有些时候文档可能用的是同一份,致敬友商。

所以针对上述的代码可能是这样的:

// 并不是真实的参数配置,仅仅举例使用

constconfig = {

price10,

goodsId1

}

// 还有可能返回值的处理方式也不相同

if(platform === 'wechat'){

config.appId = 'XXX'

config.secretKey = 'XXX'

wx.pay(config).then((err,data) => {

if(err)// error

// success

})

}elseif(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的调用方式或者一个我们约定好的规则作为基准。

我们来告诉调用方,你要怎么怎么做,你能怎样获取返回数据,然后我们在函数内部进行这些各种肮脏的判断:

functionpay({

price,

goodsId

}){

returnnewPromise((resolve,reject) => {

constconfig = {}

switch(platform){

case'wechat':

config.price = price

config.goodsId = goodsId

config.appId = 'XXX'

config.secretKey = 'XXX'

wx.pay(config).then((err,data) => {

if(err)returnreject(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

awaitpay({

price10,

goodsId1

})

对于SDK提供方,仅仅需要知道自己所需要的一些参数,然后按照自己的方式进行数据返回。

对于SDK调用方,仅仅需要我们约定好的通用的参数,以及按照约定的方式进行监听回调处理。

整合多个第三方SDK的任务就交由适配器来做,然后我们将适配器的代码压缩,混淆,放在一个看不见的角落里去,这样的代码逻辑就会变得很清晰了 :)。

适配器大致就是这样的作用,有一点一定要明确,适配器不是银弹,__那些繁琐的代码始终是存在的,只不过你在写业务的时候看不到它罢了__,眼不见心不烦。

一些其他的例子

个人觉得,jQuery中就有很多适配器的例子,包括最基础的$('selector').on,这个不就是一个很明显的适配器模式么?

一步步的进行降级,并且抹平了一些浏览器之间的差异,让我们可以通过简单的on来进行在主流浏览器中进行事件监听:

// 一个简单的伪代码示例

functionon(target,event,callback){

if(target.addEventListener){

// 标准的监听事件方式

target.addEventListener(event,callback)

}elseif(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的版本方便我们进行调用:

constfs = require('fs')

functionreadFile(fileName){

returnnewPromise((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)

    })

  })

}

然后在使用前进行对应的转换就可以用我们预期的方式来执行代码:

constfs = require('fs')

constreadFile = promisify(fs.readFile)

await readFile('test.txt')

在Node8中,官方已经实现了类似这样的工具函数:util.promisify

小结

个人观点:所有的设计模式都不是凭空想象出来的,肯定是在开发的过程中,总结提炼出的一些高效的方法,这也就意味着,可能你并不需要在刚开始的时候就去生啃这些各种命名高大上的设计模式。

因为书中所说的场景可能并不全面,也可能针对某些语言,会存在更好的解决办法,所以生搬硬套可能并不会写出有灵魂的代码 

觉得本文对你有帮助?请分享给更多人

发表评论
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

相关文章

【这一周】中国支付清算协会发布2个风险提示 香港快速支付系统FPS上线

【这一周】中国支付清算协会发布2个风险提示 香港快速支付系统FPS上线

香港金管局宣布17日启动快速支付系统,并将它命名为「转数快」。「转数快」每日24小时、每星期七天全天候运作,提供平台全面接通不同银行及储值支付工具营运商,让市民可以随时随地进行跨银行及储值支付工具的即...

朔州医院要开通微信、支付宝支付了?

朔州医院要开通微信、支付宝支付了?

如今,我们生活在这个互联网时代早就习惯了能用一部手机支付就懒得带现金出门的生活模式现在的移动支付特别方便上到买车买房、下到菜市场买菜连偏远农村的小卖部前不久阿朔生病,去了北京医院来医院的人可谓是人山人...

支付人必看的行业知识

支付人必看的行业知识

支付类型1网关支付网关支付(Payment Gateway)是银行金融网络系统和Internet网络之间的接口,是由银行操作的将Internet上传输的数据转换为金融机构内部数据的一组服务器设备,或由...

说说第三方支付接口开发及开发中遇到的坑爹问题

说说第三方支付接口开发及开发中遇到的坑爹问题

前言  最近在做公司的支付接口,从微信支付到各种第三方的支付接口,还有点卡等支付,微信支付文档相对比较详细,虽然也不少坑,被各路开发人员吐槽,但是填的人多啊,所以是最好开发的,但是公司还有用到一些第三...

凯撒黑卡怎么玩

凯撒黑卡怎么玩

今年号称国内区块链元年,国内百度莱茨狗、火牛视频、等热门区块应用跑火,吸引了一大票区块和币圈玩家进来,部分人投资回报甚至百倍千倍,当然也存在部分不法分子借这波东风大捞一把,圈完钱就跑路,所以大家要谨慎...

再见,二维码!微信、支付宝共同宣布''刷脸”时代来了

再见,二维码!微信、支付宝共同宣布''刷脸”时代来了

近日,上海地铁传来好消息:结合"人脸识别+信用支付"技术的地铁售检票系统成功面世!这意味着坐地铁,不仅可以通手机扫码支付,而且还可以刷脸进站啦!从过去的现金、刷卡支付到升级到移动支付,我们确实在生活中...