适配器模式
适配器模式用于把一个已有接口转换成调用方期望的接口。它解决的是“双方能力能对上,但接口形式不一致”的问题。
常见场景:对接第三方支付、对接不同医院接口、旧系统改造、统一短信/邮件/文件存储客户端、Spring MVC 参数适配、日志框架适配。
为什么需要适配器
假设系统需要统一发送通知,但不同供应商接口完全不同:
java
smsVendor.sendSms(phone, content);
emailVendor.sendMail(email, title, content);业务层如果直接依赖每个供应商:
- 业务代码会充满供应商差异。
- 更换供应商要改大量代码。
- 多个通知渠道无法统一编排。
- 测试时难以替换外部依赖。
适配器把不同接口统一成业务期望的接口。
总体流程
mermaid
flowchart TD
A["业务代码调用统一接口"] --> B["通知适配器"]
B --> C["转换参数"]
C --> D["调用第三方接口"]
D --> E["转换返回结果"]
E --> F["返回统一结果"]角色
| 角色 | 作用 |
|---|---|
| Target | 调用方期望的统一接口 |
| Adaptee | 已存在但接口不兼容的对象 |
| Adapter | 把 Target 调用转换成 Adaptee 调用 |
Java Demo:统一通知适配器
统一接口:
java
public interface NoticeSender {
NoticeType type();
SendResult send(NoticeCommand command);
}短信供应商旧接口:
java
public class SmsVendorClient {
public boolean sendSms(String phone, String text) {
System.out.println("send sms to " + phone + ": " + text);
return true;
}
}短信适配器:
java
@Component
public class SmsNoticeAdapter implements NoticeSender {
private final SmsVendorClient smsVendorClient = new SmsVendorClient();
@Override
public NoticeType type() {
return NoticeType.SMS;
}
@Override
public SendResult send(NoticeCommand command) {
boolean success = smsVendorClient.sendSms(command.receiver(), command.content());
return success ? SendResult.success() : SendResult.fail("短信发送失败");
}
}邮件供应商旧接口:
java
public class EmailVendorClient {
public String sendMail(String email, String subject, String body) {
return "OK";
}
}邮件适配器:
java
@Component
public class EmailNoticeAdapter implements NoticeSender {
private final EmailVendorClient emailVendorClient = new EmailVendorClient();
@Override
public NoticeType type() {
return NoticeType.EMAIL;
}
@Override
public SendResult send(NoticeCommand command) {
String code = emailVendorClient.sendMail(
command.receiver(),
command.title(),
command.content()
);
return "OK".equals(code) ? SendResult.success() : SendResult.fail("邮件发送失败");
}
}业务层统一调用:
java
@Service
public class NoticeService {
private final Map<NoticeType, NoticeSender> senderMap;
public NoticeService(List<NoticeSender> senders) {
this.senderMap = senders.stream()
.collect(Collectors.toMap(NoticeSender::type, sender -> sender));
}
public SendResult send(NoticeCommand command) {
NoticeSender sender = senderMap.get(command.type());
if (sender == null) {
throw new IllegalArgumentException("不支持的通知类型");
}
return sender.send(command);
}
}这段代码里,适配器负责屏蔽供应商接口差异,业务层只依赖统一接口。
和代理模式区别
| 对比 | 适配器 | 代理 |
|---|---|---|
| 目的 | 接口转换 | 控制访问或增强行为 |
| 目标 | 让不兼容接口能一起工作 | 让调用经过代理入口 |
| 是否改变接口 | 通常改变 | 通常保持相同接口 |
| 场景 | 第三方接口、老系统兼容 | AOP、权限、事务、远程代理 |
商业场景
医疗平台对接不同医院系统时,常见接口差异:
- 医院 A 用
patientNo,医院 B 用cardNo。 - 医院 A 返回 JSON,医院 B 返回 XML。
- 医院 A 状态码
0成功,医院 B 状态码SUCCESS成功。 - 医院 A 按页拉取,医院 B 按时间窗口拉取。
可以定义统一采集接口:
java
public interface HospitalDataAdapter {
HospitalCode hospital();
List<PatientRecord> fetchPatients(FetchCommand command);
}每家医院一个适配器,主流程不关心每家医院的协议差异。
常见坑
| 问题 | 后果 | 建议 |
|---|---|---|
| 适配器里写业务规则 | 适配层膨胀 | 适配器只做协议和字段转换 |
| 统一接口设计太宽泛 | 到处传 Map,类型不安全 | 抽象真实稳定的公共能力 |
| 忽略错误码转换 | 上层无法统一处理失败 | 返回统一错误模型 |
| 适配器吞异常 | 排查困难 | 保留原始错误信息和请求标识 |
| 为一个调用也强行适配 | 增加无意义层级 | 变化点真实存在再抽象 |
面试标准回答
text
适配器模式用于把已有接口转换成调用方期望的统一接口,解决接口不兼容但能力可复用的问题。典型场景是第三方接口、老系统改造、多供应商接入。它和代理模式不同,适配器关注接口转换,代理关注访问控制或增强。使用时要避免把业务规则塞进适配器,适配器主要负责协议、参数、返回值和错误码转换。本章小结
适配器模式的核心是“隔离外部差异”。当外部系统、供应商、旧接口不可控时,用适配器把不稳定的协议和字段挡在边界外,让内部业务代码依赖稳定模型。
