Skip to content

适配器模式 (Adapter Pattern)

一、定义

一句话概括:将一个类的接口转换成客户端期望的另一个接口,使原本不兼容的类能够一起工作。

官方定义(GoF):Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.

适配器模式就像现实生活中的电源适配器——中国的插座是 220V 三孔,美国的笔记本是 110V 两孔,中间需要一个"转接头"来适配。在软件工程中,适配器模式起到同样的"桥梁"作用。


二、解决的问题

2.1 什么场景下需要适配器模式?

  • 集成遗留系统:旧系统提供了稳定的功能,但其接口与新系统不兼容,不能修改旧代码。
  • 第三方库封装:引入第三方 SDK 时,其接口风格与项目现有代码风格不一致,需要统一适配。
  • 多数据源统一:系统需要同时对接 MySQL、Redis、Elasticsearch 等多种数据源,客户端希望使用统一接口。
  • 接口升级兼容:系统升级后,旧的 API 调用方仍需要兼容,但新接口签名已变化。

2.2 不用适配器模式会有什么问题?

问题1:代码耦合 —— 客户端代码直接依赖第三方接口,切换成本高
问题2:代码重复 —— 每个调用方都要自己写转换逻辑,大量重复代码
问题3:难以维护 —— 第三方库升级后接口变化,所有调用方都需要修改
问题4:不符合开闭原则 —— 无法在不修改客户端代码的情况下扩展新接口

三、结构

3.1 文字描述

适配器模式包含以下角色:

  • Target(目标接口):客户端期望使用的接口。
  • Adaptee(被适配者):已经存在的、需要被适配的类或接口。
  • Adapter(适配器):实现 Target 接口,内部持有 Adaptee 引用,将 Target 的调用转换为 Adaptee 的调用。

3.2 对象适配器(推荐)

┌─────────────────┐          ┌─────────────────────┐
│    <<interface>> │          │      Adaptee         │
│      Target      │          ├─────────────────────┤
├─────────────────┤          │ + specificRequest()  │
│ + request()      │          └──────────┬──────────┘
└────────┬────────┘                     │
         │                              │  adaptee
         │              ┌───────────────┴───────────┐
         │              │         Adapter            │
         │              ├───────────────────────────┤
         └─────────────>│ - adaptee: Adaptee         │
                        │ + request()                │
                        │    adaptee.specificRequest()│
                        └───────────────────────────┘

3.3 类适配器(多重继承)

┌─────────────────┐          ┌─────────────────────┐
│    <<interface>> │          │      Adaptee         │
│      Target      │          ├─────────────────────┤
├─────────────────┤          │ + specificRequest()  │
│ + request()      │          └──────────┬──────────┘
└────────┬────────┘                     │
         │          ┌───────────────────┴───────────┐
         │          │     Adapter (extends Adaptee)  │
         └─────────>│ + request()                    │
                    │   this.specificRequest()        │
                    └────────────────────────────────┘

四、代码实现

4.1 基础实现

场景描述

假设我们有一个老旧的日志系统(LegacyLogger),它的接口是 logMessage(String msg)。现在新系统希望使用统一的 Logger 接口,该接口定义了 info()error()debug() 等方法。

java
// ============ Target:客户端期望的接口 ============
interface Logger {
    void info(String message);
    void error(String message);
    void debug(String message);
}

// ============ Adaptee:已有的老旧日志实现 ============
class LegacyLogger {
    public void logMessage(String level, String message) {
        System.out.println("[" + level + "] " + message);
    }
}

// ============ Adapter:对象适配器 ============
class LoggerAdapter implements Logger {
    private LegacyLogger legacyLogger;

    public LoggerAdapter(LegacyLogger legacyLogger) {
        this.legacyLogger = legacyLogger;
    }

    @Override
    public void info(String message) {
        legacyLogger.logMessage("INFO", message);
    }

    @Override
    public void error(String message) {
        legacyLogger.logMessage("ERROR", message);
    }

