Java设计模式-适配器模式

Java 设计模式------适配器模式(Adapter Pattern)

适配器模式把一个"不兼容"的接口转换成客户端所期待的接口,使得原本由于接口不匹配而无法一起工作的类可以协同工作。这种模式也叫"Wrapper"。

在 Java 生态中,大量应用(JDBC-ODBC Bridge、XML 解析器、日志门面 slf4j、Spring 的统一消息转换器等)都在使用适配器模式。

一、模式结构与角色

  1. Target(目标接口):客户端直接使用的抽象/接口。
  2. Adaptee(被适配者):已经存在的、接口不兼容的类或第三方库。
  3. Adapter(适配器):把 Adaptee 包装起来,实现 Target,完成接口转换。

有两种实现方式:

  • 类适配器(Class Adapter):使用多重继承(Java 不支持多继承,只能用继承+实现),继承 Adaptee 并实现 Target。
  • 对象适配器(Object Adapter)推荐方式,持有 Adaptee 对象的组合引用,实现 Target;松耦合、符合"组合复用原则"。

时序图(Object Adapter):

java 复制代码
Client --> Target.request()
             |
             v
          Adapter
             |
             +---> adaptee.specificRequest()

二、代码示例

场景说明

老播放系统只能播放 mp3,新需求要支持 mp4/vlc,但 mp4、vlc 接口并不一样。采用适配器统一成可识别接口 MediaPlayer。

1. 定义目标接口 & 业务实体

java 复制代码
//目标接口
public interface MediaPlayer {
    void play(String audioType, String fileName);
}

//高级播放器接口(需要被适配)
public interface AdvancedMediaPlayer {
    void playVlc(String fileName);
    void playMp4(String fileName);
}

2. 两个具体的 Adaptee

java 复制代码
public class VlcPlayer implements AdvancedMediaPlayer {
    public void playVlc(String fileName) { System.out.println("播放 vlc: " + fileName); }
    public void playMp4(String fileName) { /* 空实现 */ }
}

public class Mp4Player implements AdvancedMediaPlayer {
    public void playVlc(String fileName) { /* 空实现 */ }
    public void playMp4(String fileName) { System.out.println("播放 mp4: " + fileName); }
}

3. 对象适配器

java 复制代码
public class MediaAdapter implements MediaPlayer {
    private AdvancedMediaPlayer advancedPlayer;

    public MediaAdapter(String audioType) {
        switch (audioType.toLowerCase()) {
            case "vlc": advancedPlayer = new VlcPlayer(); break;
            case "mp4": advancedPlayer = new Mp4Player(); break;
        }
    }

    @Override
    public void play(String audioType, String fileName) {
        switch (audioType.toLowerCase()) {
            case "vlc": advancedPlayer.playVlc(fileName); break;
            case "mp4": advancedPlayer.playMp4(fileName); break;
            default: throw new IllegalArgumentException("格式不支持");
        }
    }
}

4. 客户端使用

java 复制代码
public class AudioPlayer implements MediaPlayer {
    private MediaAdapter mediaAdapter;

    @Override
    public void play(String audioType, String fileName) {
        if ("mp3".equalsIgnoreCase(audioType)) {
            // 本地支持的格式
            System.out.println("播放 mp3: " + fileName);
        } else if ("vlc".equalsIgnoreCase(audioType) || "mp4".equalsIgnoreCase(audioType)) {
            mediaAdapter = new MediaAdapter(audioType);
            mediaAdapter.play(audioType, fileName);
        } else {
            System.out.println("不支持 " + audioType + " 格式");
        }
    }

    // 运行示例
    public static void main(String[] args) {
        AudioPlayer player = new AudioPlayer();
        player.play("mp3", "beyond.mp3");
        player.play("mp4", "alone.mp4");
        player.play("vlc", "far far away.vlc");
        player.play("avi", "mind me.avi"); //输出:不支持 avi 格式
    }
}

输出

java 复制代码
播放 mp3: beyond.mp3
播放 mp4: alone.mp4
播放 vlc: far far away.vlc
不支持 avi 格式

三、JDK & 三方库经典用例

  1. java.util.Arrays#asList(T...):将数组对象适配成 List
  2. java.io.InputStreamReader / OutputStreamWriter:把字节流 Adaptee 适配成字符流 Target。
  3. javax.xml.bind.annotation.adapters:把 XML 日期与 java.util.Date 互相转换。
  4. Spring MVC HandlerAdapter:将不同类型的后端 Handler(普通 @Controller、Servlet、带 @ResponseBody 的方法等)适配成统一的 handle 调用。
  5. slf4j:所有桥接包(log4j-over-slf4j、jcl-over-slf4j)都是适配器,把旧日志系统接口伪装成 slf4j 接口。

四、优缺点

优势

  • 透明:客户端只依赖 Target,根本不知道 Adaptee。
  • 复用:现有类无需修改,符合"开闭原则"。
  • 灵活性:可以一次封装多个 Adaptee(见 Spring MVC)。

劣势

  • 类适配器只能适配一个具体类,且会暴露 Adaptee 的 public 方法(继承导致紧耦合)。
  • 过度使用会导致系统层次变多、不易理解。

五、何时使用

  1. 想复用某个现成类,但接口不符合需要。
  2. 希望创建一个可重用类,与若干不可预见接口的类一起工作。
  3. 需要转换多种已存在的子类接口,统一对外暴露接口时(如 Spring Adapter)。
  4. 需要桥接旧系统(遗留代码)与新系统。
相关推荐
张较瘦_1 小时前
[论文阅读] 软件工程 | 告别“线程安全玄学”:基于JMM的Java类静态分析,CodeQL3分钟扫遍GitHub千仓错误
java·论文阅读·安全
A尘埃3 小时前
智慧零售全渠道业务中台系统
java·零售
尘鹄3 小时前
go 初始化组件最佳实践
后端·设计模式·golang
小wanga6 小时前
C++知识
java·开发语言·c++
我是渣哥6 小时前
Java String vs StringBuilder vs StringBuffer:一个性能优化的探险故事
java·开发语言·jvm·后端·算法·职场和发展·性能优化
工一木子6 小时前
深入Java并发:锁机制原理剖析与性能优化实战
java·性能优化·并发·
你我约定有三6 小时前
java--写在 try 中的创建连接
java·开发语言
ERP老兵-冷溪虎山6 小时前
Python/JS/Go/Java同步学习(第三篇)四语言“切片“对照表: 财务“小南“纸切片术切凭证到崩溃(附源码/截图/参数表/避坑指南/老板沉默术)
java·javascript·python·golang·中医编程·四语言同步学习·职场生存指南
科技树支点6 小时前
无GC的Java创新设计思路:作用域引用式自动内存管理
java·python·go·web·编程语言·编译器