适配器(adapter)模式在JS中实践

时间:2021-06-23 14:13:40

适配器(adapter)模式


适配器(adapter)模式在JS中实践

定义

将一个类(对象)的接口(方法或者属性)转化成另外一个接口以满足用户需求,使类(对象)之间接口的不兼容问题通过适配器得以解决。

        换句话说,就是我用我的接口,但是还是用你的服务。

举个例子:

        有一天你从香港买来一个iphone8,但是香港的插头要比大陆地区的插头大一些,你回到家之后发现在现有的屋子环境下找不到合适的插口能够插入这个插头。现在有两个办法,一是:更换屋里的所有的或者部分插口能够插下iphone8,但是这个行不通的,家是在大陆,一般电器插口还是国标的,虽然解决了我一时需要使用ip8,但是手机更换这么快很可能我下一次就从大陆地区买ip10了,要是更换屋子的插口,岂不是还要更换回来?。二是:由于iphone8对于我这个屋子来说是临时用品,所以不会改变屋子的现状,而是从香港苹果官网(公司)上买一个港标转国标的适配器,只是在ip8插头上接上这个适配器就可以使用屋子里的插口了,并且这个适配器还有变压的功能。

我们对这个例子进行抽象下:

  1. 屋子:相当于稳定的业务代码或者上层逻辑,或者说是已经发布部署好了的产品。
  2. iphone8:相当于 变化的需求,不稳定,随时变化
  3. 香港的插头要比大陆地区的插头大:需求变化带来的问题。
  4. 港标转国标的适配器:解决上述需求的方式
  5. 香港苹果官网/苹果公司: 适配器需要被上层业务管理,而不是下层底层实现。

        在一开始设计业务代码/实现上层逻辑(在建房子的时候)不会去考虑为了能够适应我以后要买港货而设计一个插口,这种设计师很扯淡的,没有人有准确预测风险的能力。这总需求的变化总是出现在上层业务实现好了的基础上的,如果说出现了需求变化(ip8出现)去改动上层业务代码也是不现实的,因为对上层业务代码更改很复杂不说,一旦改动,就要重新测试、维护、部署浪费了大量时间花在不相关的工作上,更何况这个需求以后肯定还变化。那么最好的方式就是在上层业务外部使用一种adapter能够将这个需求进行包装是便于适应我现有的业务。那么适配器是最好不过的了。但是这个适配器必须是由需求变化的一方(香港苹果公司)生产只有这样才能保证适配器的标准。

        适配器的定义就介绍到这里,接下来看看如果从一个上层业务已经写好了的情况下去适应变化的需求,这个需求问题在于接口不能应用到业务代码中。

adapter产生过程

        由于设计模式一开始实在静态编译型的语言中产生的,静态编译型语言很大一个特点就是有类型限制的,但是js只有var ,一类变量很被动,其类型受值所决定的。那么在研究js中使用adapter之前,先看看面向对象语言是怎么使用的。

        约定: client 为 业务代码 即为 屋子, target 适配的目标 即为 国标, adaptee 适配对象 即为 港标, adapter 适配器 即为 港标转国标的适配器。

在还没有引入需求引入的时候即还没有买iPhone8的时候,一些都是那么和谐,稳定 如下表示:

适配器(adapter)模式在JS中实践

当引入iPhone8的时候可能会这么做:

适配器(adapter)模式在JS中实践

        这样做一个问题,就是上层业务要进行大幅动修改,导致从新测试维护部署上线更操作,花费大量的人力物力,这就为原先的设计增加了臭味 , 这个设计方案对业务代码进行的修改这是很不好的,违反了开放封闭原则,不好的原因是,可能上层代码会被更上一层的业务所依赖,一旦改动将很有可能导致整个项目的改动。违反了依赖倒置原则,很显然国标港标都是下层业务,所以只能下层业务依赖于抽象的接口,然后上层业务使用这个接口。

简单,添加一个港标接口不就可以了,比如这样:

适配器(adapter)模式在JS中实践

        但是问题又来了,这么改肯定满足了依赖倒置原则了,但是并没有改善开放封闭原则,并且如果说可以满足开放封闭原则的化,单穿的这样修改将不会满足里氏替换原则,也就是说,在屋子中用国标插口不该动情况下,如果还能使用iph8。所以进行了如下修改。这就是问题所在,如果转化接口是的能够适应上层业务代码。

适配器(adapter)模式在JS中实践

        这样就解决了这个问题,但是我想让这个适配器不只是你能够适配iph8还能够适配其他的港标电器,所以如下:

