1. 动机
- 在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。
- 如何应对这种“迁移的变化”?如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?
2. 适配器模式定义
- 将一个类的接口转换为客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
3. 适配器结构图
4. 适配器实现
demo1
// 目标接口(新接口)
class ITarget {
public:
virtual void process() = 0;
};
// 遗留接口(老接口)
class IAdaptee {
public:
virtual void foo(int data) = 0;
virtual int bar() = 0;
}
class OldClass : IAdaptee {
virtual void foo(int data)
{
}
virtual int bar()
{
}
}
// 适配器
// 继承新的接口,组合老的接口
// 使用老接口的方法,实现新的接口
class Adapter : public ITarget {
public:
IAdaptee* pAdaptee;
public:
Adapter(IAdaptee* pAdaptee)
{
pAdaptee = pAdaptee;
}
virtual void process() {
int data = pAdaptee->bar();
pAdaptee->foo(data);
}
}
int main()
{
IAdaptee* pAdaptee = new OldClass();
ITarget* pTarget = new Adapter(pAdaptee);
pTarget->process();
}
demo2: 来自https://www.runoob.com/design-pattern/adapter-pattern.html
我们想要让 AudioPlayer 播放其他格式的音频文件。为了实现这个功能,我们需要创建一个实现了 MediaPlayer 接口的适配器类 MediaAdapter,并使用 AdvancedMediaPlayer 对象来播放所需的格式。
步骤1:为媒体播放器和更高级的媒体播放器创建接口。
public interface MediaPlayer {
public void play(String audioType, String fileName);
}
public interface AdvancedMediaPlayer {
public void playVlc(String fileName);
public void playMp4(String fileName);
}
步骤 2:创建实现了 AdvancedMediaPlayer 接口的实体类。
public class VlcPlayer implements AdvancedMediaPlayer{
@Override
public void playVlc(String fileName) {
System.out.println("Playing vlc file. Name: "+ fileName);
}
@Override
public void playMp4(String fileName) {
//什么也不做
}
}
public class Mp4Player implements AdvancedMediaPlayer{
@Override
public void playVlc(String fileName) {
//什么也不做
}
@Override
public void playMp4(String fileName) {
System.out.println("Playing mp4 file. Name: "+ fileName);
}
}
步骤 3:创建实现了 MediaPlayer 接口的适配器类。
public class MediaAdapter implements MediaPlayer {
AdvancedMediaPlayer advancedMusicPlayer; // 组合更高级的媒体播放器
MediaAdapter(String audioType){
if(audioType.equalsIgnoreCase("vlc") ){
advancedMusicPlayer = new VlcPlayer();
} else if (audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer = new Mp4Player();
}
}
@Override
public void play(String audioType, String fileName) {
if(audioType.equalsIgnoreCase("vlc")){
advancedMusicPlayer.playVlc(fileName);
}else if(audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer.playMp4(fileName);
}
}
}
步骤 4:创建实现了 MediaPlayer 接口的实体类。
public class AudioPlayer implements MediaPlayer {
MediaAdapter mediaAdapter; // 组合适配器类
@Override
public void play(String audioType, String fileName) {
//播放 mp3 音乐文件的内置支持
if(audioType.equalsIgnoreCase("mp3")){
System.out.println("Playing mp3 file. Name: "+ fileName);
}
//mediaAdapter 提供了播放其他文件格式的支持
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");
}
}
}
步骤 5 使用 AudioPlayer 来播放不同类型的音频格式。
public class AdapterPatternDemo {
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");
}
}
注: 学习设计模式不能太死板,不是必须使用GOF这种代码才能实现某种设计模式,不然会误入歧途,哈哈。注重学习使用这种设计模式的场景,解决问题的手法,对变化点和稳定点的分离方式,是不是符合模式的定义。
5. 总结概要
- Adapter模式主要应用于“希望复用一些存在的类”,但是接口又与复用环境要求不一致的情况。在遗留代码复用、类库迁移等方面非常有用。
- GOF23定义两种Adapter模式的实现结构:对象适配器和类适配器。但是类适配器采用多继承的实现方式,一般不推荐使用。对象适配器采用“对象组合”方式,更加符合松耦合。
- Adapter模式可以实现的非常灵活,不必拘泥于gof23中定义的两种结构。例如,完全可以将Adapter模式中的“现存对象”作为新的接口方法参数,来达到适配的目的。