【设计模式】适配器模式——在不兼容的接口之间搭一座桥

12 阅读7分钟

一、从一个问题开始

假设你正在开发一个支付系统,系统中已经有一个支付宝支付接口正常工作:

java

/**
 * 已有的支付宝支付接口(无法修改)
 */
public class AlipayService {
    public void doPay(String amount, String currency) {
        System.out.println("使用支付宝支付:" + amount + " " + currency);
    }
}

现在,系统需要接入微信支付,但微信的接口长这样:

java

/**
 * 微信支付接口(无法修改)
 */
public class WechatPayService {
    public boolean pay(int amountInFen) {
        System.out.println("使用微信支付:" + amountInFen + "分");
        return true;
    }
}

问题:你的业务代码统一使用这样的接口:

java

public interface Payment {
    void pay(String amount, String currency);
}

// 业务代码
public class OrderService {
    private Payment payment;
    
    public void checkout(String amount, String currency) {
        payment.pay(amount, currency);
    }
}

支付宝可以适配,但微信支付参数和返回值都不一样,怎么办?

适配器模式就是用来解决这个问题的!


二、适配器模式是什么?

一句话定义:将一个类的接口转换成客户期望的另一个接口,让原本不兼容的类可以一起工作。

通俗理解

  • 就像出国旅行用的电源转换插头——中国的两脚插头(已有的接口)到英国的三孔插座(目标接口)中间需要一个转换器
  • 就像读卡器——SD卡(已有的接口)到USB接口(目标接口)中间需要一个适配器

角色结构

text

┌─────────────────┐         ┌─────────────────┐
│   Client        │         │   Target        │
│   (客户端)       │────────▶│   (目标接口)     │
└─────────────────┘         └─────────────────┘
                                    △
                                    │ 实现
                            ┌───────┴───────┐
                            │   Adapter     │
                            │   (适配器)     │
                            └───────┬───────┘
                                    │ 持有
                                    ▼
                            ┌─────────────────┐
                            │   Adaptee       │
                            │   (被适配者)     │
                            └─────────────────┘

三、类适配器模式(继承方式)

通过继承被适配者,实现目标接口。

java

/**
 * 目标接口:统一的支付接口
 */
public interface Payment {
    void pay(String amount, String currency);
}

/**
 * 被适配者1:支付宝服务
 */
public class AlipayService {
    public void doPay(String amount, String currency) {
        System.out.println("使用支付宝支付:" + amount + " " + currency);
    }
}

/**
 * 被适配者2:微信服务
 */
public class WechatPayService {
    public boolean pay(int amountInFen) {
        System.out.println("使用微信支付:" + amountInFen + "分");
        return true;
    }
}

/**
 * 支付宝适配器(类适配器)
 * 继承支付宝服务 + 实现统一接口
 */
public class AlipayAdapter extends AlipayService implements Payment {
    @Override
    public void pay(String amount, String currency) {
        // 参数直接兼容,直接调用父类方法
        super.doPay(amount, currency);
    }
}

/**
 * 微信适配器(类适配器)
 * 继承微信服务 + 实现统一接口
 */
public class WechatAdapter extends WechatPayService implements Payment {
    @Override
    public void pay(String amount, String currency) {
        // 参数转换:元转分,货币转数字
        int amountInFen = convertToFen(amount, currency);
        // 调用父类方法
        super.pay(amountInFen);
    }
    
    private int convertToFen(String amount, String currency) {
        // 简化逻辑:元 * 100 = 分
        double yuan = Double.parseDouble(amount);
        return (int) (yuan * 100);
    }
}

/**
 * 客户端代码
 */
public class Client {
    public static void main(String[] args) {
        OrderService orderService = new OrderService();
        
        // 使用支付宝
        orderService.setPayment(new AlipayAdapter());
        orderService.checkout("100.00", "CNY");
        
        // 使用微信
        orderService.setPayment(new WechatAdapter());
        orderService.checkout("50.00", "CNY");
    }
}

class OrderService {
    private Payment payment;
    
    public void setPayment(Payment payment) {
        this.payment = payment;
    }
    
    public void checkout(String amount, String currency) {
        payment.pay(amount, currency);
    }
}

