文章目录
-
- 前言
- [1. 适配器模式是什么?](#1. 适配器模式是什么?)
- [2. 为什么要用适配器?能解决什么问题?](#2. 为什么要用适配器?能解决什么问题?)
- [3. 实现思路](#3. 实现思路)
- [4. 类适配器 vs 对象适配器](#4. 类适配器 vs 对象适配器)
- [5. 示例一:把"老接口"适配成"新接口"(对象适配器)](#5. 示例一:把“老接口”适配成“新接口”(对象适配器))
-
- [5.1 目标接口(客户端期望)](#5.1 目标接口(客户端期望))
- [5.2 已有类(第三方/老系统:接口不一致)](#5.2 已有类(第三方/老系统:接口不一致))
- [5.3 适配器(把 String 转成 byte[])](#5.3 适配器(把 String 转成 byte[]))
- [5.4 客户端使用](#5.4 客户端使用)
- [6. 示例二:常见"返回值适配"(支付/日志/响应统一)](#6. 示例二:常见“返回值适配”(支付/日志/响应统一))
-
- [6.1 目标响应](#6.1 目标响应)
- [6.2 已有类返回老格式](#6.2 已有类返回老格式)
- [6.3 适配器把 OldPayResult 转成 ApiResponse](#6.3 适配器把 OldPayResult 转成 ApiResponse)
- [7. 适配器模式常见结构](#7. 适配器模式常见结构)
- [8. 优缺点](#8. 优缺点)
- [9. 适用场景](#9. 适用场景)
- [10. 总结](#10. 总结)
前言
在很多项目里,会遇到这种情况:已经存在一个类/第三方库/老系统接口 ,但它的"输入输出/方法命名/数据结构"跟当前系统想要的不一致。
不可能为了兼容它就把原来的代码大改一遍------于是就需要一个"翻译器"把不匹配的接口对齐。
这就是**适配器模式(Adapter Pattern)**要解决的问题。

1. 适配器模式是什么?
适配器模式:把一个类的接口"包装/转换"成客户端期望的接口,让原本因为接口不兼容而无法一起工作的类能协同工作。
它常见的角色可以理解为:
- 目标接口(Target):客户端期望调用的方法集合
- 已有类(Adaptee):手上已经有的、但接口不匹配的类(第三方/老系统)
- 适配器(Adapter):实现目标接口,并内部调用已有类,把数据/行为进行转换
2. 为什么要用适配器?能解决什么问题?
适配器模式通常在以下场景非常常见:
- 第三方接口不符合系统规范
- 比如第三方库返回
Result<T>,但系统统一用ApiResponse<T>
- 比如第三方库返回
- 老代码接口需要被新系统"继续使用"
- 不想重写老系统逻辑,只做外部适配
- 同一功能需要对不同来源做兼容
- 例如支付渠道:微信/支付宝接口不同,但上层都希望统一
pay()形式
- 例如支付渠道:微信/支付宝接口不同,但上层都希望统一
3. 实现思路
适配器模式的"落地流程"一般是:
- 定义系统期望的接口(Target)
- 找到已有类并分析它与目标接口的差异(参数类型、返回值、单位、字段含义等)
- 写一个适配器类(Adapter):
- 对外实现 Target
- 内部持有 Adaptee(组合为主)
- 把参数/返回结果转换成目标格式
4. 类适配器 vs 对象适配器
GoF 里常见两种结构(思想类似):
- 类适配器(继承方式):Adapter 继承已有类 Adaptee,同时实现 Target
- 对象适配器(组合方式):Adapter 内部持有 Adaptee 实例,并实现 Target
更推荐"对象适配器",因为更符合组合优于继承,也更灵活。
下面用"对象适配器"展开讲解。
5. 示例一:把"老接口"适配成"新接口"(对象适配器)
5.1 目标接口(客户端期望)
比如客户端希望所有打印机都实现统一接口:
java
public interface Printer {
void print(String text);
}
5.2 已有类(第三方/老系统:接口不一致)
java
public class OldPrinter {
// 老系统要求打印的是字节数组
public void printBytes(byte[] bytes) {
System.out.println("OldPrinter printing: " + new String(bytes));
}
}
5.3 适配器(把 String 转成 byte[])
java
public class PrinterAdapter implements Printer {
private final OldPrinter oldPrinter;
public PrinterAdapter(OldPrinter oldPrinter) {
this.oldPrinter = oldPrinter;
}
@Override
public void print(String text) {
byte[] bytes = text.getBytes(); // 参数转换
oldPrinter.printBytes(bytes); // 调用已有类
}
}
5.4 客户端使用
java
public class Demo {
public static void main(String[] args) {
OldPrinter old = new OldPrinter();
Printer printer = new PrinterAdapter(old); // 关键:对外统一成 Printer
printer.print("Hello Adapter!");
}
}
效果 :客户端完全不需要关心 OldPrinter 怎么打印,只能调用 Printer.print()。
6. 示例二:常见"返回值适配"(支付/日志/响应统一)
很多适配器不是只做参数转换,还会做返回值/数据结构转换。
6.1 目标响应
java
public class ApiResponse<T> {
public int code;
public String msg;
public T data;
public ApiResponse(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}
6.2 已有类返回老格式
java
public class OldPayResult {
public boolean ok;
public String reason;
public String payId;
}
6.3 适配器把 OldPayResult 转成 ApiResponse
java
public class PayAdapter {
private final OldPayService oldPayService;
public PayAdapter(OldPayService oldPayService) {
this.oldPayService = oldPayService;
}
public ApiResponse<String> pay(String orderId) {
OldPayResult r = oldPayService.pay(orderId);
if (r.ok) {
return new ApiResponse<>(0, "success", r.payId);
}
return new ApiResponse<>(-1, r.reason, null);
}
}
这类适配器的价值在于:让业务层只认识统一协议,而不需要理解第三方细节。
7. 适配器模式常见结构
可以把 Adapter 的工作理解为两件事:
- 接口对齐:Adapter 对外暴露客户端需要的接口
- 语义/格式转换:Adapter 负责字段、单位、编码、异常模型等差异
8. 优缺点
优点
- 让原本不兼容的类可以协作(兼容第三方/老系统)
- 符合开闭原则:不需要修改已有类,只新增适配器
- 业务层更干净:客户端只依赖统一接口
缺点
- 类会变多:每个差异都可能带来一个 Adapter
- 适配器可能成为"翻译逻辑的集中地",需要避免过度膨胀
- 语义转换如果做得不好,可能隐藏真实差异导致"看似能用但行为不一致"
9. 适用场景
适配器模式适用于:
- 要复用第三方库/老系统,但接口不匹配
- 希望把多个来源包装成统一接口
- 不想改动 Adaptee(或不允许改动)
不太适用于:
- 差异非常大且改动频繁,适配器会越来越复杂(可能需要重构/替换方案)
- "适配器"实际上在做大量业务逻辑,应该考虑拆分或调整架构
10. 总结
当发现"已有能力能用,但接口不符合系统期待",就应该考虑适配器模式:用一个 Adapter 做翻译,让系统其余部分保持稳定。