一、从一个问题开始
假设你正在开发一个支付系统,系统中已经有一个支付宝支付接口正常工作:
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 对象适配器
| 对比项 | 类适配器 | 对象适配器 |
|---|---|---|
| 实现方式 | 继承被适配者 | 组合被适配者 |
| 灵活性 | 低(只能适配一个类) | 高(可以适配多个类) |
| 代码复杂度 | 简单 | 稍复杂 |
| 能否适配子类 | 不能 | 能(多态) |
| 推荐程度 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
为什么推荐对象适配器?
- 组合优于继承:更灵活,不会受继承限制
- 可以适配一个类的所有子类:只要传入子类实例即可
- 更符合开闭原则:不需要为每个被适配者创建子类
六、实战场景
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适配WindowListenerKeyAdapter适配KeyListenerMouseAdapter适配MouseListener
八、常见面试题
Q1:适配器模式和装饰器模式的区别?
| 对比 | 适配器模式 | 装饰器模式 |
|---|---|---|
| 目的 | 转换接口,让不兼容的类能一起工作 | 动态添加功能,不改变接口 |
| 接口变化 | 改变原有接口 | 保持原有接口 |
| 典型场景 | 对接第三方库、老系统改造 | 增强对象功能、AOP |
Q2:适配器模式和外观模式的区别?
| 对比 | 适配器模式 | 外观模式 |
|---|---|---|
| 目的 | 转换接口 | 简化接口 |
| 粒度 | 单个或多个类 | 整个子系统 |
| 意图 | 让接口变得兼容 | 让接口变得简单 |
Q3:什么时候用适配器模式?
✅ 适合的场景:
- 需要使用一个已有的类,但它的接口不符合需求
- 需要统一多个类的接口,但这些类没有共同的父类
- 对接第三方库,防止未来库升级导致代码大面积修改
- 老系统改造,逐步替换旧接口
❌ 不适合的场景:
- 可以修改原有类的接口(直接改更简单)
- 接口设计初期(应该统一设计,而不是事后适配)
Q4:适配器模式的缺点是什么?
- 增加系统复杂度(多了一层转换)
- 过多的适配器会让系统结构混乱
- 性能有微小损耗(多一次方法调用)
九、总结与速记卡
核心要点
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(目标接口)