资讯专栏INFORMATION COLUMN

适配器在JavaScript中的体现

z2xy / 847人阅读

摘要:而适配器其实在中应该是比较常见的一种了。在维基百科中,关于适配器模式的定义为在软件工程中,适配器模式是一种软件设计模式,允许从另一个接口使用现有类的接口。

适配器设计模式在JavaScript中非常有用,在处理跨浏览器兼容问题、整合多个第三方SDK的调用,都可以看到它的身影。
其实在日常开发中,很多时候会不经意间写出符合某种设计模式的代码,毕竟设计模式就是老前辈们总结提炼出来的一些能够帮助提升开发效率的一些模版,源于日常的开发中。
适配器其实在JavaScript中应该是比较常见的一种了。

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

</>复制代码

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

在生活中最常见的就是电源插头的适配器了,世界各国的插座标准各不相同,如果需要根据各国的标准购买对应的电源插头那未免太过于浪费钱财,如果说自己带着插座,把人家墙敲碎,重新接线,也肯定是不现实的。
所以就会有插头的适配器,用来将某种插头转换成另一种插头,在插座和你的电源之间做中转的这个东西,就是适配器。

在代码中的体现

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

</>复制代码

  1. 将那些你不愿意看见的脏代码藏起来,你就可以说这是一个适配器
接入多个第三方SDK

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

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

</>复制代码

  1. if (platform === "wechat") {
  2. wx.pay(config)
  3. } else if (platform === "alipay") {
  4. alipay.pay(config)
  5. }
  6. // 做一些后续的逻辑处理

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

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

</>复制代码

  1. // 并不是真实的参数配置,仅仅举例使用
  2. const config = {
  3. price: 10,
  4. goodsId: 1
  5. }
  6. // 还有可能返回值的处理方式也不相同
  7. if (platform === "wechat") {
  8. config.appId = "XXX"
  9. config.secretKey = "XXX"
  10. wx.pay(config).then((err, data) => {
  11. if (err) // error
  12. // success
  13. })
  14. } else if (platform === "alipay") {
  15. config.token = "XXX"
  16. alipay.pay(config, data => {
  17. // success
  18. }, err => {
  19. // error
  20. })
  21. }

就目前来说,代码接口还算是清晰,只要我们写好注释,这也不是一个太糟糕的代码。
但是生活总是充满了意外,我们又接到了需求需要添加QQ的SDK、美团的SDK、小米的SDK,或者某些银行的SDK。

此时你的代码可能是这样的:

</>复制代码

  1. switch (platform) {
  2. case "wechat":
  3. // 微信的处理逻辑
  4. break
  5. case "QQ":
  6. // QQ的处理逻辑
  7. break
  8. case "alipay":
  9. // 支付宝的处理逻辑
  10. break
  11. case "meituan":
  12. // 美团的处理逻辑
  13. break
  14. case "xiaomi":
  15. // 小米的处理逻辑
  16. break
  17. }

这已经不是一些注释能够弥补的问题了,这样的代码会变得越来越难维护,各种SDK千奇百怪的调用方式,如果其他人也要做类似的需求,还需要重新写一遍这样的代码,那肯定是很浪费资源的一件事儿。

所以为了保证我们业务逻辑的清晰,同时也为了避免后人重复的踩这个坑,我们会将它进行拆分出来作为一个公共的函数来存在:
找到其中某一个SDK的调用方式或者一个我们约定好的规则作为基准。
我们来告诉调用方,你要怎么怎么做,你能怎样获取返回数据,然后我们在函数内部进行这些各种肮脏的判断:

</>复制代码

  1. function pay ({
  2. price,
  3. goodsId
  4. }) {
  5. return new Promise((resolve, reject) => {
  6. const config = {}
  7. switch (platform) {
  8. case "wechat":
  9. // 微信的处理逻辑
  10. config.price = price
  11. config.goodsId = goodsId
  12. config.appId = "XXX"
  13. config.secretKey = "XXX"
  14. wx.pay(config).then((err, data) => {
  15. if (err) return reject(err)
  16. resolve(data)
  17. })
  18. break
  19. case "QQ":
  20. // QQ的处理逻辑
  21. config.price = price * 100
  22. config.gid = goodsId
  23. config.appId = "XXX"
  24. config.secretKey = "XXX"
  25. config.success = resolve
  26. config.error = reject
  27. qq.pay(config)
  28. break
  29. case "alipay":
  30. // 支付宝的处理逻辑
  31. config.payment = price
  32. config.id = goodsId
  33. config.token = "XXX"
  34. alipay.pay(config, resolve, reject)
  35. break
  36. }
  37. })
  38. }

