Java设计模式-适配器模式
Java 设计模式——适配器模式(Adapter Pattern)
适配器模式把一个“不兼容”的接口转换成客户端所期待的接口,使得原本由于接口不匹配而无法一起工作的类可以协同工作。这种模式也叫“Wrapper”。
在 Java 生态中,大量应用(JDBC-ODBC Bridge、XML 解析器、日志门面 slf4j、Spring 的统一消息转换器等)都在使用适配器模式。
一、模式结构与角色
- Target(目标接口):客户端直接使用的抽象/接口。
- Adaptee(被适配者):已经存在的、接口不兼容的类或第三方库。
- Adapter(适配器):把 Adaptee 包装起来,实现 Target,完成接口转换。
有两种实现方式:
- 类适配器(Class Adapter):使用多重继承(Java 不支持多继承,只能用继承+实现),继承 Adaptee 并实现 Target。
- 对象适配器(Object Adapter):推荐方式,持有 Adaptee 对象的组合引用,实现 Target;松耦合、符合“组合复用原则”。
时序图(Object Adapter):
Client --> Target.request()|vAdapter|+---> adaptee.specificRequest()
二、代码示例
场景说明
老播放系统只能播放 mp3,新需求要支持 mp4/vlc,但 mp4、vlc 接口并不一样。采用适配器统一成可识别接口 MediaPlayer。
1. 定义目标接口 & 业务实体
//目标接口
public interface MediaPlayer {void play(String audioType, String fileName);
}//高级播放器接口(需要被适配)
public interface AdvancedMediaPlayer {void playVlc(String fileName);void playMp4(String fileName);
}
2. 两个具体的 Adaptee
public class VlcPlayer implements AdvancedMediaPlayer {public void playVlc(String fileName) { System.out.println("播放 vlc: " + fileName); }public void playMp4(String fileName) { /* 空实现 */ }
}public class Mp4Player implements AdvancedMediaPlayer {public void playVlc(String fileName) { /* 空实现 */ }public void playMp4(String fileName) { System.out.println("播放 mp4: " + fileName); }
}
3. 对象适配器
public class MediaAdapter implements MediaPlayer {private AdvancedMediaPlayer advancedPlayer;public MediaAdapter(String audioType) {switch (audioType.toLowerCase()) {case "vlc": advancedPlayer = new VlcPlayer(); break;case "mp4": advancedPlayer = new Mp4Player(); break;}}@Overridepublic void play(String audioType, String fileName) {switch (audioType.toLowerCase()) {case "vlc": advancedPlayer.playVlc(fileName); break;case "mp4": advancedPlayer.playMp4(fileName); break;default: throw new IllegalArgumentException("格式不支持");}}
}
4. 客户端使用
public class AudioPlayer implements MediaPlayer {private MediaAdapter mediaAdapter;@Overridepublic void play(String audioType, String fileName) {if ("mp3".equalsIgnoreCase(audioType)) {// 本地支持的格式System.out.println("播放 mp3: " + fileName);} else if ("vlc".equalsIgnoreCase(audioType) || "mp4".equalsIgnoreCase(audioType)) {mediaAdapter = new MediaAdapter(audioType);mediaAdapter.play(audioType, fileName);} else {System.out.println("不支持 " + audioType + " 格式");}}// 运行示例public static void main(String[] args) {AudioPlayer player = new AudioPlayer();player.play("mp3", "beyond.mp3");player.play("mp4", "alone.mp4");player.play("vlc", "far far away.vlc");player.play("avi", "mind me.avi"); //输出:不支持 avi 格式}
}
输出
播放 mp3: beyond.mp3
播放 mp4: alone.mp4
播放 vlc: far far away.vlc
不支持 avi 格式
三、JDK & 三方库经典用例
java.util.Arrays#asList(T…)
:将数组对象适配成List
。java.io.InputStreamReader
/OutputStreamWriter
:把字节流 Adaptee 适配成字符流 Target。javax.xml.bind.annotation.adapters
:把 XML 日期与java.util.Date
互相转换。- Spring MVC
HandlerAdapter
:将不同类型的后端 Handler(普通@Controller
、Servlet、带@ResponseBody
的方法等)适配成统一的handle
调用。 - slf4j:所有桥接包(log4j-over-slf4j、jcl-over-slf4j)都是适配器,把旧日志系统接口伪装成 slf4j 接口。
四、优缺点
优势
- 透明:客户端只依赖 Target,根本不知道 Adaptee。
- 复用:现有类无需修改,符合“开闭原则”。
- 灵活性:可以一次封装多个 Adaptee(见 Spring MVC)。
劣势
- 类适配器只能适配一个具体类,且会暴露 Adaptee 的 public 方法(继承导致紧耦合)。
- 过度使用会导致系统层次变多、不易理解。
五、何时使用
- 想复用某个现成类,但接口不符合需要。
- 希望创建一个可重用类,与若干不可预见接口的类一起工作。
- 需要转换多种已存在的子类接口,统一对外暴露接口时(如 Spring
Adapter
)。 - 需要桥接旧系统(遗留代码)与新系统。