    @Override
    public void debug(String message) {
        legacyLogger.logMessage("DEBUG", message);
    }
}

// ============ 客户端测试 ============
public class AdapterDemo {
    public static void main(String[] args) {
        LegacyLogger legacy = new LegacyLogger();
        Logger logger = new LoggerAdapter(legacy);

        logger.info("用户登录成功");
        logger.error("数据库连接失败");
        logger.debug("查询参数: userId=123");
    }
}

4.2 进阶实现

4.2.1 类适配器 vs 对象适配器

对象适配器(推荐):使用组合方式持有 Adaptee 引用。

java
// 对象适配器 —— 灵活,可以适配 Adaptee 及其子类
class ObjectAdapter implements Target {
    private Adaptee adaptee;    // 组合

    public ObjectAdapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void request() {
        adaptee.specificRequest();  // 委托调用
    }
}

类适配器:通过继承 Adaptee 并实现 Target 接口。

java
// 类适配器 —— 只能适配一个具体类,但可以重写 Adaptee 的行为
class ClassAdapter extends Adaptee implements Target {
    @Override
    public void request() {
        super.specificRequest();    // 直接调用父类方法
    }
}

// 对比:
// 对象适配器优点:可以适配一个类及其所有子类,更灵活,符合"组合优于继承"
// 类适配器优点:  不需要重新实现整个 Adaptee,可以重写部分行为
// 结论:Java 中推荐使用对象适配器(因为 Java 不支持多继承)

4.2.2 双向适配器

当两个系统需要互相调用,但接口不兼容时,可以使用双向适配器。

java
// 新系统接口
interface NewService {
    String fetchData(String key);
}

// 旧系统接口
interface OldService {
    String getRecord(int id);
}

// 双向适配器
class TwoWayAdapter implements NewService, OldService {
    private NewService newService;
    private OldService oldService;

    public TwoWayAdapter(NewService newService, OldService oldService) {
        this.newService = newService;
        this.oldService = oldService;
    }

    // 实现 NewService,调用 OldService
    @Override
    public String fetchData(String key) {
        int id = Integer.parseInt(key);
        return oldService.getRecord(id);
    }

    // 实现 OldService,调用 NewService
    @Override
    public String getRecord(int id) {
        return newService.fetchData(String.valueOf(id));
    }
}

4.2.3 默认适配器(接口适配器)

当一个接口方法过多,而我们只想实现其中一部分时,可以先用一个抽象类空实现所有方法(默认适配器),再用子类重写需要的方法。

java
// 大量方法的接口
interface MouseListener {
    void onClick();
    void onDoubleClick();
    void onRightClick();
    void onDrag();
    void onDrop();
    void onHover();
}

// 默认适配器:空实现所有方法
abstract class MouseAdapter implements MouseListener {
    @Override public void onClick() {}
    @Override public void onDoubleClick() {}
    @Override public void onRightClick() {}
    @Override public void onDrag() {}
    @Override public void onDrop() {}
    @Override public void onHover() {}
}

// 客户端只需重写关心的方法
class MyMouseHandler extends MouseAdapter {
    @Override
    public void onClick() {
        System.out.println("鼠标点击!");
    }
}

4.3 生产级实现

Spring Boot 多支付渠道适配

java
// ============ 统一支付接口(Target)============
public interface PaymentService {
    PaymentResult pay(PaymentRequest request);
    PaymentResult refund(String orderId);
    PaymentStatus queryStatus(String orderId);
}

// ============ 支付宝 SDK(Adaptee 1)============
class AlipaySDK {
    public String createOrder(String amount, String subject) {
        System.out.println("支付宝创建订单: " + amount + " - " + subject);
        return "ALIPAY_" + System.currentTimeMillis();
    }

    public boolean doRefund(String tradeNo) {
        System.out.println("支付宝退款: " + tradeNo);
        return true;
    }

    public String queryTrade(String tradeNo) {
        return "SUCCESS";
    }
}

