前言
你有没有遇到过这种情况?
同事接手了一个老项目,发现旧接口返回的数据格式和新系统完全不兼容。于是他在代码里写满了
if-else,企图在每个调用的地方做转换。结果不仅代码臃肿不堪,一处修改就牵连一堆地方报错。
如果你也深受其扰,那么今天我要介绍的设计模式------适配器模式,正是为你准备的。
什么是适配器模式?
适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主要目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。
说人话:它就是一个翻译官。
想象一下你去欧洲旅游插座不匹配,需要一个转换插头------适配器就是那个插头转换器。
适配器的三种形态
在 Java 中,适配器模式主要有三种实现方式:
1. 类适配器 ------ 继承实现
核心思路:适配器同时继承被适配者(Adaptee),并实现目标接口(Target)。
来看一个具体的例子。假设我们有两个充电标准:
- 国标(Target):我们期望使用的接口
- 英标(Adaptee):已有的旧实现
csharp
// 目标接口 - 国标
public interface NationalStandard {
void use();
}
// 被适配者 - 英标(已有实现,不想改动)
public class EnglandStandard {
public void userDefault() {
System.out.println("当前是英标接口");
}
}
// 适配器
public class Adapter extends EnglandStandard implements NationalStandard {
@Override
public void use() {
// 调用英标的方法
userDefault();
// 做转换适配
System.out.println("→ 已转换为国标接口");
}
}
// 客户端使用
public class Client {
public static void main(String[] args) {
NationalStandard nationalStandard = new Adapter();
nationalStandard.use();
}
}
输出:
当前是英标接口
→ 已转换为国标接口
类适配器的特点:
- 优点:直接继承,调用简单
- 缺点:Java 单继承,灵活性受限
2. 对象适配器 ------ 组合实现(推荐)
核心思路 :适配器不继承被适配者,而是持有它的实例。
csharp
// 同样的目标接口
public interface NationalStandard {
void use();
}
// 同样的英标实现
public class EnglandStandard {
public void userDefault() {
System.out.println("当前是英标接口");
}
}
// 对象适配器 - 持有而非继承
public class Adapter implements NationalStandard {
private EnglandStandard englandStandard = new EnglandStandard();
@Override
public void use() {
englandStandard.userDefault();
System.out.println("→ 对象适配器:已转换为国标接口");
}
}
输出:
当前是英标接口
→ 对象适配器:已转换为国标接口
对象适配器的特点:
- ✅ 更灵活,不受单继承限制
- ✅ 推荐优先使用
- ⚠️ 代码稍多一步
3. 接口适配器 ------ 解决接口臃肿
核心思路:当一个接口方法太多,我们只需要实现其中几个时,可以用一个抽象适配器类空实现所有方法,子类按需覆盖。
less
// 有一个很多方法的接口
public interface AbsAdapterListener {
void start();
void pause();
void stop();
void end();
}
// 抽象适配器 - 空实现所有方法
abstract class AbstractAdapter implements AbsAdapterListener {
@Override
public void start() {}
@Override
public void pause() {}
@Override
public void stop() {}
@Override
public void end() {}
}
// 使用时只需覆盖关心的方法
public class Client {
public static void main(String[] args) {
// 只关心 start
new Player().listen(new AbstractAdapter() {
@Override
public void start() {
System.out.println("只回调 start 方法");
}
});
// 只关心 end
new Player().listen(new AbstractAdapter() {
@Override
public void end() {
System.out.println("只回调 end 方法");
}
});
}
}
实战场景
适配器模式在日常开发中随处可见:
| 场景 | 说明 |
|---|---|
| 旧系统改造 | 老接口数据格式转新格式 |
| 第三方 SDK 接入 | 第三方库的接口转内部统一接口 |
| 不同支付渠道 | 支付宝、微信、银联各自一套接口,适配成统一支付接口 |
| 数据转换 | JSON 转 XML,Map 转 Object |
真实代码演示
假设我们接入一个第三方天气 API:
typescript
// 内部统一天气接口(我们的目标)
public interface WeatherService {
WeatherResult getWeather(String city);
}
// 第三方SDK的旧接口(被适配者)
public class ThirdPartyWeatherSDK {
public Map<String, Object> query(String cityName) {
// 第三方返回的是 Map
Map<String, Object> result = new HashMap<>();
result.put("city", cityName);
result.put("temp", 22);
result.put("wd", "north");
return result;
}
}
// 适配器
public class WeatherAdapter implements WeatherService {
private ThirdPartySDK sdk = new ThirdPartySDK();
@Override
public WeatherResult getWeather(String city) {
// 调用旧接口
Map<String, Object> rawData = sdk.query(city);
// 转换为我们的格式
return WeatherResult.builder()
.city((String) rawData.get("city"))
.temperature((Integer) rawData.get("temp"))
.windDirection((String) rawData.get("wd"))
.build();
}
}
这样,调用方只需依赖 WeatherService 接口,完全不用关心背后对接的是哪个第三方。
适配器模式 vs 装饰器模式
| 适配器模式 | 装饰器模式 | |
|---|---|---|
| 目的 | 转换接口,实现兼容 | 增强功能 |
| 关系 | 通常是临时桥接 | 长期增强 |
| 新接口 | 改变原有接口 | 保持原有接口 |
| 举例 | 转换插头 | 给手机加壳 |
简单记忆:适配器是"穿马甲"(换皮),装饰器是"穿衣服"(加功能)
总结
适配器模式的核心价值在于:
- 解耦 ------ 客户端依赖抽象,不关心具体实现
- 复用 ------ 旧代码不用重写,稍作适配就能上场
- 规范 ------ 统一接口,方便扩展