专业的编程技术博客社区

网站首页 > 博客文章 正文

JavaScript设计模式之适配器模式(Adaptor Pattern)

baijin 2024-10-23 08:54:44 博客文章 6 ℃ 0 评论

适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

适配器模式的作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本 由于接口不兼容而不能工作的两个软件实体可以一起工作。 适配器的别名是包装器(wrapper),这是一个相对简单的模式。

在程序开发中有许多这样的场景:当我们试图调用模块或者对象的某个接口时,却发现这个接口的格式并不符合目前的需求。 这时候有两种解决办法,第一种是修改原来的接口实现,但如果原来的模块很复杂,或者我们拿 到的模块是一段别人编写的经过压缩的代码,修改原接口就显得不太现实了。第二种办法是创建 一个适配器,将原接口转换为客户希望的另一个接口,客户只需要和适配器打交道。

对于只有JavaScript这一门语言经验的前端开发来说,可能对于接口的概念比较陌生。建议参考阅读TypeScript-接口的文档来更好的理解接口。

ES6中的适配器模式

在前端项目中,适配器模式的使用场景一般有以下三种情况:库的适配、参数的适配和数据的适配。下面我将以我在项目中的实际例子来说明。

库的适配

项目上线前通常会要求前端开发者在页面中会接入统计网页数据用的SDK,这些SDK能够采集用户的信息和网页行生成可视化的图表和表格,来帮助网站运营人员和产品经理更好的根据用户行为来提升网页质量。我们来看一下适配器在接入采集数据的库时的使用场景:

目前国内做得比较好的数据分析网站有百度统计、神策数据、友盟等。在一个你做的电商类网站 项目上线前,你的产品经理要求你接入了百度的代码用于数据采集,并在几十个涉及用户操作的地方进行了埋点。百度统计提供的埋点接口格式如下:

_hmt.push(['_trackEvent', category, action, opt_label,opt_value]);

按照产品经理的要求,你根据上面的格式将埋点代码写到了页面的多个地方:

//index.html
_hmt.push(['_trackEvent', 'web', 'page_enter', 'position', 'index.html']);
//product-detail.html
_hmt.push(['_trackEvent', 'web', 'page_enter', 'position', 'product-detail.html']);
_hmt.push(['_trackEvent', 'web', 'product_detail_view', 'product_id', productId]);
_hmt.push(['_trackEvent', 'web', 'add-product-chart', 'product_id', productId]);
//...还有几十个页面

过了几个月之后,该电商网站发展速度很快,运营人员感觉到百度统计提供的采集数据在已经无法满足当前网站的规模。运营人员和产品经理商量后决定,数据采集平台需要从百度统计切换到神策数据,神策数据提供的埋点接口格式如下:

sa.track(eventName, {
 attrName: value 
})

接口的规则不同,就意味着你需要将几十个百度统计的_htm.push接口更改成为神策提供的sa.track接口。其实不用这么麻烦,写一个适配器就可以完成所有埋点事件的迁移:

通过分析比较百度统计的接口和神策的接口,可以知道在神策中只需要传入三个参数,eventName对应的是百度统计接口中的action, attrName对应的是百度统计接口中的opt_label, value对应的是百度统计接口中的opt_value; 删除了百度统计的SDK后,SDK所提供的_htm这个全局变量也就不存在了,我们可以利用该变量名做适配器,在push方法获取sa.track所需要的三个参数并调用sa.track即可。

参数的适配

有的情况下一个方法可能需要传入多个参数,例如在SDK这个类中有一个phoneStatus,需要传入五个参数用于接收手机的相关信息:

通常在传入的参数大于3的时候,我们就可以考虑将参数合并为一个对象的形式,就像我们$.ajax的做法一样。下面我们可以将phoneStatus的参数接口定义如下(String代表参数类型,?: 代表可选项)

可以看出,carrier、language,network这三个属性不是必须传入的,它们在方法内部可能被设置一些默认值。所以这个时候我们就可以在方法内部采用适配器来适配这个参数对象。

数据的适配

数据的适配在前端中是最为常见的场景,这时适配器在解决前后端的数据依赖上有着重要的意义。通常服务器端传递的数据和我们前端需要使用的数据格式是不一致的,特别是在在使用一些UI框架时,框架所规定的数据有着固定的格式。所以,这个时候我们就需要对后端的数据格式进行适配。

例如网页中有一个使用Echarts折线图对网站每周的uv,通常后端返回的数据格式如下所示

但是Echarts需要的x轴的数据格式和坐标点的数据是长下面这样的:

["周二", "周二", "周三", "周四", "周五", "周六", "周日"] //x轴的数据
[6300. 7100, 4300, 3300, 8300, 9300, 11300] //坐标点的数据

所以这是我们就可以使用一个适配器,将后端的返回数据做适配:

使用建议

适配器模式尽量少使用,就类似于在衣服上打补丁。特别是在接口还没有确定的时候使用,因为这样后期不利于维护,相反,这个时候我们应该重新思考我们的接口设计是否合理。

那合适使用适配器模式好呢?如果有以下情况出现时,建议使用:

  1. 使用一个已经存在的对象,但其方法或属性接口不符合你的要求;
  2. 你想创建一个可复用的对象,该对象可以与其它不相关的对象或不可见对象(即接口方法或属性不兼容的对象)协同工作;
  3. 想使用已经存在的对象,但是不能对每一个都进行原型继承以匹配它的接口。对象适配器可以适配它的父对象接口方法或属性。

总结

适配器模式在JS中的使用场景很多,在参数的适配上,有许多库和框架都使用适配器模式;数据的适配在解决前后端数据依赖上十分重要。但是适配器模式本质上是一个亡羊补牢的模式,它解决的是现存的两个接口之间不兼容的问题,你不应该在软件的初期开发阶段就使用该模式;如果在设计之初我们就能够统筹的规划好接口的一致性,那么适配器就应该尽量减少使用。

书中所说的场景可能并不全面,也可能针对某些语言,会存在更好的解决办法,所以生搬硬套可能并不会写出有灵魂的代码

下期预告:

JavaScript设计模式之中介者模式(Mediator Pattern)

参考

https://segmentfault.com/a/1190000015482452

https://juejin.im/entry/5a09b430f265da432528cf46

https://juejin.im/post/5ba1e579f265da0aa74f1ffa

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表