第 3 章:适配层架构设计
我们开始聊适配层。很多人觉得SDK改造就是改改接口参数,换个调用方式。说实话,这么想的项目最后基本都翻车了。我在早期的工作中也踩过这种坑——直接把业务逻辑塞到接口转换函数里,结果每次SDK升级都要改十来个地方。
后来我才意识到,适配层的本质是隔离变化。你的业务代码不应该知道底层SDK长什么样,也不应该关心它今天换成了哪家。适配层就是那道防火墙,把不稳定的东西挡在外面。
分层设计思想:先画清楚边界
分层这事儿听起来像老生常谈,但实践起来很多人容易走偏。我习惯从三个维度去切:
- 变化维度:哪些东西容易变?SDK接口签名、数据格式、依赖路径。这些全扔进适配层。
- 复用维度:哪些逻辑会被多个业务方共用?比如认证、重试、日志。这些也应该归适配层管。
- 稳定维度:业务核心逻辑、领域模型、业务流程。它们必须远离底层变化,放在业务层。
拿一个支付SDK改造来举例。你想想看,业务层只需要知道「调起支付,传入金额和订单号」。至于底层用的是微信支付SDK的 WXPay.request 还是支付宝的 AlipayClient.execute,适配层负责翻译。业务层根本不关心。
分层核心原则:上层依赖下层的抽象,不依赖具体实现。业务层只依赖适配层定义的接口,适配层依赖SDK,但要把依赖方向倒过来。
我见过最糟糕的设计,业务层直接引用了SDK的枚举类型。后来SDK版本升级把枚举值从大写改成了小写,业务代码里二十多处 if (status == SDKConstants.SUCCESS) 全炸了。嗯,这就是不分层的代价。
适配层与业务层解耦:怎么做到的?
解耦的核心就三个字:接口隔离。业务层定义自己需要的接口,适配层去实现它。SDK挂掉了?换一个适配实现就行,业务层一行代码都不用改。
具体步骤我一般这样设计:
- 业务层定义目标接口:声明业务需要的功能,比如
PaymentResult pay(String orderId, BigDecimal amount)。 - 适配层实现该接口:内部调用SDK的原生方法,做参数转换、结果映射、异常包装。
- 依赖注入桥接:通过工厂或DI容器将适配实现注入到业务层。业务层只知道接口,不知道具体实现是谁。
记得有次我们对接一个第三方推送SDK,对方的接口命名特别抽象:send(String a, Map。业务方每次调用都要翻文档才记得 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打交道。
适配层设计中的几个权衡点
最后聊三个实操中容易犹豫的地方,也都是我掉过坑之后总结出来的:
- 适配层要不要处理重试? 我个人建议只在适配层做协议级别的重试(网络超时、500错误)。业务级别的重试(比如余额不足、风控拦截)交给业务层。适配层一旦搞混了,调试起来特别痛苦。
- 适配接口粒度怎么控制? 太粗了不好复用,太细了接口爆炸。我一般遵循「业务场景粒度」,比如一个支付、一个查询、一个退款就够了。别把SDK的每个子方法都暴露出来。
- 单元测试怎么覆盖适配层? 对象适配器天然适合 Mock。把 SDK 实例用 Mock 框架注入,测试适配逻辑是否正确。我习惯写三套用例:正常转换、SDK抛异常、参数边界值。这三套跑通了,适配层基本稳了。
一个小建议:如果你发现适配层代码里有一大堆 try-catch,而且 catch 块都在做不同的转换,那很可能是接口粒度没控制好。试着把异常映射逻辑抽到独立的 ErrorMapper 类里,适配器只关心调用和回传结果。
好了,以上就是我这些年做适配层设计积累下来的核心经验。分层、解耦、适配器模式——这三块组合起来基本能搞定90%的SDK改造场景。剩下的10%,等你真遇到了再说。