深入理解Java适配器模式,彻底搞懂设计思想

前言

在之前的一篇博客中,我们详细探讨了Java代理模式------这种通过中间代理类控制目标对象访问、并对核心方法进行增强的设计模式,在Spring AOP、权限控制等场景中发挥着至关重要的作用。代理模式的核心是"不改变目标接口,只做功能增强",让我们在不侵入原有代码的前提下,灵活扩展业务逻辑。

而今天,我们要聊的是另一种同样常用的结构型设计模式------适配器模式,它与代理模式有着相似的"中间类"结构,却承载着完全不同的核心使命:如果说代理模式是"给原有功能加buff",那么适配器模式就是"让原本不相容的功能能并肩作战"。

接下来,我们就详细展开对适配器模式的讲解

一、什么是适配器模式?

1.定义

适配器模式(Adapter Pattern)是结构型设计模式 的一种,核心作用是让两个原本不兼容的接口 / 类能够协同工作,就像生活中的电源适配器:国内电器是两脚插头,国外插座是三孔插孔,通过电源适配器就能让两者匹配使用,无需修改电器或插座本身。

2.本质

在 Java 开发中,适配器模式的本质是:通过一个中间适配器类,将目标类的接口转换成客户端期望的接口,解决接口不兼容问题,同时遵循开闭原则(对扩展开放,对修改关闭)

3.核心角色

适配器模式包含 4 个核心角色,缺一不可:

  • 目标接口(Target) :客户端期望使用的接口,是业务的标准规范(比如「两脚充电接口」)。
  • 适配者(Adaptee) :已存在的、功能可用但接口不兼容的类(比如「三孔电源插座」),是我们需要适配的对象。
  • 适配器(Adapter) :核心中间类,实现 Target 接口,内部持有 Adaptee 对象,负责将 Target 的调用转换为 Adaptee 的调用。
  • 客户端(Client):使用 Target 接口的业务方,只和目标接口交互,无需关心适配器和适配者。

4.设计思想

  • 不修改原有代码:适配者和目标接口的源码都不改动,符合开闭原则,避免修改旧代码引发 bug。
  • 解耦:客户端只依赖目标接口,和适配者完全解耦,扩展性极强。
  • 复用:让已有的、功能完善的旧类能在新业务中继续使用,避免重复开发。

二、适配器模式的两种实现方式

Java 中适配器模式有两种经典实现:类适配器(继承实现)对象适配器(组合实现)开发中优先使用对象适配器(符合组合优于继承原则)。

1. 类适配器(继承实现)

(1)经典类适配器---功能合并

结构拆解:

  • A 类(适配者 Adaptee) :已有类,提供了m1()方法,但没有实现 B 接口。
  • B 接口(目标接口 Target) :客户端期望的接口,定义了m1()m2()两个方法。
  • 适配器继承 A 类 + 实现 B 接口 ,把 A 类的m1()功能合并到 B 接口中,同时补充实现m2()方法,让客户端可以通过 B 接口调用 A 类的功能。

核心作用:

  • 接口兼容转换:把已有类(A)的功能,适配成客户端期望的接口(B),让两者可以协同工作。
  • 功能合并复用 :通过继承 A 类复用m1(),通过实现 B 接口满足客户端的调用规范,同时补充m2()方法。
  • 核心目标是解决接口不兼容问题,让原本无法一起工作的类和接口适配起来。

代码示例:

java 复制代码
// 1. 适配者A类:已有功能,但接口不兼容
class A {
    public void m1() {
        System.out.println("A类的m1()方法:已有的功能");
    }
}

// 2. 目标接口B:客户端期望的标准接口
interface B {
    void m1();
    void m2();
}

// 3. 类适配器:继承A类 + 实现B接口,完成适配
class Adapter extends A implements B {
    // 继承A类,自动拥有了m1()方法,直接复用
    @Override
    public void m2() {
        // 补充实现目标接口的m2()方法
        System.out.println("适配器补充实现的m2()方法");
    }
}

// 客户端:只依赖目标接口B
public class Client {
    public static void main(String[] args) {
        B target = new Adapter();
        target.m1(); // 调用的是A类的m1()方法
        target.m2(); // 调用的是适配器实现的m2()方法
    }
}

(2)默认适配器---只想实现接口的指定类

结构拆解

  • 目标接口 :定义了m1()~m10()共 10 个方法,是一个多方法的通用接口。
  • 适配器 :实现了这个接口,把m1()~m10()所有方法都做了空实现 (比如public void m1() {})。
  • 业务类 :继承适配器,只重写自己关心的m1()方法,其他 9 个方法直接继承适配器的空实现,不用自己写。

