适配器设计模式在JavaScript中非常有用。在处理跨浏览器兼容问题和集成多个第三方SDK调用上可以看到。其实在日常开发中,很多时候你会不经意地写出符合某种设计模式的代码。设计模式毕竟是一些可以帮助提高开发效率的模板,来源于日常开发。其实适配器在JavaScript中应该是比较常见的。在维基百科中,适配器模式的定义是:在软件工程中,适配器模式是一种软件设计模式,允许从另一个接口使用现有类的接口。它通常用于使现有类与其他类一起工作,而无需修改它们的源代码。日常生活中的例子日常生活中最常见的就是电源插头的适配器。世界各国的插座标准各不相同。如果你需要按照每个国家的标准购买相应的电源插头,那就太浪费钱了。如果自己带插座的话,去砸别人家的墙重新接线肯定是不现实的。所以就会有插头的适配器,用来将某种类型的插头转换成另一种类型的插头。在插座和电源之间传输的是适配器。体现在代码中,转向编程。我个人的理解是这样的:把不想看到的脏代码隐藏起来,可以说这是一个对接多个第三方SDK的适配器。以日常开发为例,我们正在开发一个微信♂开发,其中使用了微信支付模块。经过长时间的联调,我们终于跑通了整个流程。就在你准备愉快的打包上线代码的时候,你又得到了一个新的需求:我们需要接入支付宝的SDK公众号,还要有一个支付流程。为了复用代码,我们可能会在脚本中写这样的逻辑:if(platform==='wechat'){wx.pay(config)}elseif(platform==='alipay'){alipay.pay(config)}//做一些后续的逻辑处理但是一般来说,各个工厂的SDK提供的接口调用方法或多或少都会有差异,虽然有时候可能会用到同一个文档,向朋友们致敬。所以对于上面的代码,可能是这样的://不是真正的参数配置,只是用constconfig={price:10,goodsId:1}//也有可能对返回值做不同的处理if(platform==='wechat'){config.appId='XXX'config.secretKey='XXX'wx.pay(config).then((err,data)=>{if(err)//错误//成功})}elseif(platform==='alipay'){config.token='XXX'alipay.pay(config,data=>{//success},err=>{//error})}现在,code界面还是比较清晰的,只要我们写好注释,这个代码就不算太差。但是生活总是充满惊喜,我们接到要求添加QQ的SDK,美团的SDK,小米的SDK,或者某家银行的SDK。此时你的代码可能是这样的://美团的处理逻辑breakcase'xiaomi'://小米的处理逻辑break}这已经不是一些注释能弥补的问题了,这样的代码会越来越难维护,各种SDK都怪别人的话要做类似的需求,需要重写这样的代码,一定是资源的浪费。所以,为了保证我们业务逻辑的清晰,也为了避免后人反复踩这个坑,我们将其拆分出来,作为一个公共函数存在:找到其中一个SDK的调用方法,或者我们自己开发的SDK的调用方法。商定规则作为基准。让我们告诉调用者要做什么,如何获取返回的数据,然后我们在函数内部进行各种肮脏的判断:functionpay({price,goodsId}){returnnewPromise((resolve,reject)=>{constconfig={}switch(platform){case'wechat'://微信处理逻辑config.price=priceconfig.goodsId=goodsIdconfig.appId='XXX'config.secretKey='XXX'wx.pay(config).then((err,data)=>{if(err)returnreject(err)resolve(data)})breakcase'QQ'://QQ处理逻辑config.price=price*100config.gid=goodsIdconfig.appId='XXX'config.secretKey='XXX'config.success=resolveconfig.error=rejectqq.pay(config)breakcase'alipay'://支付宝的处理逻辑config.payment=priceconfig.id=goodsIdconfig.token='XXX'alipay.pay(config,resolve,reject)break}})}这样,不管我们在什么环境下,只要我们的adapter支持,我们就可以按照我们约定好的通用规则来调用,具体执行什么SDK,那就是东西了适配器需要关心的是://runanywhereawaitpay({price:10,goodsId:1})对于SDKprovider来说,只需要知道自己需要的一些参数,然后按照自己的方式返回数据即可。对于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{//部分低版本浏览器监听toeventmethodstarget[`on${event}`]=callback}}或者Node中这样的例子比较常见,因为早年没有Promise,所以大部分Asynchrony都是通过callback来完成的,有一个约定好的规则,错误优先回调:constfs=require('fs')fs.readFile('test.txt',(err,data)=>{if(err)//handleexception//handlethecorrectresult})和我们的新功能都是以async/await的方式实现的。当我们需要复用旧项目中的一些功能时,直接修改旧项目的代码肯定是行不通的。这样的兼容处理需要调用方来完成,所以为了让逻辑代码看起来不会太混乱,我们可以把这样的回调转换成Promise版本供我们调用:constfs=require('fs')functionreadFile(fileName){returnnewPromise((resolve,reject)=>{fs.readFile(fileName,(err,data)=>{if(err)reject(err)resolve(data)})})}awaitreadFile('test.txt')如前所述,这个Error-first回调是一种约定形式,因此我们可以很容易地实现一个通用的适配器:functionpromisify(func){return(...args)=>newPromise((resolve,reject)=>{func(...args,(err,data)=>{if(err)reject(err)resolve(data)})})}然后在使用前进行相应的转换以使用我们预期的方式执行代码:constfs=require('fs')constreadFile=promisify(fs.readFile)awaitreadFile('test.txt')在Node8中,官方实现了这样一个工具函数:util.promisify总结个人观点:所有的设计模式都不是想象出来的稀薄的空气。他们一定是在开发过程中总结提炼一些高效的方法,也就是说你一开始可能不需要啃这些。各种命名的设计模式。因为书中提到的场景可能并不全面,某些语言可能有更好的解决方案,所以死记硬背未必能写出深情的代码:)纸上写来总是肤浅的,但从来不知道这些,你必须做它。————《冬夜读书示子聿》,陆游
