👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中… 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
🎵 当你的天空突然下了大雨,那是我在为你炸乌云
一、入门
什么是适配器模式?
适配器模式是Java中常用的结构型设计模式,它的核心作用就像现实中的电源转换器一样—让原本不兼容的两个接口能够协同工作。
为什么要用适配器模式?
假设我们需要在电商系统中接入两种第三方支付:
- 支付宝支付:方法名为
aliPay(String orderId, BigDecimal amount)
- 微信支付:方法名为
wechatPay(int merchantId, String orderNo, double money)
痛点:两个支付接口方法名、参数类型、参数顺序完全不同,导致业务代码中支付逻辑混乱。
// 支付宝SDK(参数:订单ID + 金额)
class AliPaySDK {
public boolean aliPay(String orderId, BigDecimal amount) {
System.out.println("支付宝支付成功:" + orderId + " 金额:" + amount);
return true;
}
}
// 微信支付SDK(参数:商户ID + 订单号 + 金额)
class WechatPaySDK {
public boolean wechatPay(int merchantId, String orderNo, double money) {
System.out.println("微信支付成功:商户" + merchantId + " 订单:" + orderNo + " 金额:" + money);
return true;
}
}
如何实现适配器模式?
适配器模式(Adapter)包含以下主要角色:
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
【案例】三方支付 - 改
目标接口:UnifiedPayment
类。当前系统业务所期待的接口,它可以是抽象类或接口。
interface UnifiedPayment {
/**
* 统一支付方法
* @param unifiedOrder 包含商户ID、订单号、金额等
*/
boolean pay(UnifiedOrder unifiedOrder);
}
// 统一订单参数对象
class UnifiedOrder {
private int merchantId;
private String orderId;
private BigDecimal amount;
// 构造方法、getters省略
}
适配器:AliPayAdapter
类和WechatPayAdapter
类
适配者:AliPaySDK
类和WechatPaySDK
类
// 支付宝适配器
class AliPayAdapter implements UnifiedPayment {
private AliPaySDK aliPay = new AliPaySDK();
@Override
public boolean pay(UnifiedOrder order) {
// 参数转换与适配逻辑
return aliPay.aliPay(
order.getOrderId(), // 直接使用统一订单号
order.getAmount() // 金额类型匹配
);
}
}
// 微信支付适配器
class WechatPayAdapter implements UnifiedPayment {
private WechatPaySDK wechatPay = new WechatPaySDK();
@Override
public boolean pay(UnifiedOrder order) {
// 微信特殊处理逻辑
String wechatOrderNo = "WX_" + order.getOrderId();
return wechatPay.wechatPay(
order.getMerchantId(), // 从统一参数获取商户ID
wechatOrderNo, // 转换订单号格式
order.getAmount().doubleValue() // BigDecimal转double
);
}
}
改造后业务层
public class PaymentService {
private Map<PayType, UnifiedPayment> adapters = new HashMap<>();
public PaymentService() {
adapters.put(PayType.ALI_PAY, new AliPayAdapter());
adapters.put(PayType.WECHAT_PAY, new WechatPayAdapter());
}
public boolean pay(PayType type, UnifiedOrder order) {
return adapters.get(type).pay(order); // 统一调用
}
}
⚠注意:适配器这层不做业务逻辑校验
适配器这层不做业务逻辑校验,如下所示
class WechatPayAdapter implements UnifiedPayment {
// 错误!适配器不应包含业务判断
public boolean pay(UnifiedOrder order) {
if (order.amount > 10000) { // 业务规则不应在这里
throw new Exception("超额限制");
}
// ...转换逻辑
}
}
二、适配器模式在框架源码中的运用
Java I/O 的 InputStreamReader
InputStreamReader
将字节流(InputStream
)转换为字符流(Reader
),解决字节流与字符流接口不兼容问题。
- 目标接口:Reader
- 适配器:InputStreamReader
- 适配者:InputStream
public class InputStreamReader extends Reader {
private final StreamDecoder sd; // 关键适配器
public InputStreamReader(InputStream in) {
this(in, Charset.defaultCharset());
}
// 通过StreamDecoder适配字节→字符
public int read() throws IOException {
return sd.read();
}
}
Spring MVC 的 HandlerAdapter
Spring 要支持多种 Controller
类型(如 @Controller
、HttpRequestHandler
、Servlet
等),但不同 Controller
的请求处理方式不同。
目标接口:HandlerAdapter
接口
public interface HandlerAdapter {
boolean supports(Object handler);
ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception;
}
适配器:RequestMappingHandlerAdapter
类和SimpleServletHandlerAdapter
类。
// 具体适配器示例:处理注解式Controller
public class RequestMappingHandlerAdapter implements HandlerAdapter {
public boolean supports(Object handler) {
return (handler instanceof HandlerMethod);
}
public ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 实际调用被注解的Controller方法
HandlerMethod handlerMethod = (HandlerMethod) handler;
return invokeHandlerMethod(request, response, handlerMethod);
}
}
// 另一个适配器:处理Servlet
public class SimpleServletHandlerAdapter implements HandlerAdapter {
public boolean supports(Object handler) {
return (handler instanceof Servlet);
}
public ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
((Servlet) handler).service(request, response);
return null;
}
}
适配者:我们用户实现的接口。
// 注解驱动的 Controller
@Controller
public class MyController {
@RequestMapping("/hello")
public String hello() {
return "hello";
}
}
// 基于接口的 Controller:
public class MyHttpRequestHandler implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) {
// 处理请求
}
}
// 传统的 Servlet
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
// 处理请求
}
}
三、总结
适配器模式的优点
- 解耦性:
- 将客户端与具体实现分离,客户端只依赖目标接口,无需关心底层实现。
- 例如:Spring MVC 的
DispatcherServlet
只依赖HandlerAdapter
接口,不关心具体Controller
的实现。
- 复用性:
- 可以复用现有的类或第三方库,而无需修改其源码。
- 例如:通过
InputStreamReader
复用InputStream
的功能。
- 扩展性:
- 新增适配器即可支持新的实现,符合开闭原则。
- 例如:Spring MVC 新增一种
Controller
类型时,只需添加对应的HandlerAdapter
。
- 透明性:
○ 客户端无需感知适配器的存在,调用方式与普通接口一致。
○ 例如:使用 SLF4J 日志框架时,无需关心底层是Logback
还是Log4j
。
适配器模式的缺点
- 增加复杂性:
- 引入适配器会增加类的数量,可能导致系统复杂度上升。
- 例如:一个系统中有多种数据格式转换时,可能需要多个适配器。
- 性能开销:
- 适配器通常需要额外的逻辑(如数据转换),可能带来一定的性能损耗。
- 例如:InputStreamReader 在字节到字符的转换过程中会有解码开销。
- 过度使用问题:
- 如果滥用适配器模式,可能导致系统层次过多,难以维护。
- 例如:在简单的场景中,直接修改接口可能比引入适配器更合适。
适配器模式的适用场景
- 整合遗留代码
- 场景:系统中存在老旧的模块或第三方库,无法直接修改其源码。
- 示例:将老式的 XML 数据接口适配为新的 JSON 接口。
- 统一接口规范
- 场景:需要将多个不同的实现统一为相同的接口。
- 示例:Spring MVC 的 HandlerAdapter 统一处理多种 Controller 类型。
- 兼容不同版本
- 场景:系统升级时,需要同时支持新旧版本的接口。
- 示例:API 升级时,通过适配器兼容旧版客户端。
- 跨平台或跨系统集成
- 场景:需要与外部系统对接,但接口规范不一致。
- 示例:将微信支付的接口适配为统一的支付接口。
- 数据格式转换
- 场景:需要将一种数据格式转换为另一种格式。
- 示例:InputStreamReader 将字节流转换为字符流。