这样无论我们在什么环境下,只要我们的适配器支持,就可以按照我们约定好的通用规则进行调用,而具体执行的是什么SDK,则是适配器需要关心的事情:

</>复制代码

  1. // run anywhere
  2. await pay({
  3. price: 10,
  4. goodsId: 1
  5. })

对于SDK提供方,仅仅需要知道自己所需要的一些参数,然后按照自己的方式进行数据返回。
对于SDK调用房,仅仅需要我们约定好的通用的参数,以及按照约定的方式进行监听回调处理。

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

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

一些其他的例子

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

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

</>复制代码

  1. // 一个简单的伪代码示例
  2. function on (target, event, callback) {
  3. if (target.addEventListener) {
  4. // 标准的监听事件方式
  5. target.addEventListener(event, callback)
  6. } else if (target.attachEvent) {
  7. // IE低版本的监听方式
  8. target.attachEvent(event, callback)
  9. } else {
  10. // 一些低版本的浏览器监听事件方式
  11. target[`on${event}`] = callback
  12. }
  13. }

或者在Node中的这样的例子更是常见,因为早年是没有Promise的,所以大多数的异步由callback来完成,且有一个约定好的规则,Error-first callback

</>复制代码

  1. const fs = require("fs")
  2. fs.readFile("test.txt", (err, data) => {
  3. if (err) // 处理异常
  4. // 处理正确结果
  5. })

而我们的新功能都采用了async/await的方式来进行,当我们需要复用一些老项目中的功能时,直接去修改老项目的代码肯定是不可行的。
这样的兼容处理需要调用方来做,所以为了让逻辑代码看起来不是太混乱,我们可能会将这样的回调转换为Promise的版本方便我们进行调用:

</>复制代码

  1. const fs = require("fs")
  2. function readFile (fileName) {
  3. return new Promise((resolve, reject) => {
  4. fs.readFile(fileName, (err, data) => {
  5. if (err) reject(err)
  6. resolve(data)
  7. })
  8. })
  9. }
  10. await readFile("test.txt")

因为前边也提到了,这种Error-first callback是一个约定好的形式,所以我们可以很轻松的实现一个通用的适配器:

</>复制代码

  1. function promisify(func) {
  2. return (...args) => new Promise((resolve, reject) => {
  3. func(...args, (err, data) => {
  4. if (err) reject(err)
  5. resolve(data)
  6. })
  7. })
  8. }

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

</>复制代码

  1. const fs = require("fs")
  2. const readFile = promisify(fs.readFile)
  3. await readFile("test.txt")

</>复制代码

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

个人观点:所有的设计模式都不是凭空想象出来的,肯定是在开发的过程中,总结提炼出的一些高效的方法,这也就意味着,可能你并不需要在刚开始的时候就去生啃这些各种命名高大上的设计模式。
因为书中所说的场景可能并不全面,也可能针对某些语言,会存在更好的解决办法,所以生搬硬套可能并不会写出有灵魂的代码 :)

</>复制代码

  1. 纸上得来终觉浅,绝知此事要躬行。 ———— 《冬夜读书示子聿》,陆游

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97909.html

相关文章

  • JavaScript 设计模式(四):适配者模式

    摘要:与其它模式的异同适配器模式不会改变原有接口,这一点与装饰者模式和代理模式类似。代理模式适配器模式与代理模式最相似,同样都是创建一个新对象包装一次,实现对本体的调用。外观模式外观模式与适配器模式最大的区别,是定义了一个新的接口。 showImg(https://segmentfault.com/img/bVbul8d?w=800&h=600); 适配器模式:将一个类(对象)的接口(方法或...

    MingjunYang 评论0 收藏0
  • 值得参考的css理论:OOCSS、SMACSS与BEM

    摘要:,字面意思是面向对象的,是由提出的理论,其主要的两个原则是分离结构和主题分离容器和内容用一个例子来说明。分离容器和内容要求使页面元素不依赖于其所处位置。命名规则不需要严格遵守,可以根据实际情况和自身喜好做其他的约定。 最近在The Sass Way[]一文,发现文章在开头部分就提到了OOCSS、 SMACSS、 BEM、这3个词。如果还不知道这些是什么,请先不要继续看下去,联想到作者这...

    马忠志 评论0 收藏0
  • 详解Spring中的9种设计模式「记得收藏」

    摘要:简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。中的就是简单工厂模式的体现,根据传入一个唯一的标识来获得对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。中的就是典型的工厂方法模式。 showImg(https://segmentfault.com/img/bVbwbd9?w=640&h=492); 一. 简单工厂又叫做静态工厂方法(...

    Dean 评论0 收藏0

发表评论

0条评论

z2xy

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<