设计模式结构型之适配器模式
在我们日常生活中,适配器其实是很常见的,举几个例子吧:
1、苹果手机从 7 版本开始,它的耳机接口和充电接口共用,并且都是方形孔。如果你想使用以前的圆形插孔耳机,就只能通过一个适配器来实现。
2、再比如美国电压采用 110V,而中国采用220 V,此时就需要一个适配器将 110 V 转换成 220 V,否则是无法对国内电子设备进行充电的。
通过这个例子我们可以看出,通过适配器我们可以构建出一座“桥梁”去连接两个原本不兼容的接口。而适配器模式正是对适配器的应用,
知道了适配器的定义,现在让我们看看如何用代码实现一个适配器吧。
现在考虑这样一个例子:
一开始,社会上流行的最多的是 MP3 这种音乐格式,那么由此便构建了 AudioPlayer 这一个实现类用于播放 MP3 格式的文件。就像这样:
class AudioPlayer {
// ...
play(fileName) {
console.log("播放 MP3 音乐:" , fileName)
}
}
const audioPlayer = new AudioPlayer();
audioPlayer.play("千里之外"); // 播放 MP3 音乐: 千里之外
但是随着技术进步,又出现了 VLC 和 MP4 这两种格式的文件。于是又构建了两个对应的实现类用于播放这两种格式的文件。像这样
class VideoVclPlayer {
// ...
playVLC(fileName) {
console.log("播放 VLC 视频:" , fileName);
}
}
class VideoMp4Player {
// ...
playMp4(fileName) {
console.log("播放 MP4 视频:" , fileName)
}
}
const vlc = new VideoVclPlayer();
vlc.playVLC("哪吒闹海"); // 播放 VLC 视频: 哪吒闹海
const mp4 = new VideoMp4Player();
mp4.playMp4("大闹天宫"); // 播放 MP4 视频: 大闹天宫
这个时候,生产 MP3 的老板着急了,他说我们的产品也要能播放另外两种视频格式的文件,并且要保证原来的播放器功能不能变动,那该如何去实现呢?
那么接下来我们就来实现老板的想法吧。
1、首先实现一个视频的适配器
class MediaAdapter {
// ...
play(type, fileName) {
if (type === "VLC") {
const vlc = new VideoVclPlayer();
vlc.playVLC(fileName)
} else if (type === "MP4") {
const mp4 = new VideoMp4Player();
mp4.playMp4(fileName);
}
}
}
2、通过这个适配器,我们就能在原本的 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 播放器:
const audioPlayer = new AudioPlayer();
audioPlayer.play("MP3", "千里之外"); // 播放 MP3 音乐: 千里之外
audioPlayer.play("VLC", "哪吒闹海"); // 播放 VLC 视频: 哪吒闹海
audioPlayer.play("MP4", "大闹天宫"); // 播放 MP4 视频: 大闹天宫
audioPlayer.play("JPG", ""); // 不支持该格式!
注意,在 AudioPlayer 中,我们仍然维持了原来的播放逻辑不变:
if (type === "MP3") {
console.log("播放 MP3 音乐:" , fileName);
}
也许有人会问,为什么需要单独新增 MediaAdapter 这样一个适配器呢?为什么不直接在 AudioPlayer 中直接添加?
这是因为我们的目的不是为了 AudioPlayer 中添加新的功能,而仅仅是为了它能兼容其他格式,因此使用适配器的方式处理是符合要求的。
适配器的概念其实是比较简单的,因此我们也没有对这一模式进行过多的描述。
这里对适配器模式做一个简单的小结吧:
1、适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。
2、灵活的修改一个正常运行的系统的接口,该接口已经在项目大量调用,这时应该考虑使用适配器模式。
3、这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。就像我们上面提到的 MediaAdapter 一样,在 MediaAdapter 中添加了播放视频的能力,把 MediaAdapter 作为兼容的接口。
最后,也许有同学会把装饰器模式和适配器模式弄混,这里做一个简单的区别:
装饰器模式是在原先功能的基础上扩展功能,而适配器模式是为了保证原功能的条件下去兼容执行其他功能。