第 3 章:适配层架构设计

我们开始聊适配层。很多人觉得SDK改造就是改改接口参数,换个调用方式。说实话,这么想的项目最后基本都翻车了。我在早期的工作中也踩过这种坑——直接把业务逻辑塞到接口转换函数里,结果每次SDK升级都要改十来个地方。

后来我才意识到,适配层的本质是隔离变化。你的业务代码不应该知道底层SDK长什么样,也不应该关心它今天换成了哪家。适配层就是那道防火墙,把不稳定的东西挡在外面。

分层设计思想:先画清楚边界

分层这事儿听起来像老生常谈,但实践起来很多人容易走偏。我习惯从三个维度去切:

  • 变化维度:哪些东西容易变?SDK接口签名、数据格式、依赖路径。这些全扔进适配层。
  • 复用维度:哪些逻辑会被多个业务方共用?比如认证、重试、日志。这些也应该归适配层管。
  • 稳定维度:业务核心逻辑、领域模型、业务流程。它们必须远离底层变化,放在业务层。

拿一个支付SDK改造来举例。你想想看,业务层只需要知道「调起支付,传入金额和订单号」。至于底层用的是微信支付SDK的 WXPay.request 还是支付宝的 AlipayClient.execute,适配层负责翻译。业务层根本不关心。

分层核心原则:上层依赖下层的抽象,不依赖具体实现。业务层只依赖适配层定义的接口,适配层依赖SDK,但要把依赖方向倒过来。

我见过最糟糕的设计,业务层直接引用了SDK的枚举类型。后来SDK版本升级把枚举值从大写改成了小写,业务代码里二十多处 if (status == SDKConstants.SUCCESS) 全炸了。嗯,这就是不分层的代价。

适配层与业务层解耦:怎么做到的?

解耦的核心就三个字:接口隔离。业务层定义自己需要的接口,适配层去实现它。SDK挂掉了?换一个适配实现就行,业务层一行代码都不用改。

具体步骤我一般这样设计:

  1. 业务层定义目标接口:声明业务需要的功能,比如 PaymentResult pay(String orderId, BigDecimal amount)。
  2. 适配层实现该接口:内部调用SDK的原生方法,做参数转换、结果映射、异常包装。
  3. 依赖注入桥接:通过工厂或DI容器将适配实现注入到业务层。业务层只知道接口,不知道具体实现是谁。

记得有次我们对接一个第三方推送SDK,对方的接口命名特别抽象:send(String a, Map b, Callback c)。业务方每次调用都要翻文档才记得 a 是什么、b 的 key 有哪些。后来我在适配层封装了一个 PushResult sendNotification(PushMessage message),业务方看方法名就知道传什么,再也不用去查SDK文档了。这就是解耦带来的直接好处——降低认知负担。

我的小技巧:适配层接口的参数尽量用业务领域的值对象(Value Object),避免暴露SDK的数据结构。比如 PushMessage 对象里只包含 title、body、target,而不是SDK的 JSONObject 或 HashMap。

接口适配器模式(Adapter)的应用

说透了,适配器模式就是给SDK「套个壳」。但怎么套也有讲究。我常用的有两种形式:

适配器类型实现方式适用场景我个人的偏好
类适配器继承SDK的类,实现目标接口SDK可继承且接口固定时少用,继承耦合太强
对象适配器持有SDK实例,组合调用绝大多数场景强烈推荐,灵活且易测试

对象适配器是我最喜欢的做法。你可以通过构造函数或setter注入SDK实例,这样单元测试时就能轻松替换成Mock。我之前在一个项目里就把它用活了:

// 业务层定义的接口
public interface PaymentAdapter {
    PaymentResult charge(Order order);
}

// 适配层实现 - 适配微信支付SDK
public class WechatPayAdapter implements PaymentAdapter {
    private final WechatPaySdk sdk;

    public WechatPayAdapter(WechatPaySdk sdk) {
        this.sdk = sdk;
    }

    @Override
    public PaymentResult charge(Order order) {
        try {
            // 参数转换:业务 Order → SDK 的 PayRequest
            PayRequest req = new PayRequest();
            req.setOutTradeNo(order.getOrderId());
            req.setTotalFee(order.getAmount().multiply(new BigDecimal("100")).intValue());

            // 调用SDK
            PayResponse resp = sdk.charge(req);

            // 结果转换:SDK 的 PayResponse → 业务的 PaymentResult
            return new PaymentResult(resp.getTradeNo(), resp.getStatus(), resp.getErrMsg());
        } catch (WechatPayException e) {
            // 异常包装
            throw new PaymentException("微信支付失败", e);
        }
    }
}

看到没?适配层把SDK的丑陋细节全部包住了。业务层调 paymentAdapter.charge(order) 的时候,根本不用管底层是微信还是支付宝。切换SDK?只需要写一个新的适配类,改一下依赖注入的配置就完事了。

我曾经犯过的错:刚开始做适配层时,我为了让代码看起来“专业”,在适配器里加了大量 if-else 逻辑,判断不同款型SDK的不同版本。结果适配层变得比业务层还复杂,维护成本飙升。后来我强制要求:一个适配器只适配一个版本的SDK,版本升级就写新的适配器,用策略模式去选择。别想着一个适配器通吃,那是灾难。

用一张图看清整体结构

下面这张图展示了适配层在整体SDK改造架构中的位置,以及它如何连接业务层和底层SDK。你重点看中间的矩形组:适配层拆成了「接口契约」和「适配实现」。业务层只依赖接口契约,适配实现负责和具体SDK打交道。

适配层架构概览 业务层 调用适配器接口 处理业务逻辑 传入领域对象 依赖接口不依赖实现 适配层:接口契约 PaymentAdapter 接口 适配层:适配器实现 WeChatPayAdapter AlipayAdapter SDK 接口层 微信支付 SDK 支付宝 SDK API 签名不同 数据结构不同 关键说明: • 业务层只看到 PaymentAdapter 接口,不知道具体是哪个实现。 • 适配实现内部完成参数转换、异常包装、结果映射。 • 切换 SDK 只需新增一个适配实现,修改依赖注入配置,业务层零改动。

适配层设计中的几个权衡点

最后聊三个实操中容易犹豫的地方,也都是我掉过坑之后总结出来的:

  • 适配层要不要处理重试? 我个人建议只在适配层做协议级别的重试(网络超时、500错误)。业务级别的重试(比如余额不足、风控拦截)交给业务层。适配层一旦搞混了,调试起来特别痛苦。
  • 适配接口粒度怎么控制? 太粗了不好复用,太细了接口爆炸。我一般遵循「业务场景粒度」,比如一个支付、一个查询、一个退款就够了。别把SDK的每个子方法都暴露出来。
  • 单元测试怎么覆盖适配层? 对象适配器天然适合 Mock。把 SDK 实例用 Mock 框架注入,测试适配逻辑是否正确。我习惯写三套用例:正常转换、SDK抛异常、参数边界值。这三套跑通了,适配层基本稳了。

一个小建议:如果你发现适配层代码里有一大堆 try-catch,而且 catch 块都在做不同的转换,那很可能是接口粒度没控制好。试着把异常映射逻辑抽到独立的 ErrorMapper 类里,适配器只关心调用和回传结果。

好了,以上就是我这些年做适配层设计积累下来的核心经验。分层、解耦、适配器模式——这三块组合起来基本能搞定90%的SDK改造场景。剩下的10%,等你真遇到了再说。