Skip to content

适配器模式

适配器模式用于把一个已有接口转换成调用方期望的接口。它解决的是“双方能力能对上,但接口形式不一致”的问题。

常见场景:对接第三方支付、对接不同医院接口、旧系统改造、统一短信/邮件/文件存储客户端、Spring MVC 参数适配、日志框架适配。

为什么需要适配器

假设系统需要统一发送通知,但不同供应商接口完全不同:

java
smsVendor.sendSms(phone, content);
emailVendor.sendMail(email, title, content);

业务层如果直接依赖每个供应商:

  1. 业务代码会充满供应商差异。
  2. 更换供应商要改大量代码。
  3. 多个通知渠道无法统一编排。
  4. 测试时难以替换外部依赖。

适配器把不同接口统一成业务期望的接口。

总体流程

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、权限、事务、远程代理

商业场景

医疗平台对接不同医院系统时,常见接口差异:

  1. 医院 A 用 patientNo,医院 B 用 cardNo
  2. 医院 A 返回 JSON,医院 B 返回 XML。
  3. 医院 A 状态码 0 成功,医院 B 状态码 SUCCESS 成功。
  4. 医院 A 按页拉取,医院 B 按时间窗口拉取。

可以定义统一采集接口:

java
public interface HospitalDataAdapter {
    HospitalCode hospital();

    List<PatientRecord> fetchPatients(FetchCommand command);
}

每家医院一个适配器,主流程不关心每家医院的协议差异。

常见坑

问题后果建议
适配器里写业务规则适配层膨胀适配器只做协议和字段转换
统一接口设计太宽泛到处传 Map,类型不安全抽象真实稳定的公共能力
忽略错误码转换上层无法统一处理失败返回统一错误模型
适配器吞异常排查困难保留原始错误信息和请求标识
为一个调用也强行适配增加无意义层级变化点真实存在再抽象

面试标准回答

text
适配器模式用于把已有接口转换成调用方期望的统一接口,解决接口不兼容但能力可复用的问题。典型场景是第三方接口、老系统改造、多供应商接入。它和代理模式不同,适配器关注接口转换,代理关注访问控制或增强。使用时要避免把业务规则塞进适配器,适配器主要负责协议、参数、返回值和错误码转换。

本章小结

适配器模式的核心是“隔离外部差异”。当外部系统、供应商、旧接口不可控时,用适配器把不稳定的协议和字段挡在边界外,让内部业务代码依赖稳定模型。