设计模式-适配器模式

原创
2019/03/22 14:29
阅读数 597

一、什么是适配器?

    适配器就是一种适配中间件,将两种不匹配的东西进行适配连接,举一个生活中的例子。小金最近买了最新款的macbook pro,但是发现电脑的数据接口都变成了Type c接口,这导致了之前的所有的 usb设备都不可用。那应该怎么办呢?这个时候就需要淘宝买一个能够将type c 转换成usb的器件,我们称之为适配器。

    那么适配器模式又是什么呢?适配器模式就是从适配器获得的灵感,将两种不太适配的代码进行适配,这种模式就是适配器模式了。

 

二、适配器模式

    适配器模式有类的适配器模式对象的适配器模式两种不同的形式。

 

2.1、类的适配器模式

  • UML关系图

    类的适配器模式将适配者类中的方法继承过来变成目标类的API

    从上图中Target接口可以看出客户端需要 operation1方法和operation2方法,但是Adaptee(适配者类)中只提供了operation1方法,这时通过Adapter(适配器类)继承Adaptee并且补充缺失的operation2方法满足了 客户端的需求。

 

  • 代码演示

//Target.java
public interface Target {
    public void operation1(); 
    public void operation2(); 
}

//Adaptee.java
public class Adaptee {
    public void operation1(){
        //do some thing
    }
}

//Adapter.java
public 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.java
public interface Target {
    void operation1();
    void operation2();
}

//Adaptee.java
public class Adaptee {
    public void operation1(){}
}

//Adapter.java
public 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、调用关系太复杂,不利于系统维护

 

五、总结

正如上面文章讲述,适配器模式在我们的前后端代码中都起着重要的作用。但是我们要认识到适配器模式本质上是一个”亡羊补牢”的模式,它解决的是现存的两个接口之间不兼容的问题,你不应该在软件的初期开发阶段就使用该模式。如果我们在设计之初就规划好调用方和被调用方,前端和后端之前统一一下数据格式就没有必要使用适配器模式,如果时间充裕或者没有必要一定是用适配器模式,应该对代码进行重构,使他们之间能够直接匹配。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部