适配器模式

适配器模式

适配器模式将一个类的接口,转换成客户期望的另一个接口。 适配器模式可以让原本接口不兼容的类可以合作无间。

设计原则 #

  • 针对接口编程,而不是针对实现编程
  • 多用组合,少用继承
  • 为交互对象之间的松耦合而努力
  • 类应该对拓展开放,而对修改关闭 (开放-关闭原则)
  • 依赖抽象,而不依赖具体类 (依赖倒置原则)

UML简图 #

classDiagram
direction LR
class Client{
    +Adaptor adaptor
    +otherOperations()
}
class Target {
    << Interface >>
    operationA()
}
Client *--> Adaptor
Adaptor ..|> Target
Adaptor *..> Adaptee
class Adaptor {
    +Adaptee adaptee
    operationA()
}
class Adaptee {
    << Interface >>
    operationB()
}

要点 #

  1. 适配器模式,通过创建"适配器"进行接口转换,可以让不兼容的接口变得兼容。
  2. 适配器模式让客户从接口的实现解耦。
  3. 注意适配器和装饰模式的区别,适配器改变接口以让其对不兼容的对象可用, 装饰模式是通过继承,赋予类新的行为和"职责"。
  4. 当需要使用一个现有的类而可用的接口并不适配时,考虑使用适配器模式。

对象适配器和类适配器

在Java中,看不到类适配器。类适配器,只有在支持多继承(C++)的语言中 适用。类似配器,就是被适配的对象为实例类,同时适配器继承之。

示例代码 #

Generated by Gemini 1.5-flash, revised.

普通的播放器只能播放音频,无法播放vlc和mp4等视频,需要借助“高级播放器”的能力。示例代码展示了如何让普通的媒体播放器具备播放视频的能力。

在这种情况下,如果不想修改普通媒体播放器已经存在的代码,就可以使用适配器模式,

Target(适配器的作用对象) #

1// 目标接口(Target) 
2// 普通的媒体播放器
3interface MediaPlayer {
4    void play(String audioType, String fileName);
5}

Adaptee(提供能力的对象) #

 1// 要适配的接口(Adaptee)
 2interface AdvancedMediaPlayer {
 3    void playVlc(String fileName);
 4    void playMp4(String fileName);
 5}
 6// 适配者的实现(Adaptee‘s implements)
 7class VlcPlayer implements AdvancedMediaPlayer {
 8    @Override
 9    public void playVlc(String fileName) {
10        System.out.println("正在播放 VLC 文件: " + fileName);
11    }
12    @Override
13    public void playMp4(String fileName) {
14            // do nothing
15    }
16}
17
18class Mp4Player implements AdvancedMediaPlayer {
19    @Override
20    public void playVlc(String fileName) {
21        // do nothing
22    }
23    @Override
24    public void playMp4(String fileName) {
25        System.out.println("正在播放 MP4 文件: " + fileName);
26    }
27}

Adapter(适配器) #

 1// 适配器类(Adapter)
 2class MediaAdapter implements MediaPlayer {
 3    private AdvancedMediaPlayer mediaPlayer;
 4
 5    public MediaAdapter(String mediaType) {
 6        if (mediaType.equalsIgnoreCase("vlc")) {
 7            mediaPlayer = new VlcPlayer();
 8        } else if (mediaType.equalsIgnoreCase("mp4")) {
 9            mediaPlayer = new Mp4Player();
10        }
11    }
12    @Override
13    public void play(String mediaType, String fileName) {
14         if (mediaType.equalsIgnoreCase("vlc")) {
15            mediaPlayer.playVlc(fileName);
16        } else if (mediaType.equalsIgnoreCase("mp4")) {
17            mediaPlayer.playMp4(fileName);
18        }
19    }
20}

客户端 #

 1// 客户端类
 2class AudioPlayer implements MediaPlayer {
 3    private MediaAdapter adapter;
 4
 5    public void play(String mediaType, String fileName) {
 6        if (mediaType.equalsIgnoreCase("mp3")) {
 7            System.out.println("正在播放 MP3 文件: " + fileName);
 8        } else if (mediaType.equalsIgnoreCase("vlc") 
 9            || mediaType.equalsIgnoreCase("mp4")) {
10            adapter = new MediaAdapter(mediaType);
11            adapter.play(mediaType, fileName);
12        } else {
13            System.out.println("不支持的文件格式");
14        }
15    }
16}
17
18public class AdapterPatternDemo {
19    public static void main(String[] args) {
20        AudioPlayer audioPlayer = new AudioPlayer();
21        audioPlayer.play("mp3", "beyond the horizon.mp3");
22        audioPlayer.play("vlc", "far far away.vlc");
23        audioPlayer.play("mp4", "alone.mp4");
24    }
25}

话外 #

感觉有点怪,究竟是怎样的场景,需要使用到适配器模式?我想肯定不会是 开发计划期或alpha版本开发期。

出现需要使用适配器的场景,应该是出现在代码迭代期,可能有更新版本(不兼容) 的API需要适配,又为了避免大量修改已经存在的代码。

可能,适配器模式是代码系统优化或者重构的时候,需要考虑的模式?