设计模式-适配器模式

适配器模式

1. 什么是适配器模式?

想象一下,你有一个欧标的电器插头(比如两孔圆形),但你家的插座是美标的(比如两孔扁平或三孔)。你不能直接把欧标插头插到美标插座里。这时候你需要一个"转换插头"或"适配器",这个转换插头一端可以接欧标插头,另一端可以插到美标插座上。

适配器模式 就是这样一种设计模式,它的核心思想是:将一个类的接口转换成客户端所期望的另一种接口。 使得原本由于接口不兼容而不能一起工作的类可以协同工作。

它充当两个不兼容接口之间的桥梁。

2. 适配器模式的结构 (主要角色):

  • Target (目标接口): 这是客户端代码期望使用的接口。客户端通过这个接口与适配后的对象进行交互。

  • Adaptee (源接口 / 被适配者): 这是已存在的、但其接口与 Target 接口不兼容的类。它是需要被适配的类。

  • Adapter (适配器): 这是核心角色。它实现了 Target 接口,并且内部持有一个 Adaptee 对象的引用(或者继承 Adaptee 类)。它的任务是将对 Target 接口的调用转换成对 Adaptee 接口的调用。

  • Client (客户端): 使用 Target 接口与适配器进行交互的类。客户端并不知道也不关心它实际使用的是哪个 Adaptee。

3. 适配器模式的两种主要实现方式:

a) 类适配器模式 (Class Adapter Pattern):

  • 实现方式: Adapter 类通过多重继承(在 Java 中通过继承 Adaptee 类并实现 Target 接口)来实现。

  • 结构图示:

    复制代码
    +----------------+       +----------------+
    |    Client      |------>|     Target     | (Interface or Abstract Class)
    +----------------+       +----------------+
                                      ^
                                      | (implements/extends)
                               +----------------+
                               |    Adapter     | (Class)
                               +----------------+
                                      ^
                                      | (extends)
                               +----------------+
                               |    Adaptee     | (Class)
                               +----------------+
  • 特点:

    • 适配器直接继承了 Adaptee 类,因此可以重写 Adaptee 的部分方法

    • 由于 Java 不支持多重类继承,如果 Adaptee 是一个具体的类(而不是接口),并且 Target 也是一个具体的类(通常 Target 是接口),这种方式在 Java 中可以实现(Adapter 继承 Adaptee,实现 Target 接口)。

    • 缺点: 耦合度较高。Adapter 必须是 Adaptee 的子类,这限制了其灵活性。如果 Adaptee 是 final 类,则无法使用类适配器。

b) 对象适配器模式 (Object Adapter Pattern):

  • 实现方式: Adapter 类实现 Target 接口,并且在其内部持有一个 Adaptee 对象的实例引用。所有对 Target 接口的调用都会被委派给这个 Adaptee 实例。

  • 结构图示:

    复制代码
    +----------------+       +----------------+
    |    Client      |------>|     Target     | (Interface or Abstract Class)
    +----------------+       +----------------+
                                      ^
                                      | (implements/extends)
                               +----------------+
                               |    Adapter     | (Class)
                               +----------------+
                                      |
                                      | (has-a / composition)
                                      V
                               +----------------+
                               |    Adaptee     | (Class or Interface)
                               +----------------+
  • 特点:

    • 更灵活: Adapter 和 Adaptee 之间的关系是组合/聚合关系,耦合度相对较低。

    • Adapter 可以适配 Adaptee 类及其所有子类。

    • 可以在运行时动态地改变被适配的对象。

    • 是更常用和推荐的方式。

4. 适配器模式的优缺点:

优点:

  • 提高类的复用性: 可以让现有的、接口不兼容的类在新的环境中使用,而无需修改其源代码。

  • 增加类的透明度(对于客户端): 客户端代码只需要与 Target 接口交互,无需关心具体的 Adaptee 实现。

  • 更好的灵活性和扩展性(特别是对象适配器): 可以很容易地替换或增加新的适配器来适配不同的 Adaptee。

  • 解耦: 将客户端与具体的 Adaptee 实现解耦。

缺点:

  • 过多的使用适配器会使系统变得零碎和复杂: 每引入一个适配器都会增加一个类。

  • 类适配器模式的限制:

    • 只能适配一个具体的 Adaptee 类。

    • 在支持单继承的语言(如 Java)中,如果 Adaptee 本身就是 Target 的一个不兼容的子类,可能会比较棘手。

  • 可能增加代码的间接性: 调用需要通过适配器进行转发,可能会有轻微的性能影响(通常可忽略不计)。

5. 适配器模式的应用场景:

  • 系统需要使用现有的类,而这些类的接口不符合系统的需要时。

  • 想要创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作。 (例如,一个通用的数据转换工具)

  • (对象适配器)需要适配一个类的多个子类时,或者需要在运行时动态选择适配的 Adaptee 时。

  • 在不同模块或系统之间进行集成,而它们定义的接口不一致时。