核心作用

  • 简化接口实现:避免了 "实现接口必须重写所有方法" 的冗余代码。
  • 适配的是「接口和实现者」之间的关系,让实现者只关注自己需要的方法。

代码示例

java 复制代码
// 1. 接口:方法很多,业务类只需要用其中一个
interface Target {
    void m1();
    void m2();
    // ... 省略中间方法
    void m10();
}

// 2. 适配器:实现接口,所有方法空实现
abstract class Adapter implements Target {
    @Override public void m1() {}
    @Override public void m2() {}
    // ... 其他方法也都空实现
    @Override public void m10() {}
}

// 3. 业务类:继承适配器,只重写需要的方法
class MyClass extends Adapter {
    @Override
    public void m1() {
        // 只在这里写业务逻辑,其他方法不用管
        System.out.println("我只关心m1()方法");
    }
}

2.对象适配器(组合实现,推荐)

在类适配器里面的第一种经典类适配器:A类和B接口里面是有一个相同方法名的,但如果要合并的是方法名不同,但是实现的功能相同我们要怎么做?

那就可以使用对象适配器:

结构拆解:

  • 适配者(Adaptee): A类,含+m1()已有类,提供了可用的功能,但接口不兼容
  • 目标接口(Target): B接口,含+mm1()+mm2()客户端期望的标准接口
  • 适配器(Adapter): 中间的适配器类核心中间层,实现目标接口 B,同时持有适配者 A 的对象

核心功能:

  • 基于组合,而不是继承适配者:通过new对象来持有适配者对象
  • 解决了Java单继承限制:类适配器受到Java单继承的限制,无法继承其他类,而对象适配器没有这个问题,可以同时持有多个适配器对象,适配多个不同的类

3.对比

维度 对象适配器 类适配器
实现方式 实现目标接口 + 持有适配者对象(组合) 继承适配者 + 实现目标接口(继承)
Java 单继承限制 无限制,可同时适配多个类 受限制,只能继承一个适配者
耦合度 低,适配器与适配者松耦合 高,适配器与适配者强耦合
扩展性 强,可通过构造方法传入不同适配者 弱,适配者固定为继承的类
推荐程度 ✅ 实际开发首选 ❌ 仅在特殊场景使用

三、适配器模式 VS 代理模式

适配器模式和代理模式都属于结构型模式 ,且都有「中间类」,很多人容易混淆,这里讲一下核心区别:目的不同、作用不同、关注点不同

1. 核心目的

  • 适配器模式解决「接口不兼容」问题 ,让两个无法协同的类能一起工作,不新增功能,只做转换

  • 代理模式解决「对象访问控制」问题 ,不改变目标接口,对目标对象的方法进行增强 / 控制(如日志、事务、权限)。

2. 核心关系

  • 适配器模式 :适配器 → 适配者(转换关系),目标接口和适配者接口不兼容。

  • 代理模式 :代理类 → 目标对象(代理关系 ),代理类和目标对象实现同一个接口,接口完全一致。

3. 功能侧重点

  • 适配器模式接口转换,不修改、不增强原有功能,只做兼容。

  • 代理模式功能增强 / 访问控制,在目标方法执行前后添加额外逻辑,接口不变。

4. 代码层面区别

  • 适配器:实现目标接口,内部调用适配者的不同方法(接口不兼容)。

  • 代理:实现目标接口,内部调用目标对象的相同方法(接口一致)。

相关推荐
Mr_pyx1 小时前
【LeetHOT100】二叉树的中序遍历——Java多解法详解
java·开发语言·深度优先
jay神1 小时前
基于SpringBoot的宠物生命周期信息管理系统
java·数据库·spring boot·后端·web开发·宠物·管理系统
万亿少女的梦1682 小时前
基于SpringBoot的在线考试管理系统设计与实现
java·spring boot·后端
m0_738120722 小时前
渗透测试——Djinn1靶场详细渗透提权过程讲解(绕过黑名单限制,命令执行反弹shell,pyc反编译,代码白盒分析,python沙盒逃逸)
开发语言·python·php
一勺菠萝丶2 小时前
如何在 Linux 服务器上使用 Speedtest 官方 CLI 测试带宽(小白教程)
java·服务器·前端
web守墓人2 小时前
【go语言】go语言实现go-torch, 完成Lenet-5的搭建,训练,以及pth和onnx模型导出
开发语言·后端·golang
TEC_INO2 小时前
Linux50:ROCKX+RV1126视频流检测人脸
开发语言·前端·javascript
平凡但不平庸的码农2 小时前
Go 语言常用标准库详解
开发语言·后端·golang
下载居2 小时前
Node.js(Javascript运行环境) 26.1
开发语言·javascript·node.js