适配器模式(Adapter Pattern)是一种结构型设计模式,它通过将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以一起工作。在Java中,适配器模式可以通过实现一个适配器类来实现两个不兼容接口之间的转换。
各角色定义
在适配器模式中,通常会涉及以下四种角色:
- 目标(Target)
定义客户端所期望的接口。可以是具体的类或者抽象类。 - 适配者(Adaptee)
拥有一个已经存在的接口,该接口与目标接口不兼容。这个已有的接口通常是一个没有继承和实现关系的旧接口。 - 适配器(Adapter)
充当适配器的角色,实现目标接口。
持有适配者的实例,通过适配者来实现目标接口的方法。负责将适配者的功能转换为目标接口期望的形式。 - 客户端(Client)
使用目标接口来调用适配器的方法。客户端只知道目标接口,并不知道适配器和适配者的存在。
以上如图所示:
- Target类表示目标接口,它有一个request()方法。
- Adaptee类表示已有的不兼容接口,它有一个specificRequest()方法。
- Adapter类是适配器,它实现了Target接口,并持有一个Adaptee实例。在request()方法中,调用Adaptee的specificRequest()方法来实现适配。
具体示例
下面我们介绍使用适配器来播放不同类型的音频文件的代码示例:
- 定义客户端需要的接口,比如播放指定类型的音频文件,入参是文件类型和文件名称:
public interface MediaPlayer {void play(String audioType, String fileName);
}
- 一个已有的接口(Adaptee)和实现类:
public interface AdvancedMediaPlayer {void playVlc(String fileName);void playMp4(String fileName);
}
public class VlcPlayer implements AdvancedMediaPlayer {@Overridepublic void playVlc(String fileName) {System.out.println("Playing vlc file. Name: " + fileName);}@Overridepublic void playMp4(String fileName) {// Do nothing}
}public class Mp4Player implements AdvancedMediaPlayer {@Overridepublic void playVlc(String fileName) {// Do nothing}@Overridepublic void playMp4(String fileName) {System.out.println("Playing mp4 file. Name: " + fileName);}
}
- 创建适配器类(Adapter),实现目标接口,并持有已有接口的实例:
public class MediaAdapter implements MediaPlayer {private AdvancedMediaPlayer advancedMediaPlayer;public MediaAdapter(String audioType) {if (audioType.equalsIgnoreCase("vlc")) {advancedMediaPlayer = new VlcPlayer();} else if (audioType.equalsIgnoreCase("mp4")) {advancedMediaPlayer = new Mp4Player();}}@Overridepublic void play(String audioType, String fileName) {if (audioType.equalsIgnoreCase("vlc")) {advancedMediaPlayer.playVlc(fileName);} else if (audioType.equalsIgnoreCase("mp4")) {advancedMediaPlayer.playMp4(fileName);}}
}
- 使用适配器模式
public class AudioPlayer implements MediaPlayer {private MediaAdapter mediaAdapter;@Overridepublic void play(String audioType, String fileName) {if (audioType.equalsIgnoreCase("mp3")) {System.out.println("Playing mp3 file. Name: " + fileName);} else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {mediaAdapter = new MediaAdapter(audioType);mediaAdapter.play(audioType, fileName);} else {System.out.println("Invalid media. " + audioType + " format not supported");}}
}
应用场景
适配器模式适用于以下场景:
当需要在一个已有系统中引入新的功能或者接口时,它与系统的目标接口不兼容,但又不能修改原有代码时,可以使用适配器模式。例如在一个数据库操作系统中,如果想要支持多种类型的数据库源,但系统只提供了一个固定类型数据库源的操作接口时,可以使用一个数据库源操作适配器来将不同类型数据库源转换成统一类型数据库源。
当需要在多个独立开发的系统或者组件之间进行协作时,但由于各自采用了不同的接口或者协议时,可以使用适配器模式。例如在一个分布式服务系统中,如果想要让不同语言编写的服务之间进行通信和调用,但各自采用了不同的通信协议和数据格式时,可以使用一个服务通信适配器来将不同协议和数据格式转换成统一协议和数据格式。
优点
适配器模式可以增强程序的可扩展性,通过使用适配器,可以在不修改原有代码的基础上引入新的功能或者接口。
适配器模式可以提高类的复用性,通过使用适配器,可以将已有的类或者接口重新组合和封装,使其符合新的需求。
适配器模式可以增加类的透明度,通过使用适配器,客户端只需要关注目标接口,而无需了解被适配者的具体实现。
适配器模式可以灵活地切换不同的被适配者,通过使用不同的适配器,可以动态地选择不同的被适配者来满足不同的场景。
缺点
适配器模式会增加系统的复杂性,过多地使用适配器会使系统变得零乱和难以理解。
适配器模式可能会降低系统的性能,因为每次调用目标接口时都需要经过适配器的转换。
适配器模式可能会违反开闭原则,如果目标接口发生变化,则需要修改所有的适配器类。