适配器模式如何用于老接口兼容和系统迁移?
简化版
老接口兼容中,适配器可以把旧接口包装成新接口,或者把新实现包装成旧接口,让调用方在迁移过程中不用一次性全部修改。它常用于渐进式重构、第三方替换、接口升级和新旧系统并行。
详细版
系统迁移最怕“一刀切”。如果新接口上线后要求所有调用方同时改造,风险很高。适配器模式可以在新旧接口之间加一层转换:
- 旧系统能力还在,新增适配器实现新接口;
- 新系统已经完成,但老调用方仍调用旧接口,新增反向适配器兼容旧接口;
- 多个历史版本接口并存,通过适配层统一到内部标准模型;
- 第三方供应商切换时,业务层继续依赖统一接口。
适配器的关键是隔离变化。调用方看到的是稳定接口,迁移差异集中在适配层。这样可以按业务线、流量比例、版本逐步迁移。
但要注意:适配器只能解决接口形态差异,不能掩盖业务语义差异。如果新旧接口语义不一致,需要在迁移设计中明确兼容规则。
完整版教学
一、老接口迁移为什么容易出问题
老系统通常有很多调用方:
订单系统 -> 老库存接口
促销系统 -> 老库存接口
客服系统 -> 老库存接口
报表系统 -> 老库存接口
如果新库存接口上线后,要求所有系统立刻改调用方式,风险很大:
- 调用方太多;
- 改造周期不同;
- 测试范围扩大;
- 回滚困难;
- 新旧数据语义可能不同。
适配器模式适合用来降低这种迁移风险。
二、旧能力适配成新接口
假设新接口是:
interface StockService {
boolean lockStock(String skuId, int count);
}
但旧系统只有:
class OldStockClient {
String freeze(String itemCode, String num) {
return "SUCCESS";
}
}
可以写适配器:
class OldStockAdapter implements StockService {
private final OldStockClient oldClient;
public boolean lockStock(String skuId, int count) {
String result = oldClient.freeze(skuId, String.valueOf(count));
return "SUCCESS".equals(result);
}
}
这样新业务先面向 StockService,底层暂时仍然调用旧系统。
三、新实现兼容旧接口
有时情况相反:新系统已经上线,但还有老调用方只认识旧接口。
这时可以写反向适配器,让老调用方暂时不改代码:
老调用方 -> 旧接口适配器 -> 新库存服务
这种方式给调用方争取迁移时间,避免所有系统同一天切换。
四、适配层适合做版本隔离
接口升级时经常出现多个版本:
V1 请求模型
V2 请求模型
V3 请求模型
内部系统最好不要到处处理这些版本差异。可以在入口适配层把不同版本转换成内部统一模型:
V1Adapter -> InternalCommand
V2Adapter -> InternalCommand
V3Adapter -> InternalCommand
业务核心只处理 InternalCommand,版本差异留在边界层。
五、适配器帮助渐进式重构
渐进式重构常见做法是:
- 定义新的目标接口;
- 让旧实现通过适配器实现目标接口;
- 新代码只依赖目标接口;
- 逐步替换底层旧实现;
- 调用方迁移完成后删除旧适配器。
这种路线比一次性大改安全得多。
六、适配器不能掩盖语义差异
如果旧接口的 freeze 是“冻结库存”,新接口的 lockStock 是“锁定并扣减库存”,两者语义并不等价。
这种情况下,适配器里强行转换会埋雷。正确做法是明确语义差异:
- 是否需要补充调用;
- 是否只能兼容部分场景;
- 是否需要降级处理;
- 是否需要新字段;
- 是否需要数据校验。
接口形态能适配,业务语义不能随便糊。
七、双写迁移与可撤销切换
迁移 100 个调用点时先让 Adapter 保持旧接口,按 10%→50%→100% 流量切新实现;每阶段比较成功率和结果差异。
old callers -> compatibility adapter -> old/new implementation via flag
适配器的验收标准不是“调用成功”,而是 Target 的业务语义在转换前后保持成立。参数、单位、时区、精度、空值、错误码和资源所有权都应逐项形成契约样例,否则最危险的错误会以“成功响应”的形式潜伏。
八、语义边界与契约样例验证
| 检查维度 | 应确认的内容 |
|---|---|
| 机制正确性 | 适配器支持渐进迁移的价值,在于把调用方改造和实现切换拆成可回滚步骤。 |
| 适用边界 | 适配层应有下线计划和监控,不能无限叠加 v1→v2→v3 多层转换,最终没人理解真实语义。 |
| 测试证据 | 注入 Adaptee 测试替身,断言外部请求、返回映射、异常转换和调用次数 |
| 工程代价 | 重点统计字段映射与对象分配成本、外部模型变更影响面,以及多层版本适配造成的认知债务 |
易错点:适配器支持渐进迁移的价值,在于把调用方改造和实现切换拆成可回滚步骤。
九、常见误区与追问
- 误区:加了适配器后旧接口可以永久不治理。 长期兼容会积累模型和语义债务,应统计调用量并设置弃用与删除节点。
- 误区:适配器只需要让方法签名能够编译。 真正的适配还包括单位、时区、错误码、空值、资源释放和幂等语义;签名一致不代表行为兼容。
- 追问:新旧结果不一致怎么灰度? 影子调用或双读比较但只采信一个结果,记录差异,确认副作用接口不能盲目双写。
- 追问:适配器应该放在哪一层? 通常放在系统边界或防腐层,让业务层只依赖 Target,避免第三方模型和异常向内扩散。
- 追问:适配器测试为什么需要契约样例? 因为类型相同仍可能单位、精度或错误语义不同;契约样例能证明转换后的行为而不只是编译通过。
十、加强记忆
适配器用于老接口兼容时,可以记成“新旧系统中间搭桥”。旧能力可以适配成新接口,新实现也可以反向兼容旧接口,适合渐进式迁移和版本隔离。关键是把差异关在适配层,同时警惕新旧接口业务语义不一致。