// ============ 微信支付 SDK(Adaptee 2)============
class WechatPaySDK {
    public String unifiedOrder(int totalFee, String body) {
        System.out.println("微信统一下单: " + totalFee + " - " + body);
        return "WX_" + System.currentTimeMillis();
    }

    public boolean refund(String outTradeNo) {
        System.out.println("微信退款: " + outTradeNo);
        return true;
    }

    public String orderQuery(String outTradeNo) {
        return "SUCCESS";
    }
}

// ============ 适配器 ============
@Component
class AlipayAdapter implements PaymentService {
    private final AlipaySDK alipaySDK;

    public AlipayAdapter(AlipaySDK alipaySDK) {
        this.alipaySDK = alipaySDK;
    }

    @Override
    public PaymentResult pay(PaymentRequest request) {
        String tradeNo = alipaySDK.createOrder(
            request.getAmount().toString(), request.getSubject());
        return new PaymentResult(true, tradeNo);
    }

    @Override
    public PaymentResult refund(String orderId) {
        boolean success = alipaySDK.doRefund(orderId);
        return new PaymentResult(success, orderId);
    }

    @Override
    public PaymentStatus queryStatus(String orderId) {
        String status = alipaySDK.queryTrade(orderId);
        return PaymentStatus.valueOf(status);
    }
}

@Component
class WechatPayAdapter implements PaymentService {
    private final WechatPaySDK wechatPaySDK;

    public WechatPayAdapter(WechatPaySDK wechatPaySDK) {
        this.wechatPaySDK = wechatPaySDK;
    }

    @Override
    public PaymentResult pay(PaymentRequest request) {
        String tradeNo = wechatPaySDK.unifiedOrder(
            request.getAmount().intValue(), request.getSubject());
        return new PaymentResult(true, tradeNo);
    }

    @Override
    public PaymentResult refund(String orderId) {
        boolean success = wechatPaySDK.refund(orderId);
        return new PaymentResult(success, orderId);
    }

    @Override
    public PaymentStatus queryStatus(String orderId) {
        String status = wechatPaySDK.orderQuery(orderId);
        return PaymentStatus.valueOf(status);
    }
}

// ============ 辅助类 ============
class PaymentRequest {
    private BigDecimal amount;
    private String subject;
    // getters & setters...
    public BigDecimal getAmount() { return amount; }
    public String getSubject() { return subject; }
}

class PaymentResult {
    private boolean success;
    private String tradeNo;
    public PaymentResult(boolean success, String tradeNo) {
        this.success = success;
        this.tradeNo = tradeNo;
    }
}

enum PaymentStatus { SUCCESS, FAIL, PROCESSING }

// ============ 策略模式选择适配器 ============
@Service
class PaymentGateway {
    private final Map<String, PaymentService> paymentMap;

    // Spring 自动注入所有 PaymentService 实现
    public PaymentGateway(List<PaymentService> paymentServices) {
        paymentMap = new HashMap<>();
        for (PaymentService ps : paymentServices) {
            String name = ps.getClass().getSimpleName();
            paymentMap.put(name, ps);
        }
    }

    public PaymentResult pay(String channel, PaymentRequest request) {
        PaymentService service = paymentMap.get(channel + "Adapter");
        if (service == null) {
            throw new IllegalArgumentException("不支持的支付渠道: " + channel);
        }
        return service.pay(request);
    }
}

五、优缺点

优点

优点说明
解耦将客户端与具体实现分离,客户端只依赖目标接口
复用无需修改现有代码即可复用已有功能
灵活性可以随时替换被适配者,不影响客户端
符合开闭原则新增适配器无需修改现有代码

缺点

缺点说明
复杂度增加引入额外的类和接口,系统复杂度上升
性能开销多一层间接调用,有轻微性能损失
过度设计风险对于简单场景,直接修改被适配者可能更高效

