适配器模式大白话讲解
一句话概括
就像转接头,让不兼容的东西能够一起工作
现实生活比喻
场景1:电源转接头
- 问题:中国插头(扁的)插不进欧洲插座(圆的)
- 解决方案:用一个转接头,中国插头 → 转接头 → 欧洲插座
- 结果:完美使用!
场景2:翻译官
- 问题:中国人说中文,美国人说英文,互相听不懂
- 解决方案:请个翻译,中文 → 翻译 → 英文
- 结果:顺利沟通!
完整代码示例
场景:让旧式播放器支持新格式
java
/**
* 适配器模式 - 媒体播放器示例
*/
public class Main {
public static void main(String[] args) {
System.out.println("=== 传统MP3播放 ===");
MediaPlayer mp3Player = new MP3Player();
mp3Player.play("song.mp3");
System.out.println("\n=== 通过适配器播放新格式 ===");
MediaPlayer advancedPlayer = new MediaAdapter();
// 现在可以播放各种格式了!
advancedPlayer.play("movie.avi");
advancedPlayer.play("video.mp4");
advancedPlayer.play("animation.vlc");
advancedPlayer.play("music.mp3");
}
}
/**
* 目标接口 - 我们期望的播放器接口
*/
interface MediaPlayer {
void play(String fileName);
}
/**
* 已有类 - 只能播放MP3的老式播放器
*/
class MP3Player implements MediaPlayer {
@Override
public void play(String fileName) {
if (fileName.endsWith(".mp3")) {
System.out.println("播放MP3文件: " + fileName);
} else {
System.out.println("格式不支持: " + fileName);
}
}
}
/**
* 新功能接口 - 高级媒体播放器
*/
interface AdvancedMediaPlayer {
void playVlc(String fileName);
void playMp4(String fileName);
void playAvi(String fileName);
}
/**
* 新功能实现 - 具体的高级播放器
*/
class AdvancedPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("播放VLC文件: " + fileName);
}
@Override
public void playMp4(String fileName) {
System.out.println("播放MP4文件: " + fileName);
}
@Override
public void playAvi(String fileName) {
System.out.println("播放AVI文件: " + fileName);
}
}
/**
* 适配器 - 核心!让老播放器支持新格式
*/
class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedPlayer;
public MediaAdapter() {
this.advancedPlayer = new AdvancedPlayer();
}
@Override
public void play(String fileName) {
if (fileName.endsWith(".vlc")) {
advancedPlayer.playVlc(fileName);
} else if (fileName.endsWith(".mp4")) {
advancedPlayer.playMp4(fileName);
} else if (fileName.endsWith(".avi")) {
advancedPlayer.playAvi(fileName);
} else if (fileName.endsWith(".mp3")) {
// 适配器也可以直接处理MP3,或者委托给MP3Player
System.out.println("播放MP3文件: " + fileName);
} else {
System.out.println("格式不支持: " + fileName);
}
}
}
运行结果
=== 传统MP3播放 ===
播放MP3文件: song.mp3
=== 通过适配器播放新格式 ===
播放AVI文件: movie.avi
播放MP4文件: video.mp4
播放VLC文件: animation.vlc
播放MP3文件: music.mp3
更真实的例子:数据库连接适配器
java
/**
* 数据库连接适配器示例
*/
public class DatabaseExample {
public static void main(String[] args) {
// 老系统用的MySQL
Database mysql = new MySQLDatabase();
mysql.connect();
mysql.query("SELECT * FROM users");
System.out.println("\n=== 系统升级,要支持新数据库 ===");
// 新来的MongoDB不兼容老接口
Database adapter = new MongoDBAdapter();
adapter.connect();
adapter.query("db.users.find()");
}
}
/**
* 目标接口 - 老系统期望的数据库接口
*/
interface Database {
void connect();
void query(String sql);
}
/**
* 已有类 - MySQL数据库
*/
class MySQLDatabase implements Database {
@Override
public void connect() {
System.out.println("MySQL连接成功");
}
@Override
public void query(String sql) {
System.out.println("执行SQL: " + sql);
}
}
/**
* 需要适配的类 - MongoDB(不兼容的接口)
*/
class MongoDB {
public void mongoConnect() {
System.out.println("MongoDB连接成功");
}
public void find(String command) {
System.out.println("执行Mongo命令: " + command);
}
}
/**
* 适配器 - 让MongoDB看起来像传统数据库
*/
class MongoDBAdapter implements Database {
private MongoDB mongoDB;
public MongoDBAdapter() {
this.mongoDB = new MongoDB();
}
@Override
public void connect() {
mongoDB.mongoConnect(); // 适配:connect → mongoConnect
}
@Override
public void query(String sql) {
// 把SQL转换成MongoDB的查询语法
String mongoCommand = convertSQLToMongo(sql);
mongoDB.find(mongoCommand); // 适配:query → find
}
private String convertSQLToMongo(String sql) {
// 简单的转换逻辑(实际中会很复杂)
if (sql.startsWith("SELECT")) {
return "db." + sql.substring(sql.indexOf("FROM") + 5) + ".find()";
}
return sql;
}
}
适配器模式的三种形式
1. 类适配器(通过继承)
java
// 通过继承来实现适配(Java中不常用,因为单继承限制)
class ClassAdapter extends AdvancedPlayer implements MediaPlayer {
@Override
public void play(String fileName) {
if (fileName.endsWith(".mp4")) {
playMp4(fileName); // 直接调用父类方法
}
// ... 其他格式
}
}
2. 对象适配器(通过组合,推荐)
java
// 通过组合来实现适配(更灵活)
class ObjectAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedPlayer; // 组合
public ObjectAdapter() {
this.advancedPlayer = new AdvancedPlayer();
}
@Override
public void play(String fileName) {
// 委托给advancedPlayer
if (fileName.endsWith(".mp4")) {
advancedPlayer.playMp4(fileName);
}
}
}
3. 接口适配器(缺省适配器)
java
// 当接口方法太多时,提供默认空实现
abstract class MediaAdapter implements MediaPlayer {
public void play(String fileName) {} // 默认空实现
public void stop() {} // 默认空实现
public void pause() {} // 默认空实现
}
// 使用时只需要重写需要的方法
class SimplePlayer extends MediaAdapter {
@Override
public void play(String fileName) {
System.out.println("简单播放: " + fileName);
}
// 不需要实现stop和pause方法
}
适用场景(大白话版)
✅ 适合用适配器的场景:
-
系统升级
java// 老系统要接入新组件 OldSystem system = new OldSystem(); NewComponent adapter = new Adapter(newComponent); system.use(adapter); // 老系统无感知 -
第三方库集成
java// 第三方库接口不匹配 ThirdPartyLib lib = new ThirdPartyLib(); OurInterface adapter = new LibAdapter(lib); ourSystem.use(adapter); -
接口不兼容
java// 两个系统接口不同 SystemA systemA = new SystemA(); SystemB adapter = new ABAdapter(systemA); client.use(adapter);
❌ 不适合的场景:
- 可以直接修改源码的情况
- 系统设计阶段,应该直接设计统一接口
- 过度设计,如果只是临时用一次,不如直接写兼容代码
优缺点
优点:
- 解耦:客户端和目标类解耦
- 复用:可以复用现有的类
- 灵活:可以适配多个不同的类
缺点:
- 复杂度:增加了系统的类和对象数量
- 性能:多了一层调用,有轻微性能损失
总结
适配器模式就是:
- 中间人,在两个不兼容的接口之间搭桥
- 翻译官,把一种语言翻译成另一种语言
- 转接头,让不同的插头和插座能够连接
核心口诀:
接口不兼容,
适配器来帮忙。
旧瓶装新酒,
老树开新花!
就像现实中的:
- 🔌 电源转接头:让不同国家的电器都能用
- 💬 翻译软件:让不同语言的人能交流
- 🔄 数据线转换头:让不同接口的设备能连接
- 🎮 游戏手柄转换器:让新手柄能在老主机上用
记住:适配器是补救措施,不是首选方案!在系统设计时应该尽量避免需要适配器的情况。