实验介绍
本节实验为大家带来了适配器模式,适配器模式是作为两个不兼容的接口之间的桥梁,可以将变化都封装于它本身,提供简单统一的接口使用。从一个有趣的例子开始为大家逐步的讲解适配器,帮助大家学习其基本的概念。随后为大家介绍了适配器在前端中的真实应用,加深对适配器的认识。最后为大家简单区分了装饰器模式与适配器模式,可以进行对照学习。
知识点
- 适配器模式介绍
- 适配器的应用
- 装饰器模式与适配器模式的区别
- 实验挑战
适配器模式介绍
适配器模式是作为两个不兼容的接口之间的桥梁,从名称上应该就能够明白,适配,只有不匹配不兼容的时候才需要适配。而适配器模式也是实现适配器的思想与过程。
举一个真实的例子,在几年前手机中用的内存卡是没有办法直接插入电脑中的,如果想要下载歌曲或者电影,就需要一种叫读卡器的物品,你可以将内存卡插入读卡器,再将读卡器插入笔记本,这样就能实现通过笔记本来读取内存卡,从而进行数据间的交换。
在这里,读卡器担任的正式适配器这一角色。
当然不止这一个例子,例如:
- 苹果手机从 7 版本开始,它的耳机接口和充电接口共用,并且都是方形孔。如果你想使用以前的圆形插孔耳机,就只能通过一个转换器来实现,这里的转换器就是一个适配器。
- 美国电压采用 110V,而中国采用 220 V,此时就需要一个适配器将 110 V 转换成 220 V,否则当你身处国外时是无法对国内电子设备进行充电的。
从上面的这些例子中可以看到,适配器的应用充斥着我们生活的方方面面。
知道了适配器的定义,现在让我们看看如何用代码实现一个适配器吧。
适配器
首先新建 index.html
文件,及 player.js
文件,并引入。
考虑这样一个例子:
在十几年前,技术不如现在发达的时代,一开始社会上流行的最多的 MP3 这种音乐格式,一位罗老板看准了这个机会,于是想通过卖支持播放 MP3 格式的播放器挣钱,因此他找到一个程序员张三,让他实现一个类用于播放 MP3 格式的文件。
张三一听,心想这还不简单,于是很快就实现了 AudioPlayer
类:
// player.js
class AudioPlayer {
// ...
play(fileName) {
console.log("播放 MP3 音乐:", fileName);
}
}
来看看这个 AudioPlayer
是否能满足罗老板的需求呢:
// player.js
// 构建一个 audioPlayer 实例
const audioPlayer = new AudioPlayer();
// 调用 play,播放 MP3
audioPlayer.play("千里之外"); // 播放 MP3 音乐: 千里之外
功能实现之后,罗老板因为这个技术赚的盆满钵满的,不过好景不长,随着技术的不断革新,市场上又逐渐出现了 VLC 和 MP4 这两种格式的文件。为了加强自己产品的竞争力。他赶紧叫来张三,告诉他一定要赶紧完成这两个功能。
张三一想,这好办啊,于是飞快敲起了键盘,很快就实现了 VideoVclPlayer
和 VideoMp4Player
这两个类:
// player.js
// VLC 播放器
class VideoVclPlayer {
// ...
playVLC(fileName) {
console.log("播放 VLC 视频:", fileName);
}
}
// MP4 播放器
class VideoMp4Player {
// ...
playMp4(fileName) {
console.log("播放 MP4 视频:", fileName);
}
}
来看看是否满足需要:
// player.js
// 构建一个 audioPlayer 实例
const vlc = new VideoVclPlayer();
// 调用 playVLC,播放 VLC
vlc.playVLC("哪吒闹海"); // 播放 VLC 视频: 哪吒闹海
// 构建一个 audioPlayer 实例
const mp4 = new VideoMp4Player();
// 调用 playMp4,播放 MP4
mp4.playMp4("大闹天宫"); // 播放 MP4 视频: 大闹天宫
看到这个结果,张三非常满意的点了点头,就在他得意满满的时候,罗老板却一盆冷水泼了下来,他要求张三要让我们原来播放 MP3 的产品要兼容的播放新增的两种格式,还必须保证原来播放 MP3 的功能不受影响。
张三此时慌了,这该如何去实现呢?就在他一筹莫展的时候,无意间看到了插在电脑上的读卡器,顿时脑子里灵光一闪,他完全可以通过适配器模式的思想去实现啊。
于是他决定先实现一个用于播放视频的适配器 MediaAdapter
用于实现适配功能:
// player.js
// 视频的适配器
class MediaAdapter {
// ...
// 通过类型字段 type 判断
play(type, fileName) {
if (type === "VLC") {
// 构建 vlc 实例
const vlc = new VideoVclPlayer();
vlc.playVLC(fileName);
} else if (type === "MP4") {
// 构建 mp4 实例
const mp4 = new VideoMp4Player();
mp4.playMp4(fileName);
}
}
}
通过这个适配器,就可以实现识别视频格式的一端。
但是除了适配视频的一端,我们还需要对原产品的一端进行适配的修改,就像这样:
// player.js
// 这里需要注释掉原 AudioPlayer 类
class AudioPlayer extends MediaAdapter {
constructor() {
super(); // 这是 ES6 中 class 的内容
}
play(type, fileName) {
if (type === "MP3") {
console.log("播放 MP3 音乐:", fileName);
} else if (type === "VLC" || type === "MP4") {
super.play(type, fileName); // 调用继承类的方法
} else {
console.log("不支持该格式!");
}
}
}
现在让我们尝试使用一下改造后的 AudioPlayer 播放器能否满足老板的需求:
// player.js
// 通过原产品构建 audioPlayer 播放器实例
const audioPlayer = new AudioPlayer();
// 分别调用各种格式的内容
audioPlayer.play("MP3", "千里之外"); // 播放 MP3 音乐: 千里之外
audioPlayer.play("VLC", "哪吒闹海"); // 播放 VLC 视频: 哪吒闹海
audioPlayer.play("MP4", "大闹天宫"); // 播放 MP4 视频: 大闹天宫
audioPlayer.play("JPG", ""); // 不支持该格式!
从输出来看,经过改造后的产品完全能够满足老板的需要,张三也因此受到了老板的表彰。
经过上面的例子,相信大家对适配器能有个更加直观的了解,上面的视频适配器正是连通原产品和新的视频播放功能的“桥梁”。
到这里,有些同学可能会有这样一个疑问:为什么需要单独新增 MediaAdapter
这样一个适配器呢?完全可以直接在 AudioPlayer
中直接添加啊。
这个问题很好回答,首先请看这一段代码:
if (type === "MP3") {
console.log("播放 MP3 音乐:", fileName);
}
这是我们新的 AudioPlayer
类中的 play
方法中的一段,在这里你可能觉得这样一句输出语句完全没有必要单独包裹在一个 if
判断中,然而这里我们知识为了展示,实际这里的处理逻辑会非常的复杂,涉及到各种声音的处理。
第二点,我们单独隔离出去 MP3 的部分,是因为不能保证以后会有新的播放格式加入进来,通过适配器我们可以很方便的再适配器中添加相关的逻辑,而完全不会影响到原产品播放 MP3 能力。
请再一次明确老板的需求:要求 AudioPlayer
能兼容其他格式,而不是想为它中添加新的功能.
因此使用适配器的方式处理是符合要求的。
适配器的概念其实是比较简单的,通过这个例子相应大家一定能很容易的理解适配器和适配器模式。
以上是为了帮助大家理解适配器而举的例子,作为一名前端 er,我们也需要了解适配器在前端中的应用,因此下一小节会为大家介绍在前端中的适配器应用场景。
适配器的应用
axios 相信很多同学都不陌生,它是我们在前端中用于网络请求的一项技术。通过 axios 可轻松地发起各种姿势的网络请求,而不用去关心底层的实现细节。
这里之所以提到它,也正是因其用到了适配器模式,同时它的兼容方案也值得我们学习和借鉴。
通常在 axios 的使用中,较常用的三种 api 为:
// get 请求
axios
.get("/xxx?name=zhangsan")
.then(function (response) {
// 请求成功时,调用成功的回调函数
console.log(response);
})
.catch(function (error) {
// 请求异常时,调用异常的回调函数
console.log(error);
})
.then(function () {
// 继续进行链式处理
});
// post 请求
axios
.post("/xxx", {
name: "zhangsan",
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
// 通用请求,通过 method 字段判断请求类型
axios({
method: "post",
url: "/xxx/xxxx",
data: {
name: "zhangsan",
},
});
axios 厉害的点在于,它不仅可以在浏览器环境下使用,在 node 环境中它仍然有效,用户完全不用担心二者因为环境不同而对 axios 的使用进行区别。
axios 靠着对适配器模式的应用,消除了不同环境下调用它的 api 的差异,用户仅仅需要考虑业务,而不用分心去主动适配不同环境下的网络请求。这一点可以放心的交给 axios 去帮助我们解决。
接下来,就让我们看看在 axios 内部,是如何进行适配的。
dispatchRequest
方法是 axios 中负责派发请求的核心方法:
function dispatchRequest(config) {
throwIfCancellationRequested(config);
// ...
// api 类型
utils.forEach(
["delete", "get", "head", "post", "put", "patch", "common"],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
// 判断是自定义适配器还是默认适配器
var adapter = config.adapter || defaults.adapter;
// 执行适配器
return adapter(config).then(
function onAdapterResolution(response) {
// 请求成功的回调
throwIfCancellationRequested(config);
// ...
return response;
},
function onAdapterRejection(reason) {
// 请求失败的回调
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// ...
}
return Promise.reject(reason);
}
);
}
在上面这段代码中,有这么一句,是为了区分自定义配置器和默认配置器的:
var adapter = config.adapter || defaults.adapter;
通常情况下,我们都使用的默认适配器,那么这里的默认适配器又是怎么样的:
function getDefaultAdapter() {
var adapter;
if (
typeof process !== "undefined" &&
Object.prototype.toString.call(process) === "[object process]"
) {
// node 环境,调用node专属的http适配器
adapter = require("./adapters/http");
} else if (typeof XMLHttpRequest !== "undefined") {
// 非 node 环境,调用基于xhr的适配器
adapter = require("./adapters/xhr");
}
return adapter;
}
可以看到,默认情况下 axios 内部是可以适配不同的环境的。对于用户而言,这些都不需要考虑,交给 axios 就行。
把所有的工作都交给自己,而给用户提供最简单的使用方式,这就是适配器的伟大之处。
这里我们提到 axios 只是为了让大家了解适配器在前端中的真实应用,而 axios 的内容是不仅这一点的,如果想了解更多内容可以参考:
小结
下面对适配器做一个简单的小结:
- 适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。
- 灵活的修改一个正常运行的系统的接口,该接口已经在项目大量调用,这时应该考虑使用适配器模式。
- 这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。就像我们上面提到的
MediaAdapter
一样,在MediaAdapter
中添加了播放视频的能力,把MediaAdapter
作为兼容的接口。
装饰器模式与适配器模式的区别
经过前面的学习,大家一定对适配器有了清晰的认识,不过刚开始接触设计模式的同学,有时候会把适配器模式和装饰器搞混,在这里将二者做出一个区分:
- 装饰器模式,在不修改原方法的基础上,再添加新的功能。关键点在于,原本的方法是不需要改动的。
- 适配器模式,其主要目的是兼容,即提供适配器来进行兼容,关键点在于,要保证原有的功能的情况下去兼容执行其他的功能。而并非为了添加新的功能。
这是二者的区别,希望同学们在完全了解两种模式的情况下来揣摩这里的区分描述。
实验挑战
现在市场是又出现了一种 mpeg 视频格式,请同学们在上面张三实现的适配器的基础上增加对 mpeg 格式的兼容。
最后模拟输出效果是这样的:
audioPlayer.play("MPEG", "MPEG 新格式视频"); // 播放 MPEG 视频: MPEG 新格式视频
答案代码放在实验最后课程的源码包里,大家可以自行下载。
实验总结
在实验的一开始,为大家通过读卡器等小例子帮助大家建立直观的认识,随后用罗老板和张三之间生产播放器的例子展开对代码的逐步实现,一层一层的为大家讲解适配器出现的原因和实现的过程,相信在这一过程中,大家能对适配器有一个较深的理解。随后为大家介绍了 axios 这一前端中应用极广的网络请求技术。旨在帮助大家了解适配器在前端中的真实应用。最后希望大家能认真完成实验挑战,检验自己的理解程度。
本节实验源码压缩包下载链接:适配器模式源码
标签:axios,适配器,模式,播放,MP3,fileName,设计模式,结构型 From: https://www.cnblogs.com/xzemt/p/18030131