前言:适配器模式是java结构型设计模式中最常用的模式之一,核心作用是“解决接口不兼容问题”——就像生活中的电源适配器,能让不同插头的设备正常通电。在后端开发中,我们经常遇到“旧系统接口无法修改、第三方sdk接口不匹配、不同模块接口规范不一致”的场景,此时适配器模式就能完美解决,无需修改原有代码,实现接口的无缝对接。很多开发者只会用但说不清楚原理,面试时被问起三种实现方式的区别就卡壳,本文从入门到实战,结合真实业务场景+可运行代码+面试高频考点,带你吃透适配器模式,看完直接能用、能说、能面试,新手也能快速上手。
一、为什么需要适配器模式?(痛点直击)
先看一个java后端开发中最常见的真实场景:假设你维护一个旧电商系统,其中订单模块有一个旧的订单查询接口(返回xml格式),现在新开发的前端页面只支持json格式的接口,且旧接口代码不能修改(可能被其他系统依赖),你该如何实现对接?
如果没有适配器模式,你可能会这么做:
- 直接修改旧接口代码,让其返回json格式——违反“开闭原则”,且可能影响其他依赖该接口的系统;
- 在前端代码中做xml转json的处理——增加前端复杂度,不符合“后端负责数据格式转换”的开发规范;
- 在新接口中重复编写旧接口的业务逻辑,再返回json——代码冗余,后期维护成本极高。
而适配器模式的核心价值,就是在不修改原有接口和调用方代码的前提下,通过一个“适配器”类,实现两个不兼容接口的对接,既保证原有系统稳定,又满足新需求,同时遵循开闭原则和单一职责原则。
核心结论:当系统中存在“接口不兼容、无法直接修改原有代码、需要对接第三方接口”的场景时,优先使用适配器模式。
二、适配器模式核心概念(极简理解)
适配器模式的核心思想:“定义一个适配器类,实现目标接口,同时持有适配者(旧接口/第三方接口)的引用,通过适配器将适配者的接口转换为目标接口,让调用方可以通过目标接口直接调用适配者的功能”。
简单说,适配器就是“中间转换器”,一边对接旧接口(适配者),一边对接新接口(目标),屏蔽接口差异,让调用方无需关心适配细节。
适配器模式有3个核心角色(必须掌握,面试常考,结合实战理解,无需死记硬背):
- target(目标接口):调用方期望使用的接口,定义了调用方需要的功能规范(如上述场景中“返回json格式的订单查询接口”);
- adaptee(适配者):需要被适配的原有接口/第三方接口,功能已经存在,但接口规范与目标接口不兼容(如上述场景中“返回xml格式的旧订单查询接口”);
- adapter(适配器):核心类,实现目标接口,同时持有适配者的引用,将适配者的接口转换为目标接口,实现两者的兼容(如“xml转json的订单查询适配器”)。
重点补充:java适配器模式有三种常见实现方式,分别是类适配器、对象适配器、接口适配器,其中对象适配器是企业开发中最常用的(耦合度最低),类适配器因继承关系耦合度高,接口适配器适用于接口方法过多的场景,下文会逐一实战讲解。
三、适配器模式三种实现方式(手写实战,必掌握)
以“旧订单接口(xml)适配新订单接口(json)”为统一场景,分别实现三种适配器,对比差异,掌握各自的适用场景。
场景准备:定义目标接口(target)和适配者(adaptee)
先定义两个不兼容的接口,为后续适配器实现做准备,代码可直接复制运行。
/**
* 1. 目标接口(target):新系统期望的接口(返回json格式)
* 调用方(前端)只关注这个接口,无需关心适配逻辑
*/
public interface orderjsonservice {
// 目标方法:查询订单,返回json字符串
string queryorderjson(string orderno);
}
/**
* 2. 适配者(adaptee):需要被适配的旧接口(返回xml格式)
* 假设这个类是旧系统代码,不能修改
*/
public class orderxmlservice {
// 适配者方法:查询订单,返回xml字符串
public string queryorderxml(string orderno) {
// 模拟旧系统业务逻辑,返回xml格式数据
return "<order><orderno>" + orderno + "</orderno><amount>199.9</amount></order>";
}
}实现一:类适配器(继承适配者,实现目标接口)
核心:适配器类继承适配者(adaptee),同时实现目标接口(target),通过重写目标方法,调用适配者的方法,完成接口转换。
/**
* 类适配器:继承适配者(orderxmlservice),实现目标接口(orderjsonservice)
* 缺点:因java单继承特性,适配器无法继承其他类,耦合度较高,实际开发中较少使用
*/
public class orderclassadapter extends orderxmlservice implements orderjsonservice {
@override
public string queryorderjson(string orderno) {
// 1. 调用适配者(旧接口)的方法,获取xml数据
string xmldata = super.queryorderxml(orderno);
// 2. 核心:将xml格式转换为json格式(模拟转换逻辑,实际开发中可用fastjson等工具)
string jsondata = xmltojson(xmldata);
// 3. 返回json数据,满足目标接口要求
return jsondata;
}
// 模拟xml转json的工具方法(实际开发中可使用第三方工具类)
private string xmltojson(string xml) {
// 简化逻辑,实际需解析xml节点
string orderno = xml.substring(xml.indexof("<orderno>") + 9, xml.indexof("</orderno>"));
string amount = xml.substring(xml.indexof("<amount>") + 8, xml.indexof("</amount>"));
return "{\"orderno\":\"" + orderno + "\",\"amount\":" + amount + "}";
}
}类适配器测试代码:
/**
* 类适配器测试类
*/
public class classadaptertest {
public static void main(string[] args) {
// 调用方直接使用目标接口,无需关心适配细节
orderjsonservice orderservice = new orderclassadapter();
// 调用目标方法,获取json格式数据
string json = orderservice.queryorderjson("order20260415001");
system.out.println("类适配器返回json:" + json);
}
}运行结果:
类适配器返回json:{"orderno":"order20260415001","amount":199.9}
类适配器核心要点(避坑):
- 优点:实现简单,直接继承适配者,无需额外持有适配者引用;
- 缺点:受java单继承限制,适配器无法继承其他类,耦合度高,不适用于适配者是接口的场景;
- 适用场景:适配者是具体类,且不需要继承其他类的简单场景。
实现二:对象适配器(持有适配者引用,实现目标接口)
核心:适配器类不继承适配者,而是持有适配者的引用(通过构造方法注入),同时实现目标接口,通过调用适配者的方法完成接口转换。这是企业开发中最常用的方式,耦合度低、扩展性强。
/**
* 对象适配器:持有适配者引用,实现目标接口(推荐使用)
* 优点:不依赖继承,耦合度低,可适配多个适配者,扩展性强
*/
public class orderobjectadapter implements orderjsonservice {
// 持有适配者引用(通过构造方法注入,便于扩展和测试)
private orderxmlservice orderxmlservice;
// 构造方法:注入适配者实例
public orderobjectadapter(orderxmlservice orderxmlservice) {
this.orderxmlservice = orderxmlservice;
}
@override
public string queryorderjson(string orderno) {
// 1. 调用适配者(旧接口)的方法,获取xml数据
string xmldata = orderxmlservice.queryorderxml(orderno);
// 2. xml转json(复用工具方法)
string jsondata = xmltojson(xmldata);
// 3. 返回json数据
return jsondata;
}
// 复用xml转json工具方法
private string xmltojson(string xml) {
string orderno = xml.substring(xml.indexof("<orderno>") + 9, xml.indexof("</orderno>"));
string amount = xml.substring(xml.indexof("<amount>") + 8, xml.indexof("</amount>"));
return "{\"orderno\":\"" + orderno + "\",\"amount\":" + amount + "}";
}
}对象适配器测试代码:
/**
* 对象适配器测试类(企业开发常用)
*/
public class objectadaptertest {
public static void main(string[] args) {
// 1. 创建适配者实例(旧接口)
orderxmlservice xmlservice = new orderxmlservice();
// 2. 创建适配器,注入适配者
orderjsonservice orderservice = new orderobjectadapter(xmlservice);
// 3. 调用目标方法,获取json数据
string json = orderservice.queryorderjson("order20260415002");
system.out.println("对象适配器返回json:" + json);
}
}运行结果:
对象适配器返回json:{"orderno":"order20260415002","amount":199.9}
对象适配器核心要点(重点掌握):
- 优点:不依赖继承,规避java单继承限制;通过构造方法注入适配者,耦合度低,可灵活替换适配者,扩展性强;
- 缺点:需要额外持有适配者引用,代码量略多于类适配器;
- 适用场景:企业开发主流场景,尤其是适配者可能变化、需要适配多个适配者的情况。
实现三:接口适配器(抽象类实现目标接口,空实现所有方法)
核心:当目标接口中方法过多,而调用方只需要使用其中部分方法时,定义一个抽象适配器类,实现目标接口的所有方法(空实现),调用方只需继承抽象适配器,重写自己需要的方法即可,避免实现不必要的方法。
/**
* 目标接口(方法较多,调用方只需要部分方法)
* 模拟一个复杂的订单接口,包含多个方法
*/
public interface orderservice {
// 方法1:查询订单(json格式)
string queryorderjson(string orderno);
// 方法2:查询订单(xml格式,备用)
string queryorderxml(string orderno);
// 方法3:取消订单
boolean cancelorder(string orderno);
// 方法4:修改订单金额
boolean updateorderamount(string orderno, double amount);
}
/**
* 接口适配器:抽象类,实现目标接口,空实现所有方法
* 作用:避免调用方实现不必要的方法,简化开发
*/
public abstract class orderinterfaceadapter implements orderservice {
// 空实现所有方法,调用方按需重写
@override
public string queryorderjson(string orderno) {
return null;
}
@override
public string queryorderxml(string orderno) {
return null;
}
@override
public boolean cancelorder(string orderno) {
return false;
}
@override
public boolean updateorderamount(string orderno, double amount) {
return false;
}
}
/**
* 调用方:只需要使用“查询订单json”和“取消订单”方法
* 继承接口适配器,只重写需要的方法,无需实现所有方法
*/
public class orderclientadapter extends orderinterfaceadapter {
// 持有适配者引用(旧接口)
private orderxmlservice orderxmlservice;
public orderclientadapter(orderxmlservice orderxmlservice) {
this.orderxmlservice = orderxmlservice;
}
// 重写需要的方法1:查询订单json
@override
public string queryorderjson(string orderno) {
string xmldata = orderxmlservice.queryorderxml(orderno);
return xmltojson(xmldata);
}
// 重写需要的方法2:取消订单
@override
public boolean cancelorder(string orderno) {
// 模拟取消订单逻辑
system.out.println("取消订单:" + orderno);
return true;
}
// xml转json工具方法
private string xmltojson(string xml) {
string orderno = xml.substring(xml.indexof("<orderno>") + 9, xml.indexof("</orderno>"));
string amount = xml.substring(xml.indexof("<amount>") + 8, xml.indexof("</amount>"));
return "{\"orderno\":\"" + orderno + "\",\"amount\":" + amount + "}";
}
}接口适配器测试代码:
/**
* 接口适配器测试类
*/
public class interfaceadaptertest {
public static void main(string[] args) {
orderxmlservice xmlservice = new orderxmlservice();
orderservice orderservice = new orderclientadapter(xmlservice);
// 调用重写的方法
string json = orderservice.queryorderjson("order20260415003");
boolean cancel = orderservice.cancelorder("order20260415003");
system.out.println("接口适配器返回json:" + json);
system.out.println("订单取消结果:" + cancel);
// 未重写的方法,调用抽象适配器的空实现(返回null/false)
system.out.println(orderservice.queryorderxml("order20260415003"));
}
}运行结果:
取消订单:order20260415003
接口适配器返回json:{"orderno":"order20260415003","amount":199.9}
订单取消结果:true
null
接口适配器核心要点:
- 优点:简化开发,调用方无需实现目标接口的所有方法,只需重写需要的方法;
- 缺点:只能适配一个目标接口,灵活性不如对象适配器;
- 适用场景:目标接口方法较多,且调用方只需要使用其中部分方法的场景(如spring中的handleradapter)。
三种适配器实现方式对比(面试必背)
| 实现方式 | 核心实现 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 类适配器 | 继承适配者,实现目标接口 | 实现简单,无需持有适配者引用 | 受单继承限制,耦合度高 | 适配者是具体类,无需继承其他类 |
| 对象适配器 | 持有适配者引用,实现目标接口 | 耦合度低,扩展性强,无继承限制 | 需额外持有适配者引用,代码量略多 | 企业开发主流场景,适配者可能变化 |
| 接口适配器 | 抽象类实现目标接口,空实现所有方法 | 简化开发,避免实现不必要的方法 | 只能适配一个目标接口,灵活性差 | 目标接口方法多,调用方只需部分方法 |
四、实战二:适配器模式进阶(结合spring,企业实战)
实际企业开发中,我们不会手动创建适配器实例,而是结合spring框架的依赖注入(di),将适配器、适配者交给spring容器管理,进一步降低耦合,同时规避spring xml配置中链接失效的问题(优先使用注解配置)。
场景升级:对接第三方支付sdk(接口不兼容)
假设项目中需要对接第三方支付宝sdk,第三方sdk的接口方法与项目内部的支付接口不兼容,使用对象适配器(推荐)结合spring实现适配,无需修改项目原有接口和第三方sdk代码。
步骤1:定义项目内部目标接口(target)
/**
* 项目内部目标接口:支付接口(项目统一规范)
*/
public interface payservice {
// 项目内部支付方法:参数为订单号、金额,返回支付结果(json)
string pay(string orderno, double amount);
}步骤2:定义第三方sdk适配者(adaptee)
假设这是第三方支付宝sdk的接口,无法修改,接口规范与项目内部不一致。
/**
* 适配者:第三方支付宝sdk接口(无法修改)
* 接口规范与项目内部payservice不兼容
*/
public class alipaysdk {
// 第三方sdk支付方法:参数为订单信息(json),返回支付结果(xml)
public string alipay(string orderinfo) {
// 模拟第三方sdk支付逻辑
system.out.println("第三方支付宝sdk支付:" + orderinfo);
return "<result><orderno>{orderno}</orderno><status>success</status></result>";
}
}步骤3:实现对象适配器(结合spring注解)
使用spring的@component注解将适配器、适配者交给spring管理,通过@autowired注入适配者,实现接口转换。
import org.springframework.stereotype.component;
// 适配者交给spring管理(第三方sdk实例)
@component
public class alipaysdk {
public string alipay(string orderinfo) {
system.out.println("第三方支付宝sdk支付:" + orderinfo);
return "<result><orderno>{orderno}</orderno><status>success</status></result>";
}
}
// 适配器交给spring管理,实现目标接口
@component
public class alipayadapter implements payservice {
// autowired注入适配者(第三方sdk实例),无需手动new
@autowired
private alipaysdk alipaysdk;
@override
public string pay(string orderno, double amount) {
// 1. 项目内部参数(订单号、金额)转换为第三方sdk需要的参数(json)
string orderinfo = "{\"orderno\":\"" + orderno + "\",\"amount\":" + amount + "}";
// 2. 调用第三方sdk的方法,获取xml格式结果
string xmlresult = alipaysdk.alipay(orderinfo);
// 3. 将xml结果转换为项目内部需要的json格式
string jsonresult = xmltojson(xmlresult, orderno);
// 4. 返回项目内部规范的结果
return jsonresult;
}
// 模拟xml转json(实际开发中使用fastjson、jackson等工具)
private string xmltojson(string xml, string orderno) {
return "{\"orderno\":\"" + orderno + "\",\"status\":\"success\",\"message\":\"支付成功\"}";
}
}步骤4:客户端通过spring调用(企业实战版)
通过spring的applicationcontext获取目标接口实例,直接调用方法,无需关心适配细节和实例创建。
import org.springframework.context.applicationcontext;
import org.springframework.context.annotation.annotationconfigapplicationcontext;
import org.springframework.context.annotation.componentscan;
// spring配置类:扫描组件(替代xml配置,避免链接失效)
@componentscan("com.example.adapter")
public class springconfig {
}
// 客户端调用(企业开发常用)
public class springadapterclient {
public static void main(string[] args) {
// 加载spring配置,获取容器(注解配置,无xml,避免链接失效)
applicationcontext context = new annotationconfigapplicationcontext(springconfig.class);
// 获取目标接口实例(spring自动注入适配器)
payservice payservice = context.getbean(payservice.class);
// 调用项目内部接口,底层自动通过适配器对接第三方sdk
string result = payservice.pay("order20260415004", 299.9);
system.out.println("项目内部支付结果:" + result);
}
}进阶要点(企业开发避坑)
- 优先使用注解配置(@component、@autowired、@componentscan),替代xml配置,避免xml中出现“link dead”(链接失效)问题;
- 适配器与适配者通过spring依赖注入关联,便于后续替换适配者(如将支付宝sdk替换为微信sdk,只需修改适配器注入的适配者);
- 可结合spring的@qualifier注解,实现多个适配者的灵活切换(如同时对接支付宝、微信sdk,通过注解指定适配者);
- 若必须使用xml配置,避免使用外部链接,可替换为本地xsd文件,或升级spring依赖版本,确保配置文件正常加载。
五、实战三:框架中的适配器模式(面试必说)
适配器模式在java主流框架中应用极其广泛,面试时能说出1-2个具体应用,会显得你理解更深刻,不是只懂理论。
1. spring 框架:handleradapter(核心应用)
spring mvc中的handleradapter是适配器模式的经典实现,核心作用是“适配不同类型的处理器(controller)”,让dispatcherservlet(前端控制器)无需关心不同处理器的调用方式,统一通过适配器调用。
- target(目标接口):handleradapter接口(定义了handlerequest()方法,统一处理器调用规范);
- adaptee(适配者):不同类型的处理器(如controller、httprequesthandler、servlet);
- adapter(适配器):具体适配器(如simplecontrollerhandleradapter、httprequesthandleradapter),适配不同类型的处理器。
原理:dispatcherservlet通过handleradapter找到对应的适配器,调用适配器的handlerequest()方法,适配器再调用具体处理器的方法,实现不同处理器的统一调用,完美解决“处理器类型多样、接口不统一”的问题。
2. spring 框架:methodinterceptor(aop中的适配器)
spring aop中的methodinterceptor(方法拦截器),本质也是接口适配器的实现。spring aop的拦截器链中,不同的拦截器可能实现不同的接口,通过适配器模式,将不同拦截器的接口转换为统一的拦截器接口,实现拦截器的链式调用。
3. java 内置适配器:inputstreamreader/outputstreamwriter
java io流中的inputstreamreader(字节流转字符流)、outputstreamwriter(字符流转字节流),是jdk内置的适配器模式实现,日常开发中经常用到。
- inputstreamreader:将inputstream(字节流,适配者)适配为reader(字符流,目标接口);
- outputstreamwriter:将outputstream(字节流,适配者)适配为writer(字符流,目标接口)。
// 示例:inputstreamreader(字节流转字符流,适配器模式)
inputstream inputstream = new fileinputstream("test.txt");
// inputstreamreader是适配器,将字节流适配为字符流
reader reader = new inputstreamreader(inputstream, standardcharsets.utf_8);4. 实际项目场景:日志框架适配
项目中原有日志框架使用log4j,后来需要升级为slf4j+logback,为了不修改原有项目中所有log4j的调用代码,使用适配器模式,实现log4j接口到slf4j接口的适配,让原有代码无需修改,即可使用新的日志框架。
六、适配器模式面试高频考点(必背)
1. 适配器模式的核心优势?
- 解耦:隔离原有接口和调用方,无需修改原有代码和调用方代码,实现接口兼容;
- 遵循开闭原则:新增适配场景时,只需新增适配器类,无需修改原有代码;
- 提高复用性:复用原有接口的功能,无需重复开发,降低维护成本;
- 灵活性高:可灵活适配不同接口,适配者或目标接口变化时,只需修改适配器。
2. 适配器模式的三种实现方式及区别?(高频中的高频)
核心区别:适配方式不同(继承 vs 持有引用 vs 抽象类空实现),结合表格记忆,面试直接说清:
- 类适配器:继承适配者,实现目标接口,受单继承限制,耦合度高;
- 对象适配器:持有适配者引用,实现目标接口,无继承限制,耦合度低,企业常用;
- 接口适配器:抽象类实现目标接口,空实现所有方法,适用于目标接口方法过多的场景。
3. 适配器模式和装饰者模式的区别?(面试易错点)
核心区别:目的不同,记住一句话即可:
- 适配器模式:核心是“解决接口不兼容”,让两个不相关的接口可以对接,不改变原有功能;
- 装饰者模式:核心是“增强原有功能”,在不改变原有接口的前提下,为对象增加新的功能。
补充:适配器模式是“转换接口”,装饰者模式是“增强功能”,两者都不修改原有代码,但目的和使用场景完全不同。
4. 适配器模式的缺点?(避坑重点)
- 增加系统复杂度:新增适配器类,会增加类的数量,后续维护成本略有增加;
- 适配逻辑复杂:当适配者和目标接口差异较大时,适配器的转换逻辑会比较复杂,容易出错;
- 可读性降低:调用方看不到适配细节,若适配器逻辑不清晰,后续排查问题难度较大。
5. 什么时候用适配器模式?(实战判断)
- 旧系统接口无法修改,需要对接新系统接口(如旧订单接口适配新前端);
- 对接第三方sdk,第三方接口规范与项目内部接口不兼容(如支付sdk、日志sdk);
- 系统中存在多个接口规范不一致,但功能相似的模块,需要统一调用方式;
- 目标接口方法过多,调用方只需使用部分方法(使用接口适配器)。
七、总结(实战+面试双达标)
对于java后端开发者来说,适配器模式是“解决接口兼容问题的万能钥匙”,它不像工厂模式、单例模式那样频繁使用,但在系统集成、第三方对接、旧系统维护场景中,是不可或缺的设计模式,也是面试中高频考察的重点(尤其是三种实现方式的区别)。
- 基础:掌握3个核心角色,理解适配器模式“接口转换”的核心思想,能手动实现三种适配器;
- 实战:重点掌握对象适配器(企业常用),结合spring依赖注入实现适配,规避xml链接失效的坑;
- 面试:能清晰区分三种适配器的区别,说出框架中的应用场景,掌握与装饰者模式的区别;
- 避坑:记住“接口不兼容用适配器,功能增强用装饰者”,避免过度设计(如接口兼容可直接修改代码时,无需使用适配器)。
记住一句话:适配器模式的核心是“兼容不修改,转换无感知”,通过中间适配器,让不兼容的接口无缝对接,既保证原有系统稳定,又满足新的业务需求。掌握它,能让你在系统集成、旧系统维护中事半功倍,面试时也能轻松应对设计模式相关提问。
补充:常见问题解决
- 问题1:适配者和目标接口差异过大,适配器逻辑复杂怎么办?
- 解决:拆分适配器,将复杂的转换逻辑拆分为多个小适配器,或提取公共转换方法,降低维护难度;
- 问题2:spring xml配置中出现“link dead”(链接失效)怎么办?
- 解决:优先使用注解配置(@component、@componentscan);若必须用xml,替换为本地spring xsd文件,或升级spring依赖版本;
- 问题3:如何实现多个适配者的灵活切换?
解决:结合spring的@qualifier注解,注入不同的适配者;或使用工厂模式,根据条件动态创建对应的适配器。
到此这篇关于java 适配器模式从入门到实战指南的文章就介绍到这了,更多相关java 适配器模式内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论