← 返回题目列表

如何手写一个适配器模式?核心代码怎么设计?

高频 中等 第 4 / 25 题 更新于 2026/07/28
适配器模式代码实现Java对象适配器设计模式

简化版

手写适配器模式通常先定义客户端期望的 Target 接口,再创建已有的 Adaptee 类,最后写 Adapter 实现 Target,并在内部持有 Adaptee,把 Target 方法转换成 Adaptee 方法调用。工程中更推荐对象适配器,因为它基于组合,灵活且便于依赖注入。

详细版

核心代码可以按四步写:

  1. 定义目标接口;
  2. 准备已有但不兼容的类;
  3. 编写适配器实现目标接口;
  4. 客户端只依赖目标接口。

示例:

interface Target {
    void request();
}

class Adaptee {
    void specificRequest() {
        System.out.println("已有接口");
    }
}

class Adapter implements Target {
    private final Adaptee adaptee;

    Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    public void request() {
        adaptee.specificRequest();
    }
}

真实项目中,适配器不只是方法转发,还可能做参数转换、单位转换、异常转换、返回值包装和日志记录。但适配器不应该承载过多业务规则。

完整版教学

一、第一步:定义目标接口

目标接口应该从客户端需求出发。

比如系统内部希望统一发送通知:

interface Notifier {
    NotifyResult send(NotifyCommand command);
}

这个接口是业务层希望看到的稳定抽象。

不要一开始就把第三方 SDK 的请求对象放进来,否则适配器还没写,系统就已经被第三方模型污染了。

二、第二步:已有类接口不兼容

假设第三方客户端是:

class ThirdPartyMessageClient {
    ThirdPartyResponse push(String mobile, String text, String sign) {
        // 调用第三方接口
        return new ThirdPartyResponse();
    }
}

它能发送消息,但方法名、参数形式和返回值都不符合 Notifier

这就是适配器发挥作用的地方。

三、第三步:编写对象适配器

class ThirdPartyNotifierAdapter implements Notifier {
    private final ThirdPartyMessageClient client;
    private final String sign;

    ThirdPartyNotifierAdapter(ThirdPartyMessageClient client, String sign) {
        this.client = client;
        this.sign = sign;
    }

    public NotifyResult send(NotifyCommand command) {
        ThirdPartyResponse response = client.push(
            command.getPhone(),
            command.getContent(),
            sign
        );
        return convert(response);
    }

    private NotifyResult convert(ThirdPartyResponse response) {
        return new NotifyResult(response.isSuccess(), response.getMessage());
    }
}

这里适配器做了三件事:

  • 把内部命令转换成第三方参数;
  • 调用第三方客户端;
  • 把第三方响应转换成内部结果。

四、第四步:客户端只依赖目标接口

业务层代码应该是:

class OrderService {
    private final Notifier notifier;

    void createOrder() {
        // 创建订单
        notifier.send(command);
    }
}

业务层不知道底层是阿里云、腾讯云还是其他供应商。这就是适配器带来的解耦效果。

五、适配器里可以做转换,但不要做核心业务

适配器适合做:

  • 参数名称转换;
  • 数据类型转换;
  • 单位转换;
  • 返回值包装;
  • 异常转换;
  • 外部错误码映射。

不适合做:

  • 订单状态流转;
  • 价格计算;
  • 库存扣减;
  • 复杂审批;
  • 领域规则判断。

这些核心业务应该留在业务层或领域层。

六、对象适配器为什么更适合工程

对象适配器可以配合依赖注入:

@Component
class ThirdPartyNotifierAdapter implements Notifier {
    private final ThirdPartyMessageClient client;
}

测试时也可以传入 mock client。相比继承式类适配器,组合方式更灵活,耦合更低。

七、转换的可逆性与测试证据

内部时间使用 UTC 毫秒 1720000000000,供应商要求秒,则应整除 1000;回转后最多损失 999 ms 精度,必须确认契约允许。

DomainRequest -> map units/time/null -> VendorRequest -> call -> map response/error

适配器的验收标准不是“调用成功”,而是 Target 的业务语义在转换前后保持成立。参数、单位、时区、精度、空值、错误码和资源所有权都应逐项形成契约样例,否则最危险的错误会以“成功响应”的形式潜伏。

八、语义边界与契约样例验证

检查维度应确认的内容
机制正确性适配器测试至少要覆盖正常映射、边界值、外部错误和资源释放。
适用边界转换函数应集中、可测试并处理溢出、精度和 null;网络超时与重试不要随意藏在字段映射代码里。
测试证据注入 Adaptee 测试替身,断言外部请求、返回映射、异常转换和调用次数
工程代价重点统计字段映射与对象分配成本、外部模型变更影响面,以及多层版本适配造成的认知债务

易错点:适配器测试至少要覆盖正常映射、边界值、外部错误和资源释放。

九、常见误区与追问

  • 误区:字段一一复制就是可靠适配。 同名字段可能单位、时区和空值语义不同,必须按契约逐项确认。
  • 误区:适配器只需要让方法签名能够编译。 真正的适配还包括单位、时区、错误码、空值、资源释放和幂等语义;签名一致不代表行为兼容。
  • 追问:如何测试适配器而不调用真实供应商? 注入 Adaptee 测试替身,断言传入外部请求和映射回的结果、异常及调用次数。
  • 追问:适配器应该放在哪一层? 通常放在系统边界或防腐层,让业务层只依赖 Target,避免第三方模型和异常向内扩散。
  • 追问:适配器测试为什么需要契约样例? 因为类型相同仍可能单位、精度或错误语义不同;契约样例能证明转换后的行为而不只是编译通过。

十、加强记忆

手写适配器记住四步:定义 Target,拿到 Adaptee,Adapter 实现 Target 并持有 Adaptee,Client 只依赖 Target。代码看似简单,真正的工程重点是把参数、返回值、异常和外部模型都挡在适配器内部。