一、什么是适配器?
适配器就是一种适配中间件,将两种不匹配的东西进行适配连接,举一个生活中的例子。小金最近买了最新款的macbook pro,但是发现电脑的数据接口都变成了Type c接口,这导致了之前的所有的 usb设备都不可用。那应该怎么办呢?这个时候就需要淘宝买一个能够将type c 转换成usb的器件,我们称之为适配器。
那么适配器模式又是什么呢?适配器模式就是从适配器获得的灵感,将两种不太适配的代码进行适配,这种模式就是适配器模式了。
二、适配器模式
适配器模式有类的适配器模式和对象的适配器模式两种不同的形式。
2.1、类的适配器模式
-
UML关系图
类的适配器模式将适配者类中的方法继承过来变成目标类的API
从上图中Target接口可以看出客户端需要 operation1方法和operation2方法,但是Adaptee(适配者类)中只提供了operation1方法,这时通过Adapter(适配器类)继承Adaptee并且补充缺失的operation2方法满足了 客户端的需求。
-
代码演示
//Target.javapublic interface Target { public void operation1(); public void operation2(); }//Adaptee.javapublic class Adaptee { public void operation1(){ //do some thing }}//Adapter.javapublic class Adapter extends Adaptee implements Target { /** * 因为Adaptee没有提供operation2方法, * 所以需要适配器进行补充 */ @Override public void operation2() { //do some thing }}
上面的代码中我们可以看到适配器类Adapter 继承了适配者Adaptee类,实现了目标接口Target,并且补充了Adaptee中缺少的operation2方法,最终满足了客户端的API需求。
2.2、对象适配器模式
- UML关系图
与类的适配器模式不同,对象适配器模式不是通过继承连接到Adaptee(适配者),而是通过委派的方式连接Adaptee
从上图中可以看出客户端需要operation1方法和operation2方法,但是Adaptee类中只提供了operation1方法。为了使客户端能够使用Adaptee,需要一个适配器类,将operation1方法通过 Adaptee实例委派给Adaptee类处理,并且自己实现Adaptee中没有提供的operation2方法。
- 代码演示
//Target.javapublic interface Target { void operation1(); void operation2();}//Adaptee.javapublic class Adaptee { public void operation1(){}}//Adapter.javapublic class Adapter implements Target { public Adaptee adaptee; public void Adapter(Adaptee adaptee) { this.adaptee = adaptee; } @Override public void operation1() { this.adaptee.operation1(); } @Override public void operation2() { //do some thing }}
从代码中我们看到Adapter适配器类中将operation1方法的实现委派给了Adaptee,并且自己实现了operation2方法,最终满足了客户端的API需求。
三、适配器模式在前端中的应用
3.1接口适配
假设我们引入两种地图组建baiduMap 和 gaodeMap 都提供了相同的 start启动方法
const baiduMap = { start: function () { console.log('启动百度地图'); }};const gaodeMap = { start: function () { console.log('启动高德地图'); }};const startMap = function (map) { if (map.show instanceof Function) { map.start(); }};//我们可以通过startMap方法分别启动两种地图startMap(gaodeMap); // 输出:启动高德地图startMap(baiduMap); // 输出:启动百度地图
上面我们可以通过startMap方法分别启动两种地图是因为两种地图的启动方法都是start,但是如果高德地图的启动方法是begin怎么办?这个时候我们就需要用到适配器将启动方法和高德地图进行适配。
const baiduMap = { start: function () { console.log('启动百度地图'); }};const gaodeMap = { begin: function () { console.log('启动高德地图'); }};const gaodeMapAdapter = { start : function(){ return gaodeMap.begin(); } }const startMap = function (map) { if (map.show instanceof Function) { map.start(); }};//启动高德地图的时候需要将适配器作为参数传入startMap(gaodeMapAdapter); // 输出:启动高德地图startMap(baiduMap); // 输出:启动百度地图
上面我们就是通过gaodeMapAdapter将startMap方法 和gaodeMap进行适配,使两者仍人可以正常调用。
3.2参数适配
假如小金的学校为了提高学生们的查询高考成绩效率,自己开发了一套分数查询系统只需要输入证件号码就可以,但是教育部提供的查询api中需要输入省市和学校信息,那这种参数不匹配的情况该怎么办?
var param = { id : 222404}function queryScore(param) { var newParam = argAdapter(param); return EduApi.getScore(newParam);}//教育部提供的接口var EduApi = { getScore : function(param){ if (!param.province || !param.city || param.school) { return null; } return select(param) }}//参数适配器function argAdapter(param) { var newParam = {}; var default = { province : '江苏', city : '南京', school : '南京大学附属中学' } newParam.id = param.id; newParam.province = param.province || default.province; newParam.city = param.city || default.city; newParam.school = param.school || default.school; return newParam}
上面的代码中 参数适配器argAdapter 将客户端传入的参数 和API中实际需要的参数进行了适配,让代码能够正常地运行。
3.3数据适配
接触过echarts的人应该知道,要使用echarts展示折线图,需要分别传入x轴和y轴数据,他们的数据格式都是数组格式,但如果后传来的数据是对象数组怎么办,当然是要将对象数组拆分成两组数组。
[{ "x": "huawei", "y": '9999' }, { "x": "iphone", "y": 8888 }, { "x": "samsung", "y": 7777}]//x轴适配器function xAxisAdapter(res) { return res.map(item => item.x);}//获取x轴数据//['huawei', 'iphone', 'samsung']//y轴适配器function yAxisAdapter(res) { return res.map(item => item.y);}//获取y轴数据//[9999,8888,7777]
四、适配者模式优缺点
-
优点:
1、将目标类和适配者类解耦
2、增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性
3、灵活性和扩展性都非常好,符合开放封闭原则
-
缺点:
1、过多的使用适配器,会让系统非常零乱,不易整体进行把握
2、调用关系太复杂,不利于系统维护
五、总结
正如上面文章讲述,适配器模式在我们的前后端代码中都起着重要的作用。但是我们要认识到适配器模式本质上是一个”亡羊补牢”的模式,它解决的是现存的两个接口之间不兼容的问题,你不应该在软件的初期开发阶段就使用该模式。如果我们在设计之初就规划好调用方和被调用方,前端和后端之前统一一下数据格式就没有必要使用适配器模式,如果时间充裕或者没有必要一定是用适配器模式,应该对代码进行重构,使他们之间能够直接匹配。