输出

text

使用支付宝支付:100.00 CNY
使用微信支付:5000

四、对象适配器模式(组合方式)

通过持有被适配者的引用,实现目标接口。这是更推荐的方式,因为组合优于继承。

java

/**
 * 对象适配器:支付宝
 * 持有支付宝对象,实现目标接口
 */
public class AlipayObjectAdapter implements Payment {
    
    private AlipayService alipayService;
    
    public AlipayObjectAdapter(AlipayService alipayService) {
        this.alipayService = alipayService;
    }
    
    @Override
    public void pay(String amount, String currency) {
        alipayService.doPay(amount, currency);
    }
}

/**
 * 对象适配器:微信
 * 持有微信对象,实现目标接口
 */
public class WechatObjectAdapter implements Payment {
    
    private WechatPayService wechatPayService;
    
    public WechatObjectAdapter(WechatPayService wechatPayService) {
        this.wechatPayService = wechatPayService;
    }
    
    @Override
    public void pay(String amount, String currency) {
        int amountInFen = convertToFen(amount, currency);
        wechatPayService.pay(amountInFen);
    }
    
    private int convertToFen(String amount, String currency) {
        double yuan = Double.parseDouble(amount);
        return (int) (yuan * 100);
    }
}

// 使用
AlipayService alipay = new AlipayService();
Payment alipayAdapter = new AlipayObjectAdapter(alipay);
alipayAdapter.pay("100.00", "CNY");

WechatPayService wechat = new WechatPayService();
Payment wechatAdapter = new WechatObjectAdapter(wechat);
wechatAdapter.pay("50.00", "CNY");

五、类适配器 vs 对象适配器

对比项类适配器对象适配器
实现方式继承被适配者组合被适配者
灵活性低(只能适配一个类)高(可以适配多个类)
代码复杂度简单稍复杂
能否适配子类不能能(多态)
推荐程度⭐⭐⭐⭐⭐⭐⭐

为什么推荐对象适配器?

  1. 组合优于继承:更灵活,不会受继承限制
  2. 可以适配一个类的所有子类:只要传入子类实例即可
  3. 更符合开闭原则:不需要为每个被适配者创建子类

六、实战场景

6.1 场景一:老系统接口改造

java

/**
 * 老系统的用户接口(不能改)
 */
public class LegacyUserService {
    public Map<String, Object> getUserById(String id) {
        Map<String, Object> user = new HashMap<>();
        user.put("user_id", id);
        user.put("user_name", "张三");
        user.put("user_age", 25);
        return user;
    }
}

/**
 * 新系统的用户接口
 */
public interface NewUserService {
    UserDTO getUser(String id);
}

public class UserDTO {
    private String id;
    private String name;
    private int age;
    // getter/setter...
}

/**
 * 适配器:老系统 → 新系统
 */
public class LegacyUserAdapter implements NewUserService {
    
    private LegacyUserService legacyService;
    
    public LegacyUserAdapter(LegacyUserService legacyService) {
        this.legacyService = legacyService;
    }
    
    @Override
    public UserDTO getUser(String id) {
        // 调用老系统接口
        Map<String, Object> oldUser = legacyService.getUserById(id);
        
        // 数据转换
        UserDTO newUser = new UserDTO();
        newUser.setId((String) oldUser.get("user_id"));
        newUser.setName((String) oldUser.get("user_name"));
        newUser.setAge((Integer) oldUser.get("user_age"));
        
        return newUser;
    }
}

6.2 场景二:日志框架适配(SLF4J)

SLF4J 就是典型的适配器模式应用,它适配了各种日志框架:

java

// SLF4J 的抽象接口(目标接口)
public interface Logger {
    void info(String msg);
    void error(String msg);
}

// Log4j 的日志实现(被适配者)
public class Log4jLogger {
    public void log(String level, String msg) {
        System.out.println("[Log4j] " + level + ": " + msg);
    }
}

// Log4j 适配器
public class Log4jAdapter implements Logger {
    private Log4jLogger log4j;
    
    public Log4jAdapter(Log4jLogger log4j) {
        this.log4j = log4j;
    }
    
