详解适配器在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 相关文章推荐
破除网页鼠标右键被禁用的绝招大全
Dec 27 Javascript
深入理解JavaScript作用域和作用域链
Oct 21 Javascript
ExtJS4给Combobox设置列表中的默认值示例
May 02 Javascript
使用javascript提交form表单方法汇总
Jun 25 Javascript
js验证身份证号有效性并提示对应信息
Oct 19 Javascript
javascript实现仿百度图片的瀑布流加载效果
Apr 20 Javascript
js 中获取制定的cook信息实现方法
Nov 19 Javascript
Angular实现跨域(搜索框的下拉列表)
Feb 16 Javascript
基于js中this和event 的区别(详解)
Oct 24 Javascript
js字符串倒序的实例代码
Nov 30 Javascript
原生js实现trigger方法示例代码
May 22 Javascript
vue 微信分享回调iOS和安卓回调出现错误的解决
Sep 07 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实现连接设备、通讯和发送命令的方法
2015/10/13 PHP
Laravel 5.5基于内置的Auth模块实现前后台登陆详解
2017/12/21 PHP
P3P Header解决Cookie跨域的问题
2013/03/12 Javascript
JavaScript 垃圾回收机制分析
2013/10/10 Javascript
js限制文本框的输入内容代码分享(3类)
2015/08/20 Javascript
微信支付如何实现内置浏览器的H5页面支付
2015/09/25 Javascript
基于jquery实现页面滚动到底自动加载数据的功能
2015/12/19 Javascript
基于jQuery实现弹出可关闭遮罩提示框实例代码
2016/07/18 Javascript
jQuery fadeOut 异步实例代码详解
2016/08/18 Javascript
概述javascript在Google IE中的调试技巧
2016/11/24 Javascript
vue2.0结合DataTable插件实现表格动态刷新的方法详解
2017/03/17 Javascript
javascript中this用法实例详解
2017/04/06 Javascript
Vue上传组件vue Simple Uploader的用法示例
2017/08/25 Javascript
JQuery 选择器、DOM节点操作练习实例
2017/09/28 jQuery
jQuery实现form表单序列化转换为json对象功能示例
2018/05/23 jQuery
JS实现秒杀倒计时特效
2020/01/02 Javascript
深入理解Python中装饰器的用法
2016/06/28 Python
Python中标准库OS的常用方法总结大全
2017/07/19 Python
使用PyInstaller将python转成可执行文件exe笔记
2018/05/26 Python
Python编写合并字典并实现敏感目录的小脚本
2019/02/26 Python
利用python求积分的实例
2019/07/03 Python
python给list排序的简单方法
2020/12/10 Python
Reebok俄罗斯官方网上商店:购买锐步运动服装和鞋子
2016/09/26 全球购物
Lovedrobe官网:英国领先的大码服装品牌
2019/09/19 全球购物
Linux面试题LINUX系统类
2014/11/19 面试题
JSP和EJB可以共享HttpSession么?EJB里面可以改变session里面的内容
2013/06/05 面试题
小学敬老月活动方案
2014/02/11 职场文书
工程专业应届生求职信
2014/02/19 职场文书
天下第一关导游词
2015/02/06 职场文书
2015年初中生自我评价范文
2015/03/03 职场文书
公司仓管员岗位职责
2015/04/01 职场文书
环境卫生标语
2015/08/03 职场文书
《酸的和甜的》教学反思
2016/02/18 职场文书
opencv 分类白天与夜景视频的方法
2021/06/05 Python
Python内置数据结构列表与元组示例详解
2021/08/04 Python
关于HTML编码导致的乱码问题
2021/09/04 HTML / CSS