六、适用场景

  1. 系统集成:对接第三方服务(支付、短信、推送),统一内部接口。
  2. 遗留系统改造:老系统接口与新系统不兼容,不能修改老代码。
  3. 数据格式转换:不同数据源(XML/JSON/CSV)需要统一为相同格式输出。
  4. 多版本兼容:同一个功能需要支持多个版本的 API。
  5. 硬件驱动适配:不同厂商的硬件设备需要统一的操作接口。

七、JDK / Spring 框架中的实际应用

7.1 JDK 中的适配器

java
// 1. Arrays.asList() —— 数组到 List 的适配
String[] arr = {"a", "b", "c"};
List<String> list = Arrays.asList(arr);  // 数组适配为 List

// 2. InputStreamReader —— 字节流到字符流的适配
InputStream byteStream = new FileInputStream("file.txt");
Reader charStream = new InputStreamReader(byteStream, "UTF-8");

// 3. Collections.enumeration() / Collections.list() —— 双向适配
Enumeration<String> enumeration = Collections.enumeration(list);
List<String> backToList = Collections.list(enumeration);

7.2 Spring 中的适配器

java
// 1. HandlerAdapter —— Spring MVC 的处理器适配器
// DispatcherServlet 通过 HandlerAdapter 适配各种 Controller
// HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter 等

// 2. JpaVendorAdapter —— JPA 厂商适配
// HibernateJpaVendorAdapter、EclipseLinkJpaVendorAdapter 等

// 3. AdvisorAdapter —— AOP 通知适配器
// 将不同类型的 Advice 适配为统一的 MethodInterceptor

八、与其他模式的关系

模式关系
桥接模式桥接是预先设计(分离抽象与实现),适配器是事后补救(接口不兼容)。桥接更强调"独立变化",适配器更强调"兼容转换"。
装饰器模式装饰器不改变接口,只增强功能;适配器改变接口,不改变功能。
外观模式外观定义新接口简化子系统;适配器复用已有接口进行转换。
代理模式代理和原对象实现相同接口;适配器和目标对象接口不同。
策略模式常与适配器组合——策略选择不同适配器,实现多渠道统一调用。

九、面试常见问题

Q1:类适配器和对象适配器有什么区别?各有什么优缺点?

  • 类适配器使用继承,直接继承 Adaptee 并实现 Target 接口。优点是可以重写 Adaptee 的行为,不需要重新实现整个 Adaptee;缺点是只能适配一个具体类(Java 单继承限制)。
  • 对象适配器使用组合,持有 Adaptee 引用。优点是更灵活,可以适配一个类及其所有子类,符合"组合优于继承"原则;缺点是不能重写 Adaptee 的行为。
  • Java 中推荐对象适配器

Q2:适配器模式和装饰器模式有什么区别?

  • 目的不同:适配器改变接口,让不兼容的接口协同工作;装饰器不改变接口,给对象动态添加新功能。
  • 关注点不同:适配器关注"接口转换";装饰器关注"功能增强"。
  • 典型例子InputStreamReader 是适配器(字节→字符);BufferedInputStream 是装饰器(给字节流加缓冲)。

Q3:适配器模式和外观模式有什么区别?

  • 适配器用于复用已有接口,将一个接口转换为另一个接口。
  • 外观模式用于简化接口,为复杂子系统提供统一的高层接口。
  • 适配器通常在事后使用(补救),外观模式通常在事前设计(规划)。

Q4:如何在 Spring Boot 中实现多支付渠道的适配器模式?

:定义统一的 PaymentService 接口,为每个支付渠道(支付宝、微信、银联)创建独立的 Adapter 实现类。通过 @Component 注册,利用 Spring 的依赖注入将所有实现收集到 Map<String, PaymentService> 中,根据渠道名称动态选择对应的适配器。

Q5:双向适配器是什么?什么场景使用?

:双向适配器同时实现两个接口,让两个系统可以互相调用。典型场景是两个遗留系统需要集成,但各自的接口不兼容,且双方都不能修改。双向适配器通过两种实现方式(implements A, B)将两个方向的调用相互转换。