    @Override
    public void info(String msg) {
        log4j.log("INFO", msg);
    }
    
    @Override
    public void error(String msg) {
        log4j.log("ERROR", msg);
    }
}

// 业务代码只依赖 SLF4J 接口
public class BusinessService {
    private Logger logger;  // 依赖抽象,不依赖具体实现
    
    public void doSomething() {
        logger.info("执行业务逻辑");
    }
}

6.3 场景三:Spring MVC 中的 HandlerAdapter

Spring MVC 中的 HandlerAdapter 就是适配器模式的经典应用:

java

// Spring 中的 HandlerAdapter 接口
public interface HandlerAdapter {
    boolean supports(Object handler);
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler);
}

// 适配不同的处理器类型
public class HttpRequestHandlerAdapter implements HandlerAdapter { ... }  // 适配 HttpRequestHandler
public class SimpleControllerHandlerAdapter implements HandlerAdapter { ... } // 适配 Controller
public class RequestMappingHandlerAdapter implements HandlerAdapter { ... }   // 适配 @Controller 方法

七、默认适配器模式

当一个接口有太多方法,而我们只需要实现其中一部分时,可以使用默认适配器

java

/**
 * 监听器接口(方法太多)
 */
public interface MouseListener {
    void onClick();
    void onDoubleClick();
    void onRightClick();
    void onMove();
    void onDrag();
    void onRelease();
}

/**
 * 默认适配器:提供空实现
 */
public abstract class MouseAdapter implements MouseListener {
    @Override public void onClick() {}
    @Override public void onDoubleClick() {}
    @Override public void onRightClick() {}
    @Override public void onMove() {}
    @Override public void onDrag() {}
    @Override public void onRelease() {}
}

/**
 * 使用时只需要重写需要的方法
 */
public class MyMouseListener extends MouseAdapter {
    @Override
    public void onClick() {
        System.out.println("只关心点击事件");
    }
}

Java 中的例子

  • WindowAdapter 适配 WindowListener
  • KeyAdapter 适配 KeyListener
  • MouseAdapter 适配 MouseListener

八、常见面试题

Q1:适配器模式和装饰器模式的区别?

对比适配器模式装饰器模式
目的转换接口,让不兼容的类能一起工作动态添加功能,不改变接口
接口变化改变原有接口保持原有接口
典型场景对接第三方库、老系统改造增强对象功能、AOP

Q2:适配器模式和外观模式的区别?

对比适配器模式外观模式
目的转换接口简化接口
粒度单个或多个类整个子系统
意图让接口变得兼容让接口变得简单

Q3:什么时候用适配器模式?

✅ 适合的场景

  • 需要使用一个已有的类,但它的接口不符合需求
  • 需要统一多个类的接口,但这些类没有共同的父类
  • 对接第三方库,防止未来库升级导致代码大面积修改
  • 老系统改造,逐步替换旧接口

❌ 不适合的场景

  • 可以修改原有类的接口(直接改更简单)
  • 接口设计初期(应该统一设计,而不是事后适配)

Q4:适配器模式的缺点是什么?

  1. 增加系统复杂度(多了一层转换)
  2. 过多的适配器会让系统结构混乱
  3. 性能有微小损耗(多一次方法调用)

九、总结与速记卡

核心要点

text

适配器模式 = 实现目标接口 + 持有/继承被适配者 + 方法转换

代码模板

java

// 对象适配器(推荐)
public class Adapter implements Target {
    private Adaptee adaptee;
    
    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }
    
    @Override
    public void targetMethod() {
        // 转换调用
        adaptee.adapteeMethod();
    }
}

// 类适配器
public class Adapter extends Adaptee implements Target {
    @Override
    public void targetMethod() {
        super.adapteeMethod();
    }
}

一句话记忆

场景方案
接口不兼容,代码不能改适配器模式 ✅
需要对接多个第三方SDK为每个SDK写适配器 ✅
需要统一多个类的接口适配器模式 ✅
接口方法太多,只用几个默认适配器 ✅

现实类比

text

中国插头(两脚) → 转换插头(适配器) → 英国插座(三孔)

Adaptee(被适配者) → Adapter(适配器) → Target(目标接口)