Java 中的例子:

  • java.io.InputStreamReader 和 java.io.OutputStreamWriter:

    • InputStreamReader 是一个适配器,它将字节输入流 (InputStream - Adaptee) 转换为字符输入流 (Reader - Target)。

    • OutputStreamWriter 是一个适配器,它将字符输出流 (Writer - Target) 转换为字节输出流 (OutputStream - Adaptee)。 它们解决了字节操作和字符操作之间的不兼容问题,并处理了字符编码。

  • java.util.Arrays.asList():

    • 这个方法可以将一个数组 (T[] - Adaptee) 适配成一个 List<T> (List - Target) 接口。

    • 返回的 List 是一个固定大小的列表,它内部仍然依赖于原始数组。

  • SLF4J (Simple Logging Facade for Java) 和其他日志框架 (Logback, Log4j) 的桥接包:

    • 例如 log4j-over-slf4j 或 jul-to-slf4j。这些桥接包充当适配器,将对旧日志 API (如 Log4j 1.x 或 java.util.logging) 的调用,适配到 SLF4J 接口,进而路由到 SLF4J 底层绑定的具体日志实现。

代码示例 (对象适配器模式):

假设我们有一个旧的音频播放器 OldAudioPlayer,它只能播放 .mp3 文件。我们希望我们的新播放器 AudioPlayer (Target) 能够播放 .mp4 和 .vlc 文件,同时也能利用 OldAudioPlayer 来播放 .mp3。

复制代码
// Adaptee (被适配者)
class OldAudioPlayer {
    public void playMp3(String fileName) {
        System.out.println("Playing mp3 file: " + fileName);
    }
}
​
// Target (目标接口)
interface MediaPlayer {
    void play(String audioType, String fileName);
}
​
// Adapter (适配器)
class MediaAdapter implements MediaPlayer {
    OldAudioPlayer oldAudioPlayer; // 持有 Adaptee 的引用
​
    public MediaAdapter(String audioType) {
        if (audioType.equalsIgnoreCase("mp3")) {
            oldAudioPlayer = new OldAudioPlayer();
        }
        // 如果需要适配其他类型,可以在这里实例化其他 Adaptee
    }
​
    @Override
    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("mp3")) {
            oldAudioPlayer.playMp3(fileName);
        }
        // 对于其他类型,这个适配器目前不处理,或者可以扩展
        // else if (audioType.equalsIgnoreCase("vlc")) { ... }
        // else if (audioType.equalsIgnoreCase("mp4")) { ... }
    }
}
​
// Client (客户端期望使用的播放器)
class AudioPlayer implements MediaPlayer {
    MediaAdapter mediaAdapter; // 客户端可以使用适配器
​
    @Override
    public void play(String audioType, String fileName) {
        // 内建支持播放 mp4 (假设)
        if (audioType.equalsIgnoreCase("mp4")) {
            System.out.println("Playing mp4 file: " + fileName);
        }
        // 使用适配器播放其他格式
        else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp3")) {
            // 这里为了简单,直接 new。实际项目中可能通过工厂或依赖注入获取适配器实例
            if (audioType.equalsIgnoreCase("mp3")) {
                mediaAdapter = new MediaAdapter("mp3"); // 创建针对 mp3 的适配器
                mediaAdapter.play(audioType, fileName);
            } else if (audioType.equalsIgnoreCase("vlc")) {
                // 假设我们有另一个 AdvancedMediaPlayer 接口和其实现
                // AdvancedMediaPlayer advancedMediaPlayer = new VlcPlayer();
                // mediaAdapter = new AdvancedMediaAdapter(advancedMediaPlayer); // 创建另一个适配器
                // mediaAdapter.play(audioType, fileName);
                System.out.println("Playing vlc file using a different adapter (concept): " + fileName);
            }
        } else {
            System.out.println("Invalid media. " + audioType + " format not supported");
        }
    }
}
​
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");
    }
}

总结:

适配器模式是一种非常有用的结构型设计模式,它专注于解决接口不兼容的问题,使得原本无法协同工作的组件能够一起工作。在选择类适配器还是对象适配器时,通常对象适配器因其更高的灵活性而被优先考虑。记住那个"转换插头"的例子,就能很好地理解适配器模式的核心思想了。

相关推荐
bxlj_jcj2 小时前
深入剖析Debezium:CDC领域的“数据魔法棒”
java·架构
叶 落2 小时前
ubuntu 安装 JDK8
java·ubuntu·jdk·安装·java8
爱学习的白杨树2 小时前
Sentinel介绍
java·开发语言
XW2 小时前
java mcp client调用 (modelcontextprotocol)
java·llm
哆啦A梦的口袋呀3 小时前
基于Python学习《Head First设计模式》第十章 状态模式
学习·设计模式
保持学习ing3 小时前
SpringBoot前后台交互 -- 登录功能实现(拦截器+异常捕获器)
java·spring boot·后端·ssm·交互·拦截器·异常捕获器
gadiaola4 小时前
【JVM面试篇】高频八股汇总——类加载和类加载器
java·jvm·面试
七七&5564 小时前
【Java开发日记】基于 Spring Cloud 的微服务架构分析
java·spring cloud·架构
小猫咪怎么会有坏心思呢4 小时前
华为OD机考-数字游戏-逻辑分析(JAVA 2025B卷)
java·游戏·华为od
Aesopcmc4 小时前
idea 启动jar程序并调试
java·intellij-idea·jar