详解适配器在JavaScript中的体现


Posted in Javascript onSeptember 28, 2018

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

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

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

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

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

生活中的例子

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

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

详解适配器在JavaScript中的体现

在代码中的体现

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

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

接入多个第三方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中的体现,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
列表内容的选择
Jun 30 Javascript
ExtJS Store的数据访问与更新问题
Apr 28 Javascript
JQuery 学习技巧总结
May 21 Javascript
JS中的forEach、$.each、map方法推荐
Apr 05 Javascript
JS实现刷新父页面不弹出提示框的方法
Jun 22 Javascript
jQuery中$.ajax()方法参数解析
Oct 22 Javascript
JQuery实现动态操作表格
Jan 11 Javascript
详解webpack+express多页站点开发
Dec 22 Javascript
轻量级JS Cookie插件js-cookie的使用方法
Mar 22 Javascript
Vue源码解析之数组变异的实现
Dec 04 Javascript
微信小程序日历插件代码实例
Dec 04 Javascript
前端性能优化建议
Sep 17 Javascript
在微信小程序中渲染HTML内容的方法示例
Sep 28 #Javascript
Vue父子组件之间的通信实例详解
Sep 28 #Javascript
vue引入axios同源跨域问题
Sep 27 #Javascript
解决vue-cli脚手架打包后vendor文件过大的问题
Sep 27 #Javascript
Vue CLI 3搭建vue+vuex最全分析(推荐)
Sep 27 #Javascript
让webpack+vue-cil项目不再自动打开浏览器的方法
Sep 27 #Javascript
基于vue-cli npm run build之后vendor.js文件过大的解决方法
Sep 27 #Javascript
You might like
php daodb插入、更新与删除数据
2009/03/19 PHP
PHP常用正则表达式集锦
2014/08/17 PHP
如何使用PHP Embed SAPI实现Opcodes查看器
2015/11/10 PHP
php中bind_param()函数用法分析
2017/03/28 PHP
Laravel关系模型指定条件查询方法
2019/10/10 PHP
使用JS操作页面表格,元素的一些技巧
2007/02/02 Javascript
JQuery Easyui Tree的oncheck事件实现代码
2010/05/28 Javascript
js下利用控制器载入对应脚本
2010/07/17 Javascript
修改jQuery Validation里默认的验证方法
2012/02/14 Javascript
jQuery对下拉框,单选框,多选框的操作
2014/02/21 Javascript
seajs中模块的解析规则详解和模块使用总结
2014/03/12 Javascript
js+css实现的圆角边框TAB选项卡滑动门代码分享(2款)
2015/08/26 Javascript
实例讲解jquery中mouseleave和mouseout的区别
2016/02/17 Javascript
jQuery实现获取table表格第一列值的方法
2016/03/01 Javascript
14 个折磨人的 JavaScript 面试题
2016/08/08 Javascript
JavaScript与Java正则表达式写法的区别介绍
2017/08/15 Javascript
浅谈vue中使用图片懒加载vue-lazyload插件详细指南
2017/10/23 Javascript
小程序云开发部署攻略(图文教程)
2018/10/30 Javascript
详解Vue依赖收集引发的问题
2019/04/22 Javascript
vue quill editor 使用富文本添加上传音频功能
2020/01/14 Javascript
Python多线程、异步+多进程爬虫实现代码
2016/02/17 Python
用python实现对比两张图片的不同
2018/02/05 Python
python3获取当前文件的上一级目录实例
2018/04/26 Python
Python 字符串换行的多种方式
2018/09/06 Python
Python 可变类型和不可变类型及引用过程解析
2019/09/27 Python
Python爬虫:Request Payload和Form Data的简单区别说明
2020/04/30 Python
Python3中的tuple函数知识点讲解
2021/01/03 Python
详解CSS3原生支持div铺满浏览器的方法
2018/08/30 HTML / CSS
行政工作个人的自我评价
2014/02/13 职场文书
先进集体事迹材料
2014/02/17 职场文书
安全生产宣传标语
2014/06/06 职场文书
四风对照检查材料范文
2014/09/27 职场文书
党的群众路线教育实践活动个人整改方案
2014/10/25 职场文书
大学生个人简历自我评价
2015/03/11 职场文书
JavaScript ES6的函数拓展
2022/01/18 Javascript
以MySQL5.7为例了解一下执行计划
2022/04/13 MySQL