详解适配器在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 相关文章推荐
基于jquery实现的服务器验证控件的启用和禁用代码
Apr 27 Javascript
jquery remove方法应用详解
Nov 22 Javascript
js 限制数字 js限制输入实现代码
Dec 04 Javascript
遮罩层点击按钮弹出并且具有拖动和关闭效果(两种方法)
Aug 20 Javascript
jquery带有索引按钮且自动轮播切换特效代码分享
Sep 15 Javascript
JS实现分页浏览横向图片(类轮播)实例代码
Nov 06 Javascript
基于JS实现带动画效果的流程进度条
Jun 01 Javascript
vue 点击按钮增加一行的方法
Sep 07 Javascript
基于layPage插件实现两种分页方式浅析
Jul 27 Javascript
java遇到微信小程序 "支付验证签名失败" 问题解决
Dec 22 Javascript
解决Vue中使用keepAlive不缓存问题
Aug 04 Javascript
微信小程序实现底部弹出框
Nov 18 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中用unset销毁变量并释放内存
2020/05/10 PHP
自适应图片大小的弹出窗口
2006/07/27 Javascript
Jquery 实现Tab效果 思路是js思路
2010/03/02 Javascript
JQuery EasyUI 日期控件如何控制日期选择区间
2014/05/05 Javascript
JavaScript修改浏览器tab标题小技巧
2015/01/06 Javascript
Windows系统中安装nodejs图文教程
2015/02/28 NodeJs
Nodejs的express使用教程
2015/11/23 NodeJs
javascript Slip.js实现整屏滑动的手机网页
2015/11/25 Javascript
高性能JavaScript循环语句和条件语句
2016/01/20 Javascript
基于Bootstrap使用jQuery实现简单可编辑表格
2016/05/04 Javascript
jQuery使用正则表达式限制文本框只能输入数字
2016/06/18 Javascript
AngularJS中isolate scope的用法分析
2016/11/22 Javascript
详解Angular2 关于*ngFor 嵌套循环
2017/05/22 Javascript
Grunt针对静态文件的压缩,版本控制打包的实例讲解
2017/09/29 Javascript
React Native 图片查看组件的方法
2018/03/01 Javascript
Vue写一个简单的倒计时按钮功能
2018/04/20 Javascript
关于Vue项目跨平台运行问题的解决方法
2018/09/18 Javascript
JavaScript中this用法学习笔记
2019/03/17 Javascript
JS写滑稽笑脸运动效果
2020/05/28 Javascript
React 条件渲染最佳实践小结(7种)
2020/09/27 Javascript
python BeautifulSoup使用方法详解
2013/11/21 Python
Django静态资源URL STATIC_ROOT的配置方法
2014/11/08 Python
python3使用pandas获取股票数据的方法
2018/12/22 Python
python+os根据文件名自动生成文本
2019/03/21 Python
Python pandas如何向excel添加数据
2020/05/22 Python
python爬虫中抓取指数的实例讲解
2020/12/01 Python
HTML5 textarea高度自适应的两种方案
2020/04/08 HTML / CSS
艺龙旅行网酒店预订:国内、港澳台酒店
2018/06/26 全球购物
编写类String的构造函数、析构函数和赋值函数
2012/05/29 面试题
你经历的项目中的SCM配置项主要有哪些?什么是配置项?
2013/11/04 面试题
考试作弊检讨书大全
2014/02/18 职场文书
条幅标语大全
2014/06/20 职场文书
2015年保洁员工作总结
2015/05/04 职场文书
2015年大学教师工作总结
2015/05/20 职场文书
2015年外贸业务员工作总结范文
2015/05/23 职场文书
工程款催款函
2015/06/24 职场文书