适配器模式是一种结构型设计模式,它允许不兼容的接口之间进行协作,就像现实世界中的电源适配器一样,让不同规格的插头能够正常工作。
应用场景其实还挺多见的,比如目前你负责的一个接口,本来是只能解析JSon文件的,但是后来对接新的公司,他给你一份Excel的文件,同样还要用这个接口解析。原本两种接口是不兼容的,但是你在原接口的实现类里面注入一个适配器,而这个适配器是可以处理新类型的文件的,这样就让两个不兼容的接口,一起工作了。
优点与缺点
-
优点:
- 解决接口不兼容问题,提高类的复用性。
- 降低客户端与适配者的耦合度,符合 “开闭原则”(无需修改原有代码)。
-
缺点:
- 增加系统复杂度,引入额外的适配器类。
- 过多使用适配器可能导致代码逻辑混乱,不易维护。
举例:现在有这样一个要求,你原来有一个MP3(实现类),具有播放(抽象方法).mp3格式的媒体文件的功能(接口)。代码如下,很明显,如果是其它格式的媒体文件,就会报错。适配器的思想就是,让这个Mp3具有播放其他文件的能力。我们这里的播放:System.out.println("Playing mp3: "+file);写的可能过于简单了,实际上可能代码会很多,因此直接通过if else来修改会变更大量的代码。
public class Mp3Player implements MediaPlayer{
@Override
public void play(String type, String file) {
if(type.equals("mp3") && file.endsWith(".mp3")){
System.out.println("Playing mp3: "+file);
} else{
System.out.println("Not supported");
}
}
}
适配器模式做法:
- 先提出一个接口,该接口要求对于可能扩展的媒体文件,每种格式都有单一的处理,这样可以方便后期再次添加文件格式。
public interface AdvancedMediaPlayer {
public void playVlc(String fileName);
public void playMp4(String fileName);
}
2. 接口有了,可以构建几个实现类。Mp4Player专注于playMp4方法就好。Vlc同理
public class Mp4Player implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("Not Supported");
}
@Override
public void playMp4(String fileName) {
System.out.println("Playing MP4 file: " + fileName);
}
}
public class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("Playing VLC file: " + fileName);
}
@Override
public void playMp4(String fileName) {
System.out.println("Not Supported");
}
}
3. 接下来直接把两个实现类注入MP3Player??不,这就不是适配了。接下来要用到的是聚合的思想。我们真正要注入给MP3Player的是MediaAdapter,它就像是一个转接口,当MP3Player播放不了的时候,直接交给它就行。
//既然适配器要接手原来MP3处理不了的工作,那去先去实现相关的接口
public class MediaAdapter implements MediaPlayer {
//对于额外的媒体已经定义了一个统一的接口,那么直接在此处定义接口成员变量,因为具体用的是哪个子类,要看MP3遇到的是哪一种类型。
private final AdvancedMediaPlayer advancedMediaPlayer;
//根据媒体类型决定到底用那个子类
public MediaAdapter(String mediaType) {
if (mediaType.equalsIgnoreCase("mp4")) {
advancedMediaPlayer = new Mp4Player();
}else if (mediaType.equalsIgnoreCase("vlc")) {
advancedMediaPlayer = new VlcPlayer();
}else{
throw new IllegalArgumentException("Unsupported Media Type");
}
}
//每种子类的方法是不一样滴
@Override
public void play(String type, String file) {
if (type.equals("mp4")) {
advancedMediaPlayer.playMp4(file);
} else if (type.equals("vlc")) {
advancedMediaPlayer.playVlc(file);
}else{
System.out.println("Unsupported Media Type");
}
}
}
4.最后,把我们的MP3改成下面这样:
在MP3中不需要改动原来的处理逻辑(在这个例子中指的就是: System.out.println("Playing mp3: "+file);)那么如果碰到处理不了的,直接交给mediaAdapter,由于mediaAdapter实现了play方法,MP3就不需要管适配器用的是哪一个子类的具体方法,直接play()。适配器内部再根据mp3player的调用来实例化内部的成员,调用相关的方法。后续如果要再加新的媒体格式,只要在创建一个AdvancedMediaPlayer的子类,而在MP3Player中,仍无需大量改动,只加一个判断条件就可以。
public class Mp3Player implements MediaPlayer{
MediaAdapter mediaAdapter;
@Override
public void play(String type, String file) {
if(type.equals("mp3") && file.endsWith(".mp3")){
System.out.println("Playing mp3: "+file);
} else if (type.equals("mp4") || type.equals("vlc")) {
mediaAdapter = new MediaAdapter(type);
mediaAdapter.play(type, file);
} else{
System.out.println("Not supported");
}
}
}
类图放在最后: