适配器模式:让新老系统“握手言和”

适配器模式,说白了就是“转接头”。

你想想看,我们平时用手机充电,Type-C 接口想插老旧的 USB-A 口,怎么办?加个转接头。适配器模式干的就是这个活——把一个类的接口,转换成客户期望的另一个接口。

我在项目中遇到过好几次这种场景。最典型的就是对接第三方支付,还有老系统日志改造。今天我们就拿这两个真实案例,把适配器模式聊透。

为什么需要适配器?

先问个问题:你手头有个老系统,日志用的是 log4j 1.x。现在公司统一要求迁移到 logback。你怎么办?

改所有业务代码?那得改几百个文件,风险极高。不改?架构评审过不去。

适配器模式就是来救场的。它让你在不修改原有类的前提下,通过一个中间层完成接口转换。

核心思想:客户只认目标接口,适配器负责把源接口“翻译”成目标接口。

适配器模式的三种角色

  • 目标接口(Target):客户期望的接口。比如我们公司内部定义的支付接口。
  • 源接口(Adaptee):需要被适配的现有接口。比如支付宝 SDK 的支付方法。
  • 适配器(Adapter):核心类,负责把源接口转换成目标接口。

我习惯把适配器分成两类:类适配器和对象适配器。前者用继承,后者用组合。我个人更推荐对象适配器,因为组合比继承灵活,也符合“组合优于继承”的设计原则。

SVG 结构图:适配器模式核心逻辑

客户(Client) 目标接口(Target) 适配器(Adapter) 源接口(Adaptee) 调用 实现 持有/继承

实战案例一:统一第三方支付接口

我记得有一次,公司要同时接入支付宝和微信支付。两个 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