适配器模式:让不兼容的接口握手言和

说实话,我做了十几年软件架构,最常遇到的一个问题就是——新系统要接入老模块,结果接口对不上。就像你买了个新手机,充电线却是老式的Micro-USB,这时候你需要一个转接头。在软件世界里,这个转接头就是适配器模式。

适配器模式,说白了就是让两个原本不兼容的接口能够协同工作。它不改变现有代码,而是在中间加一层转换。我当年接手一个遗留系统时,就靠这个模式救了一命——老系统的数据接口是XML,新系统只认JSON,中间加个适配器,两边都不用改,完美。

定义与核心思想

适配器模式(Adapter Pattern)属于结构型设计模式。它的定义是:将一个类的接口转换成客户期望的另一个接口。让原本因接口不匹配而无法一起工作的类能够协同工作。

你想想看,这个模式其实很生活化。你去国外旅游,插座是两孔圆头,你的充电器是三孔扁头,这时候你需要一个转换插头。适配器模式就是软件里的转换插头。

核心角色:

  • 目标接口(Target):客户期望的接口
  • 适配者(Adaptee):需要被适配的现有接口
  • 适配器(Adapter):核心转换器,连接Target和Adaptee
  • 客户(Client):使用目标接口的调用方

两种实现方式

适配器模式有两种实现方式:类适配器和对象适配器。我个人习惯优先使用对象适配器,原因后面会讲。但两种都得掌握,毕竟面试常考。

类适配器(基于继承)

类适配器通过多重继承实现。适配器同时继承目标接口和适配者类。Java不支持多重继承,所以类适配器在Java里用得少,但在C++中很常见。

// 目标接口
public interface ITarget {
    void request();
}

// 适配者类(已有代码,接口不兼容)
public class Adaptee {
    public void specificRequest() {
        System.out.println("适配者:我是老接口,干的是specificRequest的活");
    }
}

// 类适配器:继承适配者,实现目标接口
public class ClassAdapter extends Adaptee implements ITarget {
    @Override
    public void request() {
        // 在request方法里调用specificRequest
        System.out.println("类适配器:我来转接");
        super.specificRequest();
    }
}

// 客户端使用
public class Client {
    public static void main(String[] args) {
        ITarget target = new ClassAdapter();
        target.request();  // 客户端只认识request,实际调用的是specificRequest
    }
}

嗯,这里要注意:类适配器因为继承是静态的,所以适配器只能适配一个Adaptee类,不能适配其子类。灵活性差了点。

对象适配器(基于组合)

对象适配器通过组合实现。适配器持有适配者的引用,在目标接口方法中委托给适配者。这是我最推荐的方式。

// 目标接口
public interface ITarget {
    void request();
}

// 适配者类(不变)
public class Adaptee {
    public void specificRequest() {
        System.out.println("适配者:老接口干活了");
    }
}

// 对象适配器:持有适配者引用,实现目标接口
public class ObjectAdapter implements ITarget {
    private Adaptee adaptee;  // 组合方式
    
    public ObjectAdapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }
    
    @Override
    public void request() {
        System.out.println("对象适配器:我来转接");
        adaptee.specificRequest();  // 委托给适配者
    }
}

// 客户端使用
public class Client {
    public static void main(String[] args) {
        Adaptee adaptee = new Adaptee();
        ITarget target = new ObjectAdapter(adaptee);
        target.request();
    }
}

我的经验:对象适配器更灵活。它可以适配Adaptee及其所有子类,因为持有的是父类引用。而且组合比继承更符合「优先使用组合而非继承」的设计原则。我在项目中几乎只用对象适配器。

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

适配器模式结构图 Client ITarget + request() Adapter - adaptee: Adaptee + request() Adaptee + specificRequest() 使用 实现 持有引用 Client通过ITarget接口调用,Adapter将request()转换为specificRequest() Adaptee无需任何修改,所有转换逻辑都在Adapter中完成

优缺点分析

维度 类适配器 对象适配器
实现方式 继承(多重继承) 组合(持有引用)
灵活性 低,只能适配一个Adaptee类 高,可适配Adaptee及其子类
代码侵入 需要继承Adaptee,可能暴露父类方法 完全封装Adaptee,不暴露内部细节
适用语言 C++等多继承语言 所有面向对象语言
推荐度 ⭐(不推荐) ⭐⭐⭐⭐⭐(强烈推荐)

避坑指南:我曾经在一个项目中用了类适配器,结果Adaptee的父类方法被意外暴露,导致客户端直接调用了不该调的方法,出了线上bug。从那以后,我坚持用对象适配器。组合比继承安全得多。

应用场景

适配器模式不是万能的,但以下几个场景特别适合:

  1. 系统升级改造:老系统的接口不能改,新系统要接入。加个适配器,两边都满意。
  2. 第三方库集成:第三方库的接口和你系统的接口不一致。适配器做翻译官。
  3. 统一多个相似接口:多个类做类似的事,但接口不同。适配器统一成一套接口。
  4. 数据格式转换:XML转JSON,CSV转Excel。适配器做数据转换。

举个例子,我做过一个支付系统。对接了微信支付、支付宝、银联,三家接口完全不同。我定义了一个统一的支付接口,然后为每家写一个适配器。客户端只认我的接口,底层随便换支付渠道。这就是适配器模式的经典应用。

什么时候别用适配器?

  • 如果接口差异太大,适配器会变得很复杂,不如重构。
  • 如果Adaptee的接口不稳定经常变,适配器也得跟着改,维护成本高。
  • 如果只是临时用一下,适配器可能过度设计,直接改代码更简单。

总结

适配器模式,说白了就是软件世界的转接头。它不改变现有代码,而是在中间加一层转换。我个人强烈推荐对象适配器,灵活、安全、符合设计原则。

你想想看,实际项目中我们经常要面对「改不了」的代码——老系统、第三方库、同事写的烂代码。适配器模式给了我们一个优雅的解决方案:不改它,包一层。这就是适配器模式的精髓。

最后提醒一句:适配器模式解决的是接口不兼容问题,不是设计不合理问题。如果接口本身设计就有问题,别硬用适配器,该重构就重构。


公众号:蓝海资料掘金营,微信deep3321