适配器模式 (Adapter)
📖 通俗理解
出国旅行时,你带的是中国的电器插头(两孔/三孔),但国外的插座是圆孔的。怎么办?
买一个转换插头!它一边连接你的插头,一边连接国外的插座,让它们能一起工作。
适配器模式就是:让两个不兼容的接口可以一起工作,起到一个"转换器"的作用。
🎯 解决什么问题?
场景:你有一个旧的系统,现在要接入一个新的第三方库,但接口不兼容。
// 旧系统的接口
interface OldSystem {
void oldMethod();
}
// 新的第三方库
class NewLibrary {
void newMethod() { ... }
}
// 问题:旧系统调用 oldMethod(),但新库只有 newMethod()
// 怎么让它们一起工作?
解决方案:写一个适配器,把新库的接口"翻译"成旧系统能理解的接口。
🌰 生活中的例子
- 电源适配器:把220V转换成笔记本需要的电压
- 翻译官:让说不同语言的人交流
- 读卡器:让电脑能读取SD卡
- Type-C转接头:让老设备能用新接口
💻 Java 代码实现
场景:播放器适配
我们有一个只能播放 MP3 的老播放器,现在要让它也能播放 MP4 和 VLC 格式。
方式一:类适配器(继承方式)
/**
* 目标接口:客户端期望的接口
*/
public interface MediaPlayer {
void play(String audioType, String fileName);
}
/**
* 被适配者:高级播放器(只会播放高级格式)
*/
public class AdvancedMediaPlayer {
public void playVlc(String fileName) {
System.out.println("播放 VLC 文件: " + fileName);
}
public void playMp4(String fileName) {
System.out.println("播放 MP4 文件: " + fileName);
}
}
/**
* 适配器:继承被适配者,实现目标接口
*/
public class MediaAdapter extends AdvancedMediaPlayer implements MediaPlayer {
@Override
public void play(String audioType, String fileName) {
if ("vlc".equalsIgnoreCase(audioType)) {
playVlc(fileName);
} else if ("mp4".equalsIgnoreCase(audioType)) {
playMp4(fileName);
}
}
}
/**
* 音频播放器:使用适配器
*/
public class AudioPlayer implements MediaPlayer {
private MediaAdapter adapter;
@Override
public void play(String audioType, String fileName) {
// 内置支持 MP3
if ("mp3".equalsIgnoreCase(audioType)) {
System.out.println("播放 MP3 文件: " + fileName);
}
// 使用适配器播放其他格式
else if ("vlc".equalsIgnoreCase(audioType) || "mp4".equalsIgnoreCase(audioType)) {
adapter = new MediaAdapter();
adapter.play(audioType, fileName);
}
else {
System.out.println("不支持的格式: " + audioType);
}
}
}
测试代码:
public class Client {
public static void main(String[] args) {
AudioPlayer player = new AudioPlayer();
player.play("mp3", "beyond.mp3");
player.play("mp4", "alone.mp4");
player.play("vlc", "movie.vlc");
player.play("avi", "video.avi");
}
}
输出:
播放 MP3 文件: beyond.mp3
播放 MP4 文件: alone.mp4
播放 VLC 文件: movie.vlc
不支持的格式: avi
方式二:对象适配器(组合方式)⭐ 推荐
/**
* 目标接口
*/
public interface Target {
void request();
}
/**
* 被适配者:已有的类,接口不兼容
*/
public class Adaptee {
public void specificRequest() {
System.out.println("Adaptee: 执行特殊请求");
}
}
/**
* 对象适配器:通过组合持有被适配者
*/
public class ObjectAdapter implements Target {
private Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
// 把客户端的请求转换为被适配者能理解的方法
System.out.println("Adapter: 转换请求...");
adaptee.specificRequest();
}
}
使用方式:
public class Client {
public static void main(String[] args) {
// 创建被适配者
Adaptee adaptee = new Adaptee();
// 用适配器包装
Target target = new ObjectAdapter(adaptee);
// 客户端只和 Target 接口打交道
target.request();
}
}
🔥 实战案例:接入第三方登录
假设你的系统有统一的登录接口,现在要接入微信、支付宝等第三方登录。
/**
* 系统统一的登录接口
*/
public interface LoginService {
/**
* 统一登录方法
* @param token 登录凭证
* @return 用户信息
*/
UserInfo login(String token);
}
/**
* 用户信息
*/
public class UserInfo {
private String id;
private String name;
private String avatar;
// getter setter constructor...
}
// ========== 第三方 SDK(接口各不相同)==========
/**
* 微信 SDK
*/
public class WechatApi {
public WechatUser getWechatUser(String code) {
System.out.println("调用微信API,code: " + code);
return new WechatUser("wx_123", "微信用户", "wx_avatar.png");
}
}
public class WechatUser {
private String openId;
private String nickname;
private String headImgUrl;
// ...
}
/**
* 支付宝 SDK
*/
public class AlipayApi {
public AlipayUser getUserInfo(String authCode) {
System.out.println("调用支付宝API,authCode: " + authCode);
return new AlipayUser("ali_456", "支付宝用户", "ali_avatar.png");
}
}
public class AlipayUser {
private String userId;
private String userName;
private String avatar;
// ...
}
// ========== 适配器:将第三方接口适配为统一接口 ==========
/**
* 微信登录适配器
*/
public class WechatLoginAdapter implements LoginService {
private WechatApi wechatApi = new WechatApi();
@Override
public UserInfo login(String token) {
// 调用微信 API
WechatUser wechatUser = wechatApi.getWechatUser(token);
// 转换为统一的用户信息格式
return new UserInfo(
"wechat_" + wechatUser.getOpenId(),
wechatUser.getNickname(),
wechatUser.getHeadImgUrl()
);
}
}
/**
* 支付宝登录适配器
*/
public class AlipayLoginAdapter implements LoginService {
private AlipayApi alipayApi = new AlipayApi();
@Override
public UserInfo login(String token) {
// 调用支付宝 API
AlipayUser alipayUser = alipayApi.getUserInfo(token);
// 转换为统一的用户信息格式
return new UserInfo(
"alipay_" + alipayUser.getUserId(),
alipayUser.getUserName(),
alipayUser.getAvatar()
);
}
}
使用方式:
public class LoginController {
public UserInfo login(String type, String token) {
LoginService loginService;
switch (type) {
case "wechat":
loginService = new WechatLoginAdapter();
break;
case "alipay":
loginService = new AlipayLoginAdapter();
break;
default:
throw new IllegalArgumentException("不支持的登录方式: " + type);
}
// 统一的调用方式
return loginService.login(token);
}
public static void main(String[] args) {
LoginController controller = new LoginController();
// 微信登录
UserInfo user1 = controller.login("wechat", "wx_code_123");
System.out.println("登录成功: " + user1.getName());
// 支付宝登录
UserInfo user2 = controller.login("alipay", "ali_code_456");
System.out.println("登录成功: " + user2.getName());
}
}
📊 类图对比
类适配器(继承)
┌────────────┐ ┌────────────┐
│ Target │ │ Adaptee │
│ +request() │ │ +specific │
│ │ │ Request() │
└─────▲──────┘ └─────▲──────┘
│ │
│ ┌──────────────┘
│ │
┌────┴───┴────┐
│ Adapter │ 继承 Adaptee
│ +request() │ 实现 Target
└─────────────┘
对象适配器(组合)⭐
┌────────────┐ ┌────────────┐
│ Target │ │ Adaptee │
│ +request() │ │ +specific │
│ │ │ Request() │
└─────▲──────┘ └─────────────┘
│ ▲
│ │ 组合
┌────┴───────┐ │
│ Adapter │──────────┘
│ -adaptee │ 持有 Adaptee 引用
│ +request() │
└────────────┘
✅ 适用场景
- 使用第三方库:接口不兼容时
- 系统升级:新老接口不一致
- 统一接口:多个类似功能的类,统一为一个接口
⚠️ 类适配器 vs 对象适配器
| 对比项 | 类适配器 | 对象适配器 |
|---|---|---|
| 实现方式 | 继承 | 组合 |
| 灵活性 | 低(Java 单继承限制) | 高 |
| 可适配范围 | 只能适配一个类 | 可适配多个类 |
| 推荐程度 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
小结
适配器模式的核心:在不修改原有代码的情况下,让不兼容的接口可以一起工作。
简单记忆:适配器 = 转换插头 = 翻译官
👉 下一篇:桥接模式
