每日一学:设计模式之适配器模式

什么是适配器模式?

适配器模式是结构型设计模式之一,它的核心思想非常简单 ------引入一个 "中间转换层",将一个接口转换成客户端期望的另一个接口

在 Java 开发中,我们经常会遇到这样的场景:现有代码模块的接口与新需求的接口不兼容,无法直接复用;或者对接第三方组件时,其暴露的方法与我们系统的接口规范不一致。此时,强行修改原有代码会破坏封装性、增加耦合度,甚至引发未知 bug。而适配器模式,正是解决这类 "接口不兼容" 问题的最优方案之一。

在Java中的典型使用

字节转换流:InputStreamReader

一、适配器模式的核心定义与角色

官方定义:将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作,其别名为包装器(Wrapper)。

适配器模式的核心是 转换,这种转换依赖于四个核心角色,缺一不可,我们结合生活中的电源适配器案例,就能快速理解:

1. 目标接口(Target)

客户端期望使用的接口,是适配器最终要提供的接口规范。比如手机充电需要的 5V 直流充电接口,就是目标接口 ------ 客户端(手机)只认这个接口,不符合规范就无法工作。在 Java 中,目标接口通常是一个抽象类或接口,定义了客户端需要的方法。

2. 被适配者(Adaptee)

现有存在的、接口不兼容的类或模块,是我们需要适配的对象。比如墙上的220V 交流电源,它只能提供 220V 交流电,无法直接满足手机的充电需求,就是被适配者。被适配者本身功能完整,只是接口不符合客户端的期望。

3. 适配器(Adapter)

核心转换类,也是适配器模式的核心。它既要实现目标接口,又要持有被适配者的引用,通过自身的逻辑,将被适配者的接口转换成目标接口的规范。比如手机充电头,它一端对接 220V 交流电源(被适配者),另一端提供 5V 直流接口(目标接口),中间完成电压转换,就是适配器。

4. 客户端(Client)

使用目标接口的对象,它只关注目标接口的方法,不关心被适配者和适配器的内部逻辑。比如手机,它只需要通过 5V 接口充电,不需要知道充电头如何将 220V 转换成 5V。

四个角色的关系可以总结为:客户端调用适配器的目标接口方法 → 适配器调用被适配者的方法 → 适配器将结果转换为目标接口格式 → 客户端获得符合预期的结果

二、适配器模式的两种实现方式(Java 实战)

在 Java 中,适配器模式有两种主流实现方式,核心区别在于适配器与被适配者的关联方式 ------ 继承 vs 组合 。其中,组合方式因低耦合、高灵活度,成为实际开发中的首选

方式一:对象适配器(推荐)

对象适配器通过 组合 的方式持有被适配者的引用,适配器实现目标接口,在目标接口的方法中调用被适配者的方法,并完成转换。 这种方式符合合成复用原则,耦合度低,且能避免 Java 单继承的限制。

实战场景:字节流转字符流的简化实现

我们模拟 JDK 中InputStreamReader的核心逻辑,实现一个简单的字节流到字符流的适配器,直观感受对象适配器的工作流程。

1、定义目标接口(Target):字符流接口 Reader

java 复制代码
// 目标接口:客户端期望的字符流接口
public interface Reader {
    // 读取字符到字符数组中,返回读取的字符数
    int read(char[] cbuf);
}

2、定义被适配者(Adaptee):字节流类 InputStream

java 复制代码
// 被适配者:现有的字节流,接口不兼容(只能读字节)
public class InputStream {
    // 读取字节到字节数组中,返回读取的字节数
    public int read(byte[] b) {
        // 模拟读取数据:假设读取到"Java适配器模式"的字节
        String data = "Java适配器模式";
        byte[] bytes = data.getBytes();
        // 将模拟数据复制到传入的字节数组
        System.arraycopy(bytes, 0, b, 0, bytes.length);
        return bytes.length;
    }
}

3、实现适配器(Adapter):InputStreamReader

java 复制代码
// 适配器:将字节流适配成字符流(实现目标接口,持有被适配者引用)
public class InputStreamReader implements Reader {
    // 组合:持有被适配者(字节流)的引用
    private final InputStream inputStream;

    // 构造方法:传入被适配者对象
    public InputStreamReader(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    // 实现目标接口方法:完成字节→字符的转换
    @Override
    public int read(char[] cbuf) {
        // 1. 调用被适配者的方法,读取字节
        byte[] bytes = new byte[cbuf.length];
        int readLen = inputStream.read(bytes);

        // 2. 核心转换:将字节数组转换成字符数组
        if (readLen > 0) {
            String str = new String(bytes, 0, readLen);
            str.getChars(0, readLen, cbuf, 0);
        }

        return readLen;
    }
}

3、客户端调用(Client)

java 复制代码
// 客户端:只使用目标接口(Reader),不关心适配逻辑
public class Client {
    public static void main(String[] args) {
        // 1. 实例化被适配者(字节流)
        InputStream inputStream = new InputStream();
        // 2. 实例化适配器,包装字节流
        Reader reader = new InputStreamReader(inputStream);
        // 3. 客户端使用字符流接口,完成读取
        char[] buffer = new char[20];
        int len = reader.read(buffer);
        System.out.println("读取到的字符:" + new String(buffer, 0, len));
    }
}

运行结果

复制代码
读取到的字符:Java适配器模式

从代码可以看出,客户端只与Reader接口交互,完全不知道InputStream的存在,适配器完美隐藏了转换逻辑,实现了解耦。

方式二:类适配器

类适配器通过 继承 的方式,让适配器继承被适配者,同时实现目标接口。这种方式耦合度高,因为 Java 是单继承语言,适配器继承被适配者后,就无法再继承其他类,灵活性极差,仅适用于简单场景。

类适配器实现

java 复制代码
// 类适配器:继承被适配者,实现目标接口
public class ClassAdapter extends InputStream implements Reader {
    @Override
    public int read(char[] cbuf) {
        // 继承被适配者的read方法,读取字节
        byte[] bytes = new byte[cbuf.length];
        int readLen = super.read(bytes);
        // 字节→字符转换
        if (readLen > 0) {
            String str = new String(bytes, 0, readLen);
            str.getChars(0, readLen, cbuf, 0);
        }
        return readLen;
    }
}

类适配器的缺点很明显:单继承限制导致扩展性差,适配器与被适配者耦合过紧,一旦被适配者修改,适配器也需要同步修改,不符合 "开闭原则"。因此,实际开发中几乎不使用类适配器。

三、JDK 中的适配器模式实战:InputStreamReader 深度解析

前面我们模拟了InputStreamReader的简化实现,而 JDK 中真正的InputStreamReader,正是适配器模式的经典应用,其底层逻辑与我们手写的版本完全一致,只是增加了编码处理、缓冲区优化等细节。

1. JDK 中角色对应

