javascript 设计模式之适配器模式

概念

适配器模式:将一个类(对象)的接口(方法或属性)转化成客户希望的另外一个接口(方法或属性),使得原本由于接口不兼容而不能一起工作的那些类(对象)可以正常协作。简单理解就是为兼容而生的 “转换器”。

本文代码

生活中的适配器

比如生活中去香港或国外,电源接口跟国内不同就需要一个转换器
电源转换器
再比如Type-c 转接口,比如我之前用的耳机线都是圆头的,直到最近买了个小米10,发现它的手机孔竟然是方形的,好在厂商有送了个转换器,我才能继续听歌
耳机转换器
比照上面的定义,

  • 类:小米10
  • 类的接口:方形
  • 客户(我)
  • 另一种接口:圆形

适配器的业务场景

插头转换器

class Adapter {
	specificRequest() {
		return '德国标准插头'
	}
}
class Target {
	constructor() {
		this.adapter = new Adapter()
	}
	request() {
		let info = `${this.adapter.specificRequest()}---转换成---中国插头`
		return info
	}
}
let target = new Target()
console.info(target.request())

地图接口之间的适配

var googleMap = {
	show: function () {
		// 方法是show
		console.log('开始渲染谷歌地图')
	}
}
var baiduMap = {
	display: function () {
		//方法是display
		console.log('开始渲染百度地图')
	}
}
var baiduMapAdapter = {
	show: function () {
		//适配器也改为show,返回的是display
		return baiduMap.display()
	}
}
//下面是渲染地图的方法,传入地图对象
var renderMap = function (map) {
	//传入地图对象
	if (map.show instanceof Function) {
		//判断
		map.show() //地图对象的show方法
		//在传入baiduMapAdapter对象的时候,调用show方法,返回的
		//实际是baiduMap的display方法。
	}
}

renderMap(googleMap) // 输出:开始渲染谷歌地图
renderMap(baiduMapAdapter) // 输出:开始渲染百度地图

兼容接口请求

export default class HttpUtils {
  // get方法
  static get(url) {
    return new Promise((resolve, reject) => {
      // 调用fetch
      fetch(url)
        .then(response => response.json())
        .then(result => {
          resolve(result)
        })
        .catch(error => {
          reject(error)
        })
    })
  }
  
  // post方法,data以object形式传入
  static post(url, data) {
    return new Promise((resolve, reject) => {
      // 调用fetch
      fetch(url, {
        method: 'POST',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        // 将object类型的数据格式化为合法的body参数
        body: this.changeData(data)
      })
        .then(response => response.json())
        .then(result => {
          resolve(result)
        })
        .catch(error => {
          reject(error)
        })
    })
  }
  
  // body请求体的格式化方法
  static changeData(obj) {
    var prop,
      str = ''
    var i = 0
    for (prop in obj) {
      if (!prop) {
        return
      }
      if (i == 0) {
        str += prop + '=' + obj[prop]
      } else {
        str += '&' + prop + '=' + obj[prop]
      }
      i++
    }
    return str
  }
}

当想使用fetch 发起请求时,只需要按如下方式调用即可:

// 定义目标url地址
const URL = "xxxxx"
// 定义post入参
const params = {
    ...
}

// 发起post请求
 const postResponse = await HttpUtils.post(URL,params) || {}
 
 // 发起get请求
 const getResponse = await HttpUtils.get(URL)

但项目中不免有些旧的接口调用方式,比如有如下的:

// 发送get请求
Ajax('get', url地址, post入参, function(data){
    // 成功的回调逻辑
}, function(error){
    // 失败的回调逻辑
})

为了抹平差异,可以采用适配器模式

// Ajax适配器函数,入参与旧接口保持一致
async function AjaxAdapter(type, url, data, success, failed) {
    const type = type.toUpperCase()
    let result
    try {
         // 实际的请求全部由新接口发起
         if(type === 'GET') {
            result = await HttpUtils.get(url) || {}
        } else if(type === 'POST') {
            result = await HttpUtils.post(url, data) || {}
        }
        // 假设请求成功对应的状态码是1
        result.statusCode === 1 && success ? success(result) : failed(result.statusCode)
    } catch(error) {
        // 捕捉网络错误
        if(failed){
            failed(error.statusCode);
        }
    }
}

// 用适配器适配旧的Ajax方法
async function Ajax(type, url, data, success, failed) {
    await AjaxAdapter(type, url, data, success, failed)
}

如此一来,就用适配器(AjaxAdapter) 去承接旧接口的参数,实现新旧接口的无缝对接。

使用适配器模式的场景

jQuery 中的应用

适配器模式非常适用于跨浏览器兼容,例如强大的 jQuery 封装了事件处理的适配器,解决跨浏览器兼容性问题。

// $('selector').on 的实现
function on(target, event, callback) {
    if (target.addEventListener) {
        // 标准事件监听
        target.addEventListener(event, callback);
    } else if (target.attachEvent) {
        // IE低版本事件监听
        target.attachEvent(event, callback)
    } else {
        // 低版本浏览器事件监听
        target[`on${event}`] = callback
    }
}

axios

axios 完美抹平了浏览器端与 Node 环境下 api 的调用差异,两个环境手法完全一致,靠的正是对适配器模式的应用
源码中重点代码如下 :

function getDefaultAdapter() {
  var adapter;
  // 判断当前是否是node环境
  if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // 如果是node环境,调用node专属的http适配器
    adapter = require('./adapters/http');
  } else if (typeof XMLHttpRequest !== 'undefined') {
    // 如果是浏览器环境,调用基于xhr的适配器
    adapter = require('./adapters/xhr');
  }
  return adapter;
}

Node 的 http 适配器和 xhr 适配器大概长啥样:
http 适配器:

module.exports = function httpAdapter(config) {
  return new Promise(function dispatchHttpRequest(resolvePromise, rejectPromise) {
    // 具体逻辑
  }
}

xhr 适配器:

module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    // 具体逻辑
  }
}
  • 两个适配器的入参都是 config;
  • 两个适配器的出参都是一个 Promise。

这么一来,通过 axios 发起跨平台的网络请求,不仅调用的接口名是同一个,连入参、出参的格式都只需要掌握同一套。

参考链接

JavaScript 设计模式核⼼原理与应⽤实践

结语

你的点赞是对我最大的肯定,如果觉得有帮助,请留下你的赞赏,谢谢!!!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值