适配器模式 (Adapter Pattern)
概述
适配器模式是一种结构型设计模式,它将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
意图
- 将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
适用场景
- 需要使用现有的类,而它的接口不符合需要的接口
- 想要创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作
- 需要使用几个现有的子类,但通过对每个子类进行子类化来适配它们的接口不现实
结构
适配器模式有两种实现方式:类适配器(使用继承)和对象适配器(使用组合)。
类适配器结构
┌─────────────┐ ┌─────────────┐
│ Client │──────────>│ Target │
├─────────────┤ ├─────────────┤
│ │ │ + request() │
└─────────────┘ └─────────────┘
▲
│
┌─────────────┐ ┌─────────────┐
│ Adaptee │<─────────│ Adapter │
├─────────────┤ ├─────────────┤
│ + specificRequest()│ │ + request() │
└─────────────┘ └─────────────┘
对象适配器结构
┌─────────────┐ ┌─────────────┐
│ Client │──────────>│ Target │
├─────────────┤ ├─────────────┤
│ │ │ + request() │
└─────────────┘ └─────────────┘
▲
│
┌─────────────┐ ┌─────────────┐
│ Adaptee │ │ Adapter │
├─────────────┤ ├─────────────┤
│ + specificRequest()│ │ - adaptee │
└─────────────┘ │ + request() │
└─────────────┘
参与者
- Target:定义客户使用的与特定领域相关的接口
- Client:与符合Target接口的对象协同工作
- Adaptee:定义一个已经存在的接口,这个接口需要适配
- Adapter:对Adaptee的接口与Target接口进行适配
示例代码
对象适配器示例
下面是一个完整的对象适配器模式示例,以电源适配器为例:
java
// Target - 目标接口
public interface PowerSupply {
void provide5V();
}
// Adaptee - 被适配者
public class PowerSocket {
public void provide220V() {
System.out.println("提供220V交流电");
}
}
// Adapter - 适配器
public class PowerAdapter implements PowerSupply {
private PowerSocket powerSocket;
public PowerAdapter(PowerSocket powerSocket) {
this.powerSocket = powerSocket;
}
@Override
public void provide5V() {
System.out.println("电源适配器开始工作...");
powerSocket.provide220V();
System.out.println("将220V交流电转换为5V直流电");
System.out.println("提供5V直流电");
}
}
// Client - 客户端
public class MobilePhone {
private PowerSupply powerSupply;
public MobilePhone(PowerSupply powerSupply) {
this.powerSupply = powerSupply;
}
public void charge() {
System.out.println("手机开始充电...");
powerSupply.provide5V();
System.out.println("手机充电完成");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 创建电源插座(220V)
PowerSocket powerSocket = new PowerSocket();
// 创建电源适配器
PowerAdapter powerAdapter = new PowerAdapter(powerSocket);
// 创建手机并充电
MobilePhone mobilePhone = new MobilePhone(powerAdapter);
mobilePhone.charge();
}
}
类适配器示例
java
// Target - 目标接口
public interface PowerSupply {
void provide5V();
}
// Adaptee - 被适配者
public class PowerSocket {
public void provide220V() {
System.out.println("提供220V交流电");
}
}
// Adapter - 适配器(类适配器,使用继承)
public class PowerClassAdapter extends PowerSocket implements PowerSupply {
@Override
public void provide5V() {
System.out.println("电源适配器开始工作...");
provide220V();
System.out.println("将220V交流电转换为5V直流电");
System.out.println("提供5V直流电");
}
}
// Client - 客户端
public class MobilePhone {
private PowerSupply powerSupply;
public MobilePhone(PowerSupply powerSupply) {
this.powerSupply = powerSupply;
}
public void charge() {
System.out.println("手机开始充电...");
powerSupply.provide5V();
System.out.println("手机充电完成");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 创建电源适配器(类适配器)
PowerClassAdapter powerAdapter = new PowerClassAdapter();
// 创建手机并充电
MobilePhone mobilePhone = new MobilePhone(powerAdapter);
mobilePhone.charge();
}
}
另一个示例 - 音频播放器
java
// Target - 目标接口
public interface MediaPlayer {
void play(String audioType, String fileName);
}
// Adaptee - 被适配者
public class AdvancedMediaPlayer {
public void playVlc(String fileName) {
System.out.println("播放VLC文件: " + fileName);
}
public void playMp4(String fileName) {
System.out.println("播放MP4文件: " + fileName);
}
}
// Adapter - 适配器
public class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedMediaPlayer;
public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMediaPlayer = new AdvancedMediaPlayer();
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMediaPlayer = new AdvancedMediaPlayer();
}
}
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMediaPlayer.playVlc(fileName);
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMediaPlayer.playMp4(fileName);
}
}
}
// ConcreteTarget - 具体目标
public class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
// 播放MP3音乐文件的内置支持
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("播放MP3文件: " + fileName);
}
// mediaAdapter提供了对其他文件格式的支持
else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("无效的媒体格式: " + audioType);
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
audioPlayer.play("avi", "mind me.avi");
}
}
双向适配器
java
// 两个需要相互适配的接口
public interface AmericanSocket {
void provide110V();
}
public interface EuropeanSocket {
void provide220V();
}
// 两个具体实现
public class AmericanSocketImpl implements AmericanSocket {
@Override
public void provide110V() {
System.out.println("提供110V电压");
}
}
public class EuropeanSocketImpl implements EuropeanSocket {
@Override
public void provide220V() {
System.out.println("提供220V电压");
}
}
// 双向适配器
public class SocketAdapter implements AmericanSocket, EuropeanSocket {
private AmericanSocket americanSocket;
private EuropeanSocket europeanSocket;
public SocketAdapter(AmericanSocket americanSocket) {
this.americanSocket = americanSocket;
}
public SocketAdapter(EuropeanSocket europeanSocket) {
this.europeanSocket = europeanSocket;
}
@Override
public void provide110V() {
if (europeanSocket != null) {
System.out.println("将220V转换为110V");
europeanSocket.provide220V();
} else {
americanSocket.provide110V();
}
}
@Override
public void provide220V() {
if (americanSocket != null) {
System.out.println("将110V转换为220V");
americanSocket.provide110V();
} else {
europeanSocket.provide220V();
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 美国插座
AmericanSocket americanSocket = new AmericanSocketImpl();
// 欧洲插座
EuropeanSocket europeanSocket = new EuropeanSocketImpl();
// 将美国插座适配为欧洲插座
EuropeanSocket adapter1 = new SocketAdapter(americanSocket);
adapter1.provide220V();
// 将欧洲插座适配为美国插座
AmericanSocket adapter2 = new SocketAdapter(europeanSocket);
adapter2.provide110V();
}
}
优缺点
优点
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码
- 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性
- 灵活性和扩展性都非常好,通过配置文件可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合"开闭原则"
缺点
- 过多使用适配器会使系统变得非常凌乱,不易整体进行把握
- 对于类适配器来说,由于Java是单继承语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用也有一定的局限性
相关模式
- 桥接模式:桥接模式与适配器模式类似,但桥接模式的目的是分离接口和实现,而适配器模式的目的是使接口不兼容的类能够协同工作
- 装饰器模式:装饰器模式在不改变对象结构的情况下,动态地给对象增加一些职责,而适配器模式则是改变接口
- 外观模式:外观模式为子系统中的所有接口提供一个统一的接口,而适配器模式则是为一个类提供另一个接口
实际应用
- Java中的Arrays.asList()方法
- Java中的InputStreamReader和OutputStreamWriter
- Spring框架中的HandlerAdapter
- Java中的java.util.Collections.enumeration()方法
- JDBC驱动程序中的适配器模式
类适配器与对象适配器的选择
- 类适配器:使用继承,适配器直接继承被适配者类,可以重写被适配者的方法,但只能适配一个被适配者类,且目标类必须是接口
- 对象适配器:使用组合,适配器持有被适配者类的实例,可以适配多个被适配者类,更灵活,是更常用的方式
在Java中,由于不支持多继承,对象适配器比类适配器更常用。