  • 目标接口(Target)Reader(抽象类,定义字符流的核心方法)
  • 被适配者(Adaptee)InputStream(抽象类,定义字节流的核心方法)
  • 适配器(Adapter)InputStreamReader(继承 Reader,持有 InputStream 引用)

2. 源码核心逻辑(简化)

java 复制代码
public class InputStreamReader extends Reader {
    // 持有被适配者:字节流
    private final InputStream in;
    // 字节→字符解码引擎(JDK优化,处理编码问题)
    private final StreamDecoder sd;

    // 构造方法传入被适配者
    public InputStreamReader(InputStream in) {
        super();
        this.in = in;
        this.sd = StreamDecoder.forInputStreamReader(in, this, Charset.defaultCharset());
    }

    // 实现Reader的read方法,底层调用解码引擎转换
    @Override
    public int read(char[] cbuf) throws IOException {
        return sd.read(cbuf);
    }
}

可以看到,JDK 的实现依然遵循对象适配器 的核心逻辑:持有被适配者(InputStream),实现目标接口(Reader),通过StreamDecoder完成字节到字符的转换(解决编码问题,比如 UTF-8、GBK 等)。这也印证了对象适配器的实用性和灵活性。

除了InputStreamReader,JDK 中还有很多适配器模式的应用,比如:

  • OutputStreamWriter:将字节流适配成字符流(与InputStreamReader对称)
  • Arrays.asList():将数组适配成 List 集合
  • Spring MVC 的HandlerAdapter:适配不同类型的 Controller,统一请求处理接口

四、适配器模式的优缺点与适用场景

任何设计模式都有其适用范围,适配器模式也不例外,我们需要明确其优缺点,才能在合适的场景中使用。

核心优点

  1. 解耦:将客户端与被适配者分离,客户端无需修改代码,就能使用不兼容的模块,符合 "开闭原则"。
  2. 复用性:复用现有不兼容的类,无需重写代码,降低开发成本。
  3. 扩展性:新增适配逻辑时,只需新增适配器类,不改动原有代码,灵活性高。
  4. 单一职责:适配逻辑集中在适配器类中,让被适配者和目标接口各司其职,符合 "单一职责原则"。

潜在缺点

  1. 增加代码复杂度:引入适配器会增加一个中间层,可能导致代码层级变多,可读性下降。
  2. 适配过度:如果接口差异过大,适配器的转换逻辑会非常复杂,反而增加维护成本。

适用场景

  1. 现有类的接口与客户端需求不兼容,且无法修改现有类(比如第三方组件、老系统代码)。
  2. 需要复用多个不相关的类,让它们统一适配到一个目标接口,供客户端统一调用。
  3. 系统升级或重构时,需要兼容老系统的接口,避免大量修改客户端代码。

五、适配器模式的核心总结

适配器模式的本质是 转换与兼容,它不是创造新功能,而是通过中间层,让原本不兼容的代码协同工作。我们可以用三句话快速记住它:

  1. 核心作用:解决接口不兼容问题,实现解耦与复用。
  2. 实现首选:对象适配器(组合),避免类适配器(继承)的单继承限制和高耦合。
  3. 经典案例 :JDK 的InputStreamReader,完美诠释了适配器模式的核心逻辑。
相关推荐
程序员老邢2 小时前
【技术底稿 18】FTP 文件处理 + LibreOffice Word 转 PDF 在线预览 + 集群乱码终极排查全记录
java·经验分享·后端·pdf·word·springboot
磊 子2 小时前
类模板与派生1
java·开发语言·c++
:1212 小时前
java面试基础2
java·开发语言·面试
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【30】Nacos Skill Registry 的底层设计与实现
java·人工智能·spring
北辰屿风2 小时前
宝塔部署tomcat项目,nginx负载均衡代理访问报错404问题
java·tomcat
鱼鳞_2 小时前
Java学习笔记_Day37(网络编程)
java·网络·笔记·学习
Metaphor6922 小时前
使用 Python 合并 PDF 文件
java·python·pdf
我是无敌小恐龙3 小时前
Java SE 零基础入门Day03 数组核心详解(定义+内存+遍历+算法+实战案例)
java·开发语言·数据结构·人工智能·算法·aigc·动态规划