什么是适配器模式?它解决什么问题?
简化版
适配器模式是把一个已有类或接口转换成客户端期望的接口,让原本因为接口不兼容而不能一起工作的对象可以协作。它主要解决“已有能力可用,但接口不匹配”的问题,常用于旧系统改造、第三方 SDK 封装、接口统一和兼容历史代码。
详细版
适配器模式属于结构型设计模式,核心思想是“转换接口,而不是重写能力”。
典型场景是:客户端只认识 Target 接口,但现有对象 Adaptee 提供的是另一套接口。我们不希望修改客户端,也不希望改动已有对象,就在中间加一个 Adapter,由适配器实现 Target,内部调用 Adaptee。
结构可以理解为:
Client -> Target
^
|
Adapter -> Adaptee
适配器模式的价值是复用已有代码、隔离外部接口变化、降低系统集成成本。它不负责增强功能,也不负责隐藏复杂子系统;它的重点是让接口“对得上”。
面试时可以举例:电源转换插头、第三方支付接口统一、日志框架适配、老接口迁移到新接口、Java I/O 中字节流到字符流的转换思想。
完整版教学
一、先理解“接口不兼容”是什么问题
假设系统里有一个统一的支付接口:
interface PayService {
void pay(String orderId, int amount);
}
业务代码都按这个接口调用:
payService.pay(orderId, amount);
后来接入一个第三方支付 SDK,但它的方法长这样:
class ThirdPartyPayClient {
void createPayment(String tradeNo, String money) {
// 调用第三方支付
}
}
它不是不能用,而是接口和系统期望的不一样。直接改业务代码去适配第三方 SDK,会让业务层依赖外部接口细节,后续换 SDK 时更麻烦。
适配器模式就是在中间加一层:
class ThirdPartyPayAdapter implements PayService {
private final ThirdPartyPayClient client;
public void pay(String orderId, int amount) {
client.createPayment(orderId, String.valueOf(amount));
}
}
业务层仍然调用 PayService,第三方差异被适配器消化。
二、适配器模式的本质是“复用已有能力”
适配器不是重新写一个支付系统,也不是给支付系统额外加很多功能。它只是把已有能力包装成目标接口能接受的形态。
这和现实里的电源转换头很像:笔记本需要三孔插头,墙上只有两孔插座,转换头不发电,只负责把接口对齐。
软件里的适配器也是一样:已有能力在 Adaptee 里,客户端需要的是 Target,适配器负责转换。
三、适配器模式的三个核心角色
适配器模式通常有三个角色:
- Target:客户端期望的目标接口;
- Adaptee:已经存在但接口不兼容的类;
- Adapter:实现 Target,内部调用 Adaptee。
示意图:
Client 只依赖 Target
Adapter 实现 Target
Adapter 内部调用 Adaptee
这样客户端不用知道 Adaptee 的存在。
四、适配器解决的是集成问题,不是抽象所有差异
适配器很适合系统集成,但不要把它理解成万能转换器。
如果两个接口只是参数名称不同、方法名不同、返回值格式略有差异,适配器很好用。
但如果两个系统的业务语义完全不同,比如一个是“预授权支付”,另一个是“立即扣款支付”,只是强行改方法名并不能解决语义差异。此时需要重新梳理业务抽象,而不是只写适配器。
五、适配器常见于哪些地方
常见场景包括:
- 第三方 SDK 接入;
- 老接口兼容新接口;
- 多支付渠道统一;
- 多短信供应商统一;
- 日志门面适配不同日志实现;
- 字节流和字符流转换;
- 框架中把用户实现适配成框架需要的回调接口。
这些场景共同点是:已有对象能做事,但接口不符合当前系统期望。
六、Target 与 Adaptee 的语义转换
业务要求金额以分传入,第三方接口接收元;订单 12345 分必须转换为 123.45 元,少除以 100 会放大 100 倍。
Client -> Target.pay(cents) -> Adapter(convert /100) -> Adaptee.pay(yuan)
适配器的验收标准不是“调用成功”,而是 Target 的业务语义在转换前后保持成立。参数、单位、时区、精度、空值、错误码和资源所有权都应逐项形成契约样例,否则最危险的错误会以“成功响应”的形式潜伏。
七、语义边界与契约样例验证
| 检查维度 | 应确认的内容 |
|---|---|
| 机制正确性 | 适配的核心是让已有能力满足目标契约,而不是简单改方法名。 |
| 适用边界 | 适配器能隔离接口差异,不能掩盖无法等价转换的业务语义;信息丢失时必须拒绝或显式降级。 |
| 测试证据 | 注入 Adaptee 测试替身,断言外部请求、返回映射、异常转换和调用次数 |
| 工程代价 | 重点统计字段映射与对象分配成本、外部模型变更影响面,以及多层版本适配造成的认知债务 |
易错点:适配的核心是让已有能力满足目标契约,而不是简单改方法名。
八、常见误区与追问
- 误区:适配器会修改被适配类源码。 典型适配器在外部包装或继承已有类,不要求修改第三方或遗留实现。
- 误区:适配器只需要让方法签名能够编译。 真正的适配还包括单位、时区、错误码、空值、资源释放和幂等语义;签名一致不代表行为兼容。
- 追问:怎样定义 Target 才稳定? 从本系统业务语义出发定义最小契约,不照抄某个供应商的数据模型。
- 追问:适配器应该放在哪一层? 通常放在系统边界或防腐层,让业务层只依赖 Target,避免第三方模型和异常向内扩散。
- 追问:适配器测试为什么需要契约样例? 因为类型相同仍可能单位、精度或错误语义不同;契约样例能证明转换后的行为而不只是编译通过。
九、加强记忆
适配器模式要记成“已有能力不能直接用,中间加一层把接口转成客户端想要的样子”。它解决接口不兼容,重点是复用和集成;不是增强功能,也不是隐藏子系统,而是让旧对象、第三方对象或不同接口体系能在统一目标接口下协作。