适配器模式:让新老系统“握手言和”
适配器模式,说白了就是“转接头”。
你想想看,我们平时用手机充电,Type-C 接口想插老旧的 USB-A 口,怎么办?加个转接头。适配器模式干的就是这个活——把一个类的接口,转换成客户期望的另一个接口。
我在项目中遇到过好几次这种场景。最典型的就是对接第三方支付,还有老系统日志改造。今天我们就拿这两个真实案例,把适配器模式聊透。
为什么需要适配器?
先问个问题:你手头有个老系统,日志用的是 log4j 1.x。现在公司统一要求迁移到 logback。你怎么办?
改所有业务代码?那得改几百个文件,风险极高。不改?架构评审过不去。
适配器模式就是来救场的。它让你在不修改原有类的前提下,通过一个中间层完成接口转换。
核心思想:客户只认目标接口,适配器负责把源接口“翻译”成目标接口。
适配器模式的三种角色
- 目标接口(Target):客户期望的接口。比如我们公司内部定义的支付接口。
- 源接口(Adaptee):需要被适配的现有接口。比如支付宝 SDK 的支付方法。
- 适配器(Adapter):核心类,负责把源接口转换成目标接口。
我习惯把适配器分成两类:类适配器和对象适配器。前者用继承,后者用组合。我个人更推荐对象适配器,因为组合比继承灵活,也符合“组合优于继承”的设计原则。
SVG 结构图:适配器模式核心逻辑
实战案例一:统一第三方支付接口
我记得有一次,公司要同时接入支付宝和微信支付。两个 SDK 的接口完全不同:
- 支付宝:
alipay.trade.pay(AlipayRequest req) - 微信支付:
wxpay.unifiedOrder(WxPayRequest req)
业务方不想关心这些差异。他们只想要一个统一的 pay(Order order) 方法。
好,适配器上场。
第一步:定义目标接口
// 公司内部统一支付接口
public interface UnifiedPay {
PayResult pay(Order order);
PayResult refund(Order order);
}
第二步:实现支付宝适配器
public class AlipayAdapter implements UnifiedPay {
private AlipayClient alipayClient; // 支付宝SDK客户端
public AlipayAdapter(AlipayClient client) {
this.alipayClient = client;
}
@Override
public PayResult pay(Order order) {
// 将内部Order转换为支付宝的AlipayRequest
AlipayRequest req = convertToAlipayRequest(order);
// 调用支付宝SDK
AlipayResponse resp = alipayClient.execute(req);
// 将支付宝响应转换为统一PayResult
return convertToPayResult(resp);
}
// 转换逻辑...
}
第三步:实现微信支付适配器
public class WechatPayAdapter implements UnifiedPay {
private WxPayClient wxPayClient;
public WechatPayAdapter(WxPayClient client) {
this.wxPayClient = client;
}
@Override
public PayResult pay(Order order) {
WxPayRequest req = convertToWxRequest(order);
WxPayResponse resp = wxPayClient.unifiedOrder(req);
return convertToPayResult(resp);
}
}
我的经验:适配器里最容易出 bug 的地方是参数转换。支付宝和微信对金额、时间格式的要求不一样。我建议在适配器里写单元测试,专门验证转换逻辑。
实战案例二:老版本日志接口适配
这个场景更常见。老系统用 Logger.log(String msg),新框架要求 Logger.info(String msg)、Logger.error(String msg)。
你不可能去改所有业务代码。写个适配器,一劳永逸。
// 老版本日志接口
public interface OldLogger {
void log(String message);
}
// 新日志框架接口
public interface NewLogger {
void info(String message);
void error(String message);
void warn(String message);
}
// 适配器:将老接口适配到新框架
public class LoggerAdapter implements OldLogger {
private NewLogger newLogger;
public LoggerAdapter(NewLogger newLogger) {
this.newLogger = newLogger;
}
@Override
public void log(String message) {
// 默认将老接口的log映射到info级别
newLogger.info(message);
}
}
你看,业务代码里还是调用 logger.log("xxx"),但底层已经悄悄换成了新框架。这就是适配器的魅力——对调用方完全透明。
避坑指南:我曾经在一个项目里,直接把老接口的 log 方法全部映射到新框架的 info 级别。结果线上发现大量 error 级别的日志被当成 info 打印了,排查问题差点崩溃。后来我加了一个日志级别检测逻辑,根据消息前缀或关键字动态路由到对应的新接口方法。
适配器模式 vs 装饰器模式
很多初学者容易把这两个模式搞混。我简单说下区别:
| 对比维度 | 适配器模式 | 装饰器模式 |
|---|---|---|
| 核心目的 | 接口转换 | 功能增强 |
| 是否改变接口 | 是,完全改变 | 否,保持原接口 |
| 典型场景 | 对接第三方、老系统改造 | 加缓存、加日志、加权限 |
| 对客户透明性 | 客户感知到新接口 | 客户无感知 |
说白了,适配器是“改头换面”,装饰器是“锦上添花”。
什么时候用适配器模式?
- 你想复用现有的类,但它的接口和你的系统不匹配
- 你想创建一个可复用的类,让它和多个不相关的类协同工作
- 你需要接入多个第三方 SDK,且它们接口各不相同
- 老系统迁移,不想大规模改动已有代码
我的建议:适配器模式虽然简单,但别滥用。如果只是两个类之间接口不匹配,写个适配器没问题。但如果系统里到处都是适配器,说明你的架构设计可能有问题——接口抽象层级不够。
总结
适配器模式,说白了就是“中间人”。它让原本不兼容的接口能够协同工作,而且对调用方完全透明。
我个人觉得,适配器模式是设计模式里最“实用”的一个。因为它解决的是真实世界中每天都在发生的问题——新老系统对接、第三方集成、架构迁移。
嗯,记住一点:适配器不是银弹。它能帮你解决接口不匹配的问题,但解决不了架构设计本身的缺陷。该重构的时候,别偷懒。
公众号:蓝海资料掘金营,微信deep3321