适配器(adapter)模式在JS中实践

        看起来,在java这类静态语言中这么实现可以的,那么是不是也要在js中这么实现?可是js中没有接口抽象类这些概念,难道不能使用了吗?或者和其他书所说的要模拟一个接口类模拟抽象类吗? 我感觉并不是这样的,想一想js语言和java这类语言的区别在于js这门语言是解析型的动态变化的,java静态编译的,所以java会有严格的类型限制,但是js没有。再想一下抽象类和接口之间目的是什么?是为了隔离变化,没错确实是这样的,那么这个变化是什么? 自我感觉来说就是类型,再细一步说就是对象类型,所以抽象类接口依赖了向下类型转换是在不改变上层业务基础上能够灵活的使用适应扩展的需求。

        所以,在我们平常写js代码中很不自然的就使用了adapter模式,只不过并没有注意到,这归根到底就是js的灵活性,不需要抽象类接口这些难理解的东东。不过还要说一点,就是接口的函数特征还是要统一的,一般是在编码文档中给出统一。接下来将重点介绍在js中adapter应用。

在js中灵活的应用


类上的适配

        还是上述那个房子的例子,看看在js中如何用代码实现它

/*上层业务 房子*/

function Client(socket) {
socket.electrify();
}

/**
* 文档说明:
* 所有的电器都必须实现electrify接口形式如下
* electrify()
*/

/**
* 我们这里并没有将guoBiaoAirCondition和gangBiaoIphone8继承与一个电器父类
* js中继承还是要有的,但是我们这里只有electrify 质押品实现文档规范的接口即函数统一特征就可以了
*
*/

var guoBiaoAirCondition = {
electrify: function() {
console.log('国标美的空调通电了');
}
};

var gangBiaoIphone8 = {
electrify: function() {
console.log('港标iPhone8通电了');
}
};

Client(guoBiaoAirCondition);
Client(gangBiaoIphone8);

        完全体现出了js动态语言的特性。


一个项目的使用库适配到另一个库中

        比如说我们现在一个项目已经上线了是使用的公司内部开发的M框架,但是由于一些原因,想使用jquery框架提供的服务,但是还不能改变已经上线的代码。

/*M框架使用如下方式进行属性选择*/
M.get(s){
return document.querySelectorAll(s);
}

/*jquery中可以如下 */
$(s);
/*使用后可以这样*/
M.get(s){
return $(s);
}

传入函数参数统一化处理

        
由于js中的灵活性,函数参数的顺序,以及传入函数实参的数量要进行控制,比如说一个函数是这样的:
function fun(name, id, age, adds, class, school);
变成人员要是能够记住这些参数数量和顺序已经非常麻烦,并且还存在一些参数可以不传入直接使用默认值。
所以一般情况下都是使用传入一个参数对象的方式,这样可以省略某些参数,也可以不注重顺序了。代码如下:

function fun(obj){
obj.age = obj.age || 12;
obj.id = oj.id || 0;
obj.school = obj.school || null;
console.log(obj.age, obj,name, obj.school);
}

fun({
age:18,
school:"QDU"
});

我们吧这个操作提取出来就是一种函数参数到函数标准参数的适配。

function args_adapter(modle, obj) {
var newObj = {};
/*新建一个对象而不是在原对象上操作降低该函数对外部对象的依赖*/
for (var i in modle) {
newObj[i] = (obj[i] === undefined) ? modle[i] : obj[i];
}
return newObj;
}

function fun(...arg) {
var modle = { age: 12, school: null, name: "maotr" };
var obj = args_adapter(modle, ...arg);
console.log(obj.age, obj.name, obj.school);
}

fun({
age: 18,
name: "M"
});

后端与前端文件格式适配

        
在前端使用后端接口之前先将后端提出来的数据进行进一步的格式化成我们需要的数据格式然后在使用,比如在ajax请求中对后端传来的数据可能是xml或者json格式,前端完全可以不依赖后端使用json格式进行编写,只不过在提取后端文件那一步之后要加一个适配器将他转化成我们需要的格式。


处理代码兼容性

function XHRObj() {
if (window.activeXObject) {
return ActiveXObject("Microsoft.XMLHTTP");
} else {
return new XMLHttpRequest();
}
}

        在java这类语言中使用ifelse分支或者switch处理多种情况是不妥当的,必然增加软件这几僵化性脆弱性臭味,但是在前端js中不存在因为浏览器就那么几种几乎不会变化。

总结

        适配器一般应用在业务逻辑代码与我使用的服务接口不匹配情况下,这种不匹配不单单是函数特征不匹配,甚至是参数不匹配等一些情况.这种模式并不会在设计的时候进行有意的创建,而是由于需求变更带来了这种模式的使用。