欢迎你来读这篇博客,这篇博客主要是关于适配器模式。
其中包括适配器模式的核心思想、适用场景、类适配器、对象适配器、接口适配器,以及 Java 后端开发中第三方支付系统适配的完整案例。
序言
在软件开发中,我们经常会遇到一种很现实的问题:
我想使用一个已有功能,但它提供的接口和我当前系统需要的接口不一样。
比如你的系统内部定义了统一的支付接口:
1
| PaymentResult pay(PaymentRequest request);
|
但是第三方支付 SDK 提供的方法可能是:
1
| AliPayResponse execute(AliPayTradeRequest request);
|
另一个支付平台又是:
1
| WxPayResult unifiedOrder(WxOrderParam param);
|
业务层当然不希望到处写:
1 2 3 4 5 6 7
| if ("ALI_PAY".equals(channel)) { }
if ("WX_PAY".equals(channel)) { }
|
这会让业务代码直接依赖外部 SDK,系统会越来越乱。
适配器模式就是为了解决这种“接口不兼容”的问题。
它的核心思想是:
在旧接口和新接口之间加一层适配器,把一个类的接口转换成客户端期望的另一个接口。
生活中的例子也很直观。
你的电脑是 Type-C 接口,投影仪是 HDMI 接口,二者不能直接连接,于是你需要一个转接头。
这个转接头不会改变电脑,也不会改变投影仪,但它能让它们协同工作。
适配器模式就是代码世界里的“转接头”。
正文
chapter 1:什么是适配器模式
适配器模式,英文是 Adapter Pattern,属于结构型设计模式。
它的定义是:
将一个类的接口转换成客户端希望的另一个接口,使原本由于接口不兼容而不能一起工作的类可以协同工作。
简单说:
适配器模式就是把不兼容的接口包装成统一接口。
它通常包含三个角色:
- Target 目标接口:客户端期望使用的接口。
- Adaptee 被适配者:已经存在的类或接口,但它不符合客户端期望。
- Adapter 适配器:实现目标接口,并在内部调用被适配者。
结构如下:
1 2 3 4 5 6 7
| Client │ ▼ Target Interface ▲ │ Adapter ─────► Adaptee
|
客户端只依赖 Target,不用关心底层 Adaptee 是什么。
chapter 2:为什么需要适配器模式
适配器模式通常用于系统集成。
只要你接过第三方系统,大概率就遇到过适配器模式。
例如:
- 接入支付宝、微信支付、Stripe;
- 接入阿里云 OSS、腾讯云 COS、MinIO;
- 接入短信服务商;
- 接入物流平台;
- 接入不同数据库驱动;
- 接入不同消息中间件;
- 老系统接口改造;
- 新旧 API 兼容;
- 第三方 SDK 封装。
这些外部系统通常都有自己的接口风格。
如果业务层直接调用它们,会出现几个问题:
- 业务代码和第三方 SDK 强耦合;
- 第三方 SDK 替换成本高;
- 多个平台接口风格不统一;
- 测试困难;
- 异常、返回值、状态码难以统一;
- 外部系统细节污染核心业务代码。
适配器模式的作用就是:
把外部系统的不统一,关在适配层里。
业务层看到的是统一接口。
适配层负责处理不同平台之间的差异。
chapter 3:适配器模式的核心思想
适配器模式不是为了“创造新功能”,而是为了“转换接口”。
它一般做这几件事:
- 参数转换;
- 返回值转换;
- 异常转换;
- 状态码转换;
- 方法名转换;
- 调用流程适配;
- 新旧系统兼容;
- 第三方 SDK 屏蔽。
例如:
1
| PaymentResult pay(PaymentRequest request);
|
这是系统内部统一接口。
支付宝 SDK 可能返回:
微信支付 SDK 可能返回:
适配器要做的事情就是把它们统一转换成:
这样业务层就不需要知道底层到底是支付宝还是微信支付。
chapter 4:适配器模式的三种常见形式
适配器模式常见有三种形式:
- 类适配器;
- 对象适配器;
- 接口适配器。
1. 类适配器
类适配器通过继承被适配类,并实现目标接口来完成适配。
1
| Adapter extends Adaptee implements Target
|
2. 对象适配器
对象适配器通过组合被适配对象,并实现目标接口来完成适配。
1 2
| Adapter implements Target └── has a Adaptee
|
3. 接口适配器
接口适配器通常用于接口方法太多,而实现类只想关注其中一部分方法的场景。
可以提供一个抽象默认适配类,给接口方法提供默认实现,子类只覆盖需要的方法。
在 Java 8 之后,接口默认方法也能承担一部分接口适配器的角色。
chapter 5:类适配器
先看类适配器。
假设已有一个旧系统接口:
1 2 3 4 5 6
| public class LegacyUserService {
public String findUserNameById(Long userId) { return "legacy-user-" + userId; } }
|
新系统希望使用统一接口:
1 2 3 4
| public interface UserClient {
String getUsername(Long userId); }
|
这两个接口不兼容。
可以使用类适配器:
1 2 3 4 5 6 7
| public class UserClientClassAdapter extends LegacyUserService implements UserClient {
@Override public String getUsername(Long userId) { return findUserNameById(userId); } }
|
客户端调用:
1 2 3 4 5 6 7 8 9 10
| public class Client {
public static void main(String[] args) { UserClient userClient = new UserClientClassAdapter();
String username = userClient.getUsername(1001L);
System.out.println(username); } }
|
类适配器的优点
- 实现简单;
- 可以直接复用父类能力;
- 不需要额外持有被适配对象。
类适配器的缺点
- 依赖继承;
- Java 不支持多继承;
- 如果被适配类是
final,无法继承;
- 适配器和被适配类耦合较强;
- 不够灵活。
所以在真实 Java 项目中,类适配器用得相对少。
更常用的是对象适配器。
chapter 6:对象适配器
对象适配器通过组合实现适配。
仍然使用刚才的例子。
旧系统类:
1 2 3 4 5 6
| public class LegacyUserService {
public String findUserNameById(Long userId) { return "legacy-user-" + userId; } }
|
目标接口:
1 2 3 4
| public interface UserClient {
String getUsername(Long userId); }
|
对象适配器:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class UserClientObjectAdapter implements UserClient {
private final LegacyUserService legacyUserService;
public UserClientObjectAdapter(LegacyUserService legacyUserService) { this.legacyUserService = legacyUserService; }
@Override public String getUsername(Long userId) { return legacyUserService.findUserNameById(userId); } }
|
客户端调用:
1 2 3 4 5 6 7 8 9 10 11 12
| public class Client {
public static void main(String[] args) { LegacyUserService legacyUserService = new LegacyUserService();
UserClient userClient = new UserClientObjectAdapter(legacyUserService);
String username = userClient.getUsername(1001L);
System.out.println(username); } }
|
对象适配器更加灵活,因为它使用组合而不是继承。
这也符合设计原则中的一条重要经验:
多用组合,少用继承。
chapter 7:接口适配器
接口适配器适合接口方法很多,但实现类不想实现所有方法的场景。
假设系统中有一个消息监听接口:
1 2 3 4 5 6 7 8 9 10
| public interface MessageListener {
void onConnect();
void onMessage(String message);
void onError(Exception exception);
void onClose(); }
|
如果每个实现类都要实现所有方法,会很烦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class OrderMessageListener implements MessageListener {
@Override public void onConnect() { }
@Override public void onMessage(String message) { System.out.println("处理订单消息:" + message); }
@Override public void onError(Exception exception) { }
@Override public void onClose() { } }
|
这里真正关心的只有 onMessage()。
可以定义一个抽象适配器类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public abstract class MessageListenerAdapter implements MessageListener {
@Override public void onConnect() { }
@Override public void onMessage(String message) { }
@Override public void onError(Exception exception) { }
@Override public void onClose() { } }
|
业务类只覆盖需要的方法:
1 2 3 4 5 6 7
| public class OrderMessageListener extends MessageListenerAdapter {
@Override public void onMessage(String message) { System.out.println("处理订单消息:" + message); } }
|
Java 8 之后,也可以直接在接口中提供默认方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public interface MessageListener {
default void onConnect() { }
default void onMessage(String message) { }
default void onError(Exception exception) { }
default void onClose() { } }
|
这样实现类也只需要覆盖关心的方法。
chapter 8:案例背景:统一支付适配
下面用一个贴近 Java 后端开发的案例来讲适配器模式。
假设我们正在开发一个订单系统,需要支持多种支付渠道:
- 支付宝;
- 微信支付;
- 银行卡支付;
- 后续可能接入 Stripe。
业务层希望只依赖一个统一支付接口:
1
| PaymentResult pay(PaymentRequest request);
|
但是第三方 SDK 的接口完全不一样。
支付宝 SDK:
1
| AliPayResponse tradePay(AliPayTradeRequest request);
|
微信支付 SDK:
1
| WxPayResponse unifiedOrder(WxPayOrderRequest request);
|
如果业务层直接调用这些 SDK,会被第三方接口绑死。
所以我们需要定义统一接口,并为每个第三方平台写适配器。
chapter 9:定义系统内部统一模型
先定义支付请求对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import java.math.BigDecimal;
public class PaymentRequest {
private String orderNo;
private BigDecimal amount;
private String subject;
private String userId;
public PaymentRequest(String orderNo, BigDecimal amount, String subject, String userId) { this.orderNo = orderNo; this.amount = amount; this.subject = subject; this.userId = userId; }
public String getOrderNo() { return orderNo; }
public BigDecimal getAmount() { return amount; }
public String getSubject() { return subject; }
public String getUserId() { return userId; } }
|
再定义支付结果对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public class PaymentResult {
private final boolean success;
private final String paymentNo;
private final String message;
private final String rawCode;
private PaymentResult(boolean success, String paymentNo, String message, String rawCode) { this.success = success; this.paymentNo = paymentNo; this.message = message; this.rawCode = rawCode; }
public static PaymentResult success(String paymentNo, String message, String rawCode) { return new PaymentResult(true, paymentNo, message, rawCode); }
public static PaymentResult failure(String message, String rawCode) { return new PaymentResult(false, null, message, rawCode); }
public boolean isSuccess() { return success; }
public String getPaymentNo() { return paymentNo; }
public String getMessage() { return message; }
public String getRawCode() { return rawCode; } }
|
定义统一支付客户端接口。
1 2 3 4 5 6
| public interface PaymentClient {
String channel();
PaymentResult pay(PaymentRequest request); }
|
这里的 PaymentClient 就是适配器模式中的 Target 目标接口。
业务层只依赖它。
chapter 10:模拟支付宝 SDK
假设支付宝 SDK 是第三方提供的,我们不能修改它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import java.math.BigDecimal;
public class AliPayTradeRequest {
private String outTradeNo;
private BigDecimal totalAmount;
private String subject;
private String buyerId;
public String getOutTradeNo() { return outTradeNo; }
public void setOutTradeNo(String outTradeNo) { this.outTradeNo = outTradeNo; }
public BigDecimal getTotalAmount() { return totalAmount; }
public void setTotalAmount(BigDecimal totalAmount) { this.totalAmount = totalAmount; }
public String getSubject() { return subject; }
public void setSubject(String subject) { this.subject = subject; }
public String getBuyerId() { return buyerId; }
public void setBuyerId(String buyerId) { this.buyerId = buyerId; } }
|
支付宝返回对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class AliPayResponse {
private String code;
private String tradeNo;
private String msg;
public AliPayResponse(String code, String tradeNo, String msg) { this.code = code; this.tradeNo = tradeNo; this.msg = msg; }
public String getCode() { return code; }
public String getTradeNo() { return tradeNo; }
public String getMsg() { return msg; } }
|
支付宝 SDK 客户端:
1 2 3 4 5 6 7 8
| public class AliPaySdkClient {
public AliPayResponse tradePay(AliPayTradeRequest request) { System.out.println("调用支付宝 SDK,下单号:" + request.getOutTradeNo());
return new AliPayResponse("10000", "ALI202604010001", "支付成功"); } }
|
注意,支付宝 SDK 的方法名、请求对象、返回对象都和我们系统内部接口不同。
这就是典型的接口不兼容。
chapter 11:编写支付宝适配器
支付宝适配器实现系统统一接口 PaymentClient,内部调用支付宝 SDK。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| public class AliPayClientAdapter implements PaymentClient {
private final AliPaySdkClient aliPaySdkClient;
public AliPayClientAdapter(AliPaySdkClient aliPaySdkClient) { this.aliPaySdkClient = aliPaySdkClient; }
@Override public String channel() { return "ALI_PAY"; }
@Override public PaymentResult pay(PaymentRequest request) { AliPayTradeRequest aliPayRequest = convertRequest(request);
AliPayResponse response = aliPaySdkClient.tradePay(aliPayRequest);
return convertResponse(response); }
private AliPayTradeRequest convertRequest(PaymentRequest request) { AliPayTradeRequest aliPayRequest = new AliPayTradeRequest(); aliPayRequest.setOutTradeNo(request.getOrderNo()); aliPayRequest.setTotalAmount(request.getAmount()); aliPayRequest.setSubject(request.getSubject()); aliPayRequest.setBuyerId(request.getUserId());
return aliPayRequest; }
private PaymentResult convertResponse(AliPayResponse response) { if ("10000".equals(response.getCode())) { return PaymentResult.success( response.getTradeNo(), response.getMsg(), response.getCode() ); }
return PaymentResult.failure(response.getMsg(), response.getCode()); } }
|
这个适配器做了三件事:
- 把系统内部
PaymentRequest 转换成支付宝 AliPayTradeRequest;
- 调用支付宝 SDK;
- 把支付宝
AliPayResponse 转换成系统统一 PaymentResult。
业务层不需要知道支付宝 SDK 的细节。
chapter 12:模拟微信支付 SDK
再模拟一个微信支付 SDK。
微信请求对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import java.math.BigDecimal;
public class WxPayOrderRequest {
private String orderId;
private BigDecimal money;
private String description;
private String openId;
public String getOrderId() { return orderId; }
public void setOrderId(String orderId) { this.orderId = orderId; }
public BigDecimal getMoney() { return money; }
public void setMoney(BigDecimal money) { this.money = money; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getOpenId() { return openId; }
public void setOpenId(String openId) { this.openId = openId; } }
|
微信返回对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class WxPayResponse {
private String returnCode;
private String transactionId;
private String returnMsg;
public WxPayResponse(String returnCode, String transactionId, String returnMsg) { this.returnCode = returnCode; this.transactionId = transactionId; this.returnMsg = returnMsg; }
public String getReturnCode() { return returnCode; }
public String getTransactionId() { return transactionId; }
public String getReturnMsg() { return returnMsg; } }
|
微信 SDK 客户端:
1 2 3 4 5 6 7 8
| public class WxPaySdkClient {
public WxPayResponse unifiedOrder(WxPayOrderRequest request) { System.out.println("调用微信支付 SDK,下单号:" + request.getOrderId());
return new WxPayResponse("SUCCESS", "WX202604010001", "支付成功"); } }
|
chapter 13:编写微信支付适配器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| public class WxPayClientAdapter implements PaymentClient {
private final WxPaySdkClient wxPaySdkClient;
public WxPayClientAdapter(WxPaySdkClient wxPaySdkClient) { this.wxPaySdkClient = wxPaySdkClient; }
@Override public String channel() { return "WX_PAY"; }
@Override public PaymentResult pay(PaymentRequest request) { WxPayOrderRequest wxPayRequest = convertRequest(request);
WxPayResponse response = wxPaySdkClient.unifiedOrder(wxPayRequest);
return convertResponse(response); }
private WxPayOrderRequest convertRequest(PaymentRequest request) { WxPayOrderRequest wxPayRequest = new WxPayOrderRequest(); wxPayRequest.setOrderId(request.getOrderNo()); wxPayRequest.setMoney(request.getAmount()); wxPayRequest.setDescription(request.getSubject()); wxPayRequest.setOpenId(request.getUserId());
return wxPayRequest; }
private PaymentResult convertResponse(WxPayResponse response) { if ("SUCCESS".equals(response.getReturnCode())) { return PaymentResult.success( response.getTransactionId(), response.getReturnMsg(), response.getReturnCode() ); }
return PaymentResult.failure(response.getReturnMsg(), response.getReturnCode()); } }
|
微信适配器和支付宝适配器实现了同一个目标接口:
但是内部调用完全不同的 SDK。
这就是适配器模式的意义。
chapter 14:业务层如何使用统一接口
定义支付服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import java.util.List; import java.util.Map; import java.util.stream.Collectors;
public class PaymentService {
private final Map<String, PaymentClient> paymentClientMap;
public PaymentService(List<PaymentClient> paymentClients) { this.paymentClientMap = paymentClients.stream() .collect(Collectors.toUnmodifiableMap(PaymentClient::channel, client -> client)); }
public PaymentResult pay(String channel, PaymentRequest request) { PaymentClient paymentClient = paymentClientMap.get(channel);
if (paymentClient == null) { throw new IllegalArgumentException("Unsupported payment channel: " + channel); }
return paymentClient.pay(request); } }
|
客户端调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import java.math.BigDecimal; import java.util.List;
public class Client {
public static void main(String[] args) { PaymentClient aliPayClient = new AliPayClientAdapter(new AliPaySdkClient()); PaymentClient wxPayClient = new WxPayClientAdapter(new WxPaySdkClient());
PaymentService paymentService = new PaymentService(List.of(aliPayClient, wxPayClient));
PaymentRequest request = new PaymentRequest( "ORDER202604010001", new BigDecimal("99.00"), "设计模式课程", "USER1001" );
PaymentResult result = paymentService.pay("ALI_PAY", request);
System.out.println("支付是否成功:" + result.isSuccess()); System.out.println("支付流水号:" + result.getPaymentNo()); System.out.println("支付消息:" + result.getMessage()); } }
|
业务层只关心:
1
| paymentService.pay("ALI_PAY", request);
|
至于底层是支付宝、微信,还是未来的 Stripe,业务层不需要关心。
如果后续新增 Stripe,只需要新增:
StripeSdkClient
StripePayClientAdapter
然后注册到 PaymentService 即可。
chapter 15:在 Spring Boot 中落地
在 Spring Boot 项目中,适配器通常会被注册成 Bean。
目标接口:
1 2 3 4 5 6
| public interface PaymentClient {
String channel();
PaymentResult pay(PaymentRequest request); }
|
支付宝适配器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import org.springframework.stereotype.Component;
@Component public class AliPayClientAdapter implements PaymentClient {
private final AliPaySdkClient aliPaySdkClient;
public AliPayClientAdapter(AliPaySdkClient aliPaySdkClient) { this.aliPaySdkClient = aliPaySdkClient; }
@Override public String channel() { return "ALI_PAY"; }
@Override public PaymentResult pay(PaymentRequest request) { AliPayTradeRequest aliPayRequest = convertRequest(request);
AliPayResponse response = aliPaySdkClient.tradePay(aliPayRequest);
return convertResponse(response); }
private AliPayTradeRequest convertRequest(PaymentRequest request) { AliPayTradeRequest aliPayRequest = new AliPayTradeRequest(); aliPayRequest.setOutTradeNo(request.getOrderNo()); aliPayRequest.setTotalAmount(request.getAmount()); aliPayRequest.setSubject(request.getSubject()); aliPayRequest.setBuyerId(request.getUserId());
return aliPayRequest; }
private PaymentResult convertResponse(AliPayResponse response) { if ("10000".equals(response.getCode())) { return PaymentResult.success(response.getTradeNo(), response.getMsg(), response.getCode()); }
return PaymentResult.failure(response.getMsg(), response.getCode()); } }
|
微信适配器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import org.springframework.stereotype.Component;
@Component public class WxPayClientAdapter implements PaymentClient {
private final WxPaySdkClient wxPaySdkClient;
public WxPayClientAdapter(WxPaySdkClient wxPaySdkClient) { this.wxPaySdkClient = wxPaySdkClient; }
@Override public String channel() { return "WX_PAY"; }
@Override public PaymentResult pay(PaymentRequest request) { WxPayOrderRequest wxPayRequest = convertRequest(request);
WxPayResponse response = wxPaySdkClient.unifiedOrder(wxPayRequest);
return convertResponse(response); }
private WxPayOrderRequest convertRequest(PaymentRequest request) { WxPayOrderRequest wxPayRequest = new WxPayOrderRequest(); wxPayRequest.setOrderId(request.getOrderNo()); wxPayRequest.setMoney(request.getAmount()); wxPayRequest.setDescription(request.getSubject()); wxPayRequest.setOpenId(request.getUserId());
return wxPayRequest; }
private PaymentResult convertResponse(WxPayResponse response) { if ("SUCCESS".equals(response.getReturnCode())) { return PaymentResult.success(response.getTransactionId(), response.getReturnMsg(), response.getReturnCode()); }
return PaymentResult.failure(response.getReturnMsg(), response.getReturnCode()); } }
|
统一支付服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import org.springframework.stereotype.Service;
import java.util.List; import java.util.Map; import java.util.stream.Collectors;
@Service public class PaymentService {
private final Map<String, PaymentClient> paymentClientMap;
public PaymentService(List<PaymentClient> paymentClients) { this.paymentClientMap = paymentClients.stream() .collect(Collectors.toUnmodifiableMap(PaymentClient::channel, client -> client)); }
public PaymentResult pay(String channel, PaymentRequest request) { PaymentClient paymentClient = paymentClientMap.get(channel);
if (paymentClient == null) { throw new IllegalArgumentException("Unsupported payment channel: " + channel); }
return paymentClient.pay(request); } }
|
这种方式非常适合 Spring Boot 项目。
它结合了:
- 适配器模式;
- 策略模式;
- Spring 依赖注入;
- 面向接口编程;
- 开闭原则。
新增支付渠道时,只要新增一个实现 PaymentClient 的适配器,并交给 Spring 管理即可。
chapter 16:适配器里应该放什么逻辑
适配器不是业务服务,它应该主要负责“转换”和“屏蔽差异”。
适配器中适合放:
- 请求参数转换;
- 响应结果转换;
- 外部状态码转换;
- 外部异常转换;
- 第三方 SDK 调用;
- 外部字段命名映射;
- 外部单位转换;
- 协议格式转换。
例如金额单位转换:
1 2 3
| private Integer convertAmountToCent(BigDecimal amount) { return amount.multiply(new BigDecimal("100")).intValueExact(); }
|
例如异常转换:
1 2 3 4 5 6
| try { AliPayResponse response = aliPaySdkClient.tradePay(aliPayRequest); return convertResponse(response); } catch (AliPayException e) { throw new PaymentException("Call AliPay failed", e); }
|
适配器中不适合放:
- 订单状态流转;
- 库存扣减;
- 优惠券核销;
- 积分发放;
- 复杂业务决策;
- 事务主流程控制。
这些应该放在应用服务或领域服务中。
适配器不是垃圾桶,不要什么都往里扔。它是转接头,不是瑞士军刀。
chapter 17:适配器模式和代理模式的区别
适配器模式和代理模式都可能包装一个对象,但目的不同。
| 对比项 |
适配器模式 |
代理模式 |
| 目的 |
转换接口 |
控制访问或增强能力 |
| 接口是否一致 |
通常不一致 |
通常一致 |
| 关注点 |
兼容已有接口 |
增强或控制原对象 |
| 典型场景 |
第三方 SDK 适配 |
AOP、权限、缓存、远程代理 |
| 示例 |
把 AliPayResponse 转成 PaymentResult |
给 PaymentService 加事务或日志 |
适配器是“接口不一样,我帮你转一下”。
代理是“接口一样,但我在前后加点东西”。
chapter 18:适配器模式和装饰器模式的区别
装饰器模式也会包装对象,但它主要用于动态增强功能。
| 对比项 |
适配器模式 |
装饰器模式 |
| 目的 |
转换接口 |
增强功能 |
| 接口是否一致 |
通常不同 |
通常相同 |
| 关注点 |
让不兼容接口能协作 |
给对象叠加新能力 |
| 示例 |
第三方短信接口适配成 SmsClient |
给 SmsClient 增加限流、日志、重试 |
适配器解决的是“能不能用”。
装饰器解决的是“用的时候加点料”。
chapter 19:适配器模式和门面模式的区别
门面模式也常用于屏蔽复杂系统,但它和适配器模式不一样。
| 对比项 |
适配器模式 |
门面模式 |
| 目的 |
解决接口不兼容 |
简化复杂子系统调用 |
| 是否转换接口 |
是 |
不一定 |
| 被包装对象 |
通常是一个已有类或 SDK |
通常是一组子系统 |
| 典型场景 |
旧接口适配新接口 |
统一封装多个复杂服务 |
适配器像转接头。
门面像服务大厅窗口。
一个解决接口不匹配,一个解决调用太复杂。
chapter 20:适配器模式和桥接模式的区别
适配器模式和桥接模式都能解耦抽象和实现,但使用时机不同。
| 对比项 |
适配器模式 |
桥接模式 |
| 使用时机 |
系统已经存在不兼容接口 |
系统设计初期主动拆分抽象和实现 |
| 目的 |
兼容已有类 |
避免多维度继承爆炸 |
| 关注点 |
事后补救 |
事前设计 |
| 示例 |
老系统接口适配新接口 |
消息类型和发送渠道分离 |
适配器更像“后期补丁”。
桥接更像“前期架构”。
chapter 21:适配器模式的优点
1. 解耦客户端和第三方接口
业务层只依赖系统内部统一接口,不依赖外部 SDK。
2. 提高系统扩展性
新增第三方平台时,只需要新增适配器,不需要大改业务逻辑。
3. 提高复用性
可以复用已有类或旧系统能力,而不用修改原有代码。
4. 符合开闭原则
对新增适配器开放,对已有业务代码修改较少。
5. 屏蔽外部差异
外部系统的字段、状态码、异常、协议都可以在适配层统一处理。
chapter 22:适配器模式的缺点
1. 增加类数量
每接入一个外部系统,通常就要新增一个适配器。
2. 适配层可能变复杂
如果外部接口和内部接口差异很大,适配逻辑会比较复杂。
3. 可能掩盖底层能力差异
统一接口有时候会抹平不同平台的特殊能力。
例如某支付平台支持分账,另一个平台不支持。
如果统一接口设计不好,就会很别扭。
4. 过度适配会导致抽象失真
如果为了统一而统一,把不一样的东西强行抽象成一样,系统会越来越难受。
适配器是用来降低复杂度的,不是用来制造“看起来很统一,实际很痛苦”的抽象幻觉。
chapter 23:适用场景
适配器模式适合以下场景。
1. 接入第三方 SDK
例如:
- 支付 SDK;
- 短信 SDK;
- 云存储 SDK;
- OCR SDK;
- 地图 SDK;
- 物流 SDK。
2. 新旧系统兼容
老系统接口不能改,但新系统希望使用统一接口。
这时可以用适配器包一层。
3. 统一多个外部服务商接口
例如多个短信服务商:
1
| SmsClient.send(SmsRequest request);
|
底层可以适配:
- 阿里云短信;
- 腾讯云短信;
- 华为云短信;
- 极光短信。
4. 框架集成
把某个外部组件适配成系统内部标准接口。
例如:
- 把不同缓存客户端适配成统一 CacheClient;
- 把不同文件存储适配成 FileStorageClient;
- 把不同消息队列适配成 MessageProducer。
5. 历史代码改造
如果已有老代码不能轻易修改,可以写适配器让新代码以新接口调用老代码。
chapter 24:不适合使用的场景
以下场景不建议使用适配器模式。
1. 接口本来就兼容
如果两个接口已经一致,不需要额外加一层适配器。
2. 只是想偷懒隐藏坏设计
有些时候不是接口不兼容,而是抽象设计本身不合理。
这时应该重构接口,而不是不断加适配器补丁。
3. 适配逻辑比业务逻辑还复杂
如果适配器里出现大量业务判断,说明职责边界可能错了。
4. 外部能力差异过大
如果多个平台能力完全不同,强行统一接口可能会导致内部模型非常扭曲。
这种情况下可以考虑分层抽象,或者只抽象共同能力,把特殊能力单独暴露。
chapter 25:真实项目中的实践建议
1. 内部接口要稳定
适配器模式的核心是让业务层依赖内部稳定接口。
例如:
1 2 3 4
| PaymentClient SmsClient FileStorageClient MessageProducer
|
这些接口应该由你的系统定义,而不是直接照搬第三方 SDK。
2. 适配器命名要清晰
推荐命名方式:
1 2 3 4 5
| AliPayClientAdapter WxPayClientAdapter AliyunSmsClientAdapter TencentCosStorageAdapter MinioStorageAdapter
|
看到名字就知道它在适配谁。
3. 请求和响应模型要统一
适配器的价值之一是统一模型。
例如统一返回:
而不是让业务层直接处理:
1 2 3
| AliPayResponse WxPayResponse StripeResponse
|
4. 异常也要统一
不要把第三方异常直接抛到业务层。
建议转换成系统内部异常。
1
| throw new PaymentException("Call AliPay failed", e);
|
这样业务层可以统一处理。
5. 日志要保留外部原始信息
适配器里最好记录第三方请求和响应的关键字段。
例如:
- channel;
- orderNo;
- externalTradeNo;
- rawCode;
- rawMessage;
- costMillis。
这样线上排查问题会轻松很多。
支付问题排查不留外部流水号,就像破案现场不留监控,后面只能靠玄学。
6. 不要泄露第三方模型
业务层不要出现:
1 2
| AliPayTradeRequest WxPayResponse
|
这些应该只存在于适配层。
一旦第三方模型泄露到业务层,适配器的隔离价值就会下降。
7. 配合策略模式使用
在多渠道场景下,适配器经常和策略模式一起使用。
1
| Map<String, PaymentClient>
|
每个适配器既是一个外部接口适配器,也是一种支付策略。
chapter 26:完整案例代码汇总
统一请求对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import java.math.BigDecimal;
public class PaymentRequest {
private String orderNo;
private BigDecimal amount;
private String subject;
private String userId;
public PaymentRequest(String orderNo, BigDecimal amount, String subject, String userId) { this.orderNo = orderNo; this.amount = amount; this.subject = subject; this.userId = userId; }
public String getOrderNo() { return orderNo; }
public BigDecimal getAmount() { return amount; }
public String getSubject() { return subject; }
public String getUserId() { return userId; } }
|
统一返回对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public class PaymentResult {
private final boolean success;
private final String paymentNo;
private final String message;
private final String rawCode;
private PaymentResult(boolean success, String paymentNo, String message, String rawCode) { this.success = success; this.paymentNo = paymentNo; this.message = message; this.rawCode = rawCode; }
public static PaymentResult success(String paymentNo, String message, String rawCode) { return new PaymentResult(true, paymentNo, message, rawCode); }
public static PaymentResult failure(String message, String rawCode) { return new PaymentResult(false, null, message, rawCode); }
public boolean isSuccess() { return success; }
public String getPaymentNo() { return paymentNo; }
public String getMessage() { return message; }
public String getRawCode() { return rawCode; } }
|
统一目标接口
1 2 3 4 5 6
| public interface PaymentClient {
String channel();
PaymentResult pay(PaymentRequest request); }
|
支付宝 SDK 模拟
1 2 3 4 5 6 7 8
| public class AliPaySdkClient {
public AliPayResponse tradePay(AliPayTradeRequest request) { System.out.println("调用支付宝 SDK,下单号:" + request.getOutTradeNo());
return new AliPayResponse("10000", "ALI202604010001", "支付成功"); } }
|
支付宝适配器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class AliPayClientAdapter implements PaymentClient {
private final AliPaySdkClient aliPaySdkClient;
public AliPayClientAdapter(AliPaySdkClient aliPaySdkClient) { this.aliPaySdkClient = aliPaySdkClient; }
@Override public String channel() { return "ALI_PAY"; }
@Override public PaymentResult pay(PaymentRequest request) { AliPayTradeRequest aliPayRequest = new AliPayTradeRequest(); aliPayRequest.setOutTradeNo(request.getOrderNo()); aliPayRequest.setTotalAmount(request.getAmount()); aliPayRequest.setSubject(request.getSubject()); aliPayRequest.setBuyerId(request.getUserId());
AliPayResponse response = aliPaySdkClient.tradePay(aliPayRequest);
if ("10000".equals(response.getCode())) { return PaymentResult.success(response.getTradeNo(), response.getMsg(), response.getCode()); }
return PaymentResult.failure(response.getMsg(), response.getCode()); } }
|
微信 SDK 模拟
1 2 3 4 5 6 7 8
| public class WxPaySdkClient {
public WxPayResponse unifiedOrder(WxPayOrderRequest request) { System.out.println("调用微信支付 SDK,下单号:" + request.getOrderId());
return new WxPayResponse("SUCCESS", "WX202604010001", "支付成功"); } }
|
微信支付适配器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class WxPayClientAdapter implements PaymentClient {
private final WxPaySdkClient wxPaySdkClient;
public WxPayClientAdapter(WxPaySdkClient wxPaySdkClient) { this.wxPaySdkClient = wxPaySdkClient; }
@Override public String channel() { return "WX_PAY"; }
@Override public PaymentResult pay(PaymentRequest request) { WxPayOrderRequest wxPayRequest = new WxPayOrderRequest(); wxPayRequest.setOrderId(request.getOrderNo()); wxPayRequest.setMoney(request.getAmount()); wxPayRequest.setDescription(request.getSubject()); wxPayRequest.setOpenId(request.getUserId());
WxPayResponse response = wxPaySdkClient.unifiedOrder(wxPayRequest);
if ("SUCCESS".equals(response.getReturnCode())) { return PaymentResult.success( response.getTransactionId(), response.getReturnMsg(), response.getReturnCode() ); }
return PaymentResult.failure(response.getReturnMsg(), response.getReturnCode()); } }
|
统一支付服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import java.util.List; import java.util.Map; import java.util.stream.Collectors;
public class PaymentService {
private final Map<String, PaymentClient> paymentClientMap;
public PaymentService(List<PaymentClient> paymentClients) { this.paymentClientMap = paymentClients.stream() .collect(Collectors.toUnmodifiableMap(PaymentClient::channel, client -> client)); }
public PaymentResult pay(String channel, PaymentRequest request) { PaymentClient paymentClient = paymentClientMap.get(channel);
if (paymentClient == null) { throw new IllegalArgumentException("Unsupported payment channel: " + channel); }
return paymentClient.pay(request); } }
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import java.math.BigDecimal; import java.util.List;
public class Client {
public static void main(String[] args) { PaymentClient aliPayClient = new AliPayClientAdapter(new AliPaySdkClient()); PaymentClient wxPayClient = new WxPayClientAdapter(new WxPaySdkClient());
PaymentService paymentService = new PaymentService(List.of(aliPayClient, wxPayClient));
PaymentRequest request = new PaymentRequest( "ORDER202604010001", new BigDecimal("99.00"), "设计模式课程", "USER1001" );
PaymentResult result = paymentService.pay("ALI_PAY", request);
System.out.println("支付是否成功:" + result.isSuccess()); System.out.println("支付流水号:" + result.getPaymentNo()); System.out.println("支付消息:" + result.getMessage()); } }
|
chapter 27:一句话总结
适配器模式的本质是:
把一个已有类或外部系统的接口转换成当前系统期望的接口,让不兼容的接口能够协同工作。
它特别适合第三方 SDK 接入、新旧系统兼容、多平台统一封装等场景。
在 Java 后端开发中,适配器模式最常见的价值是:
- 隔离第三方 SDK;
- 统一请求响应模型;
- 屏蔽外部接口差异;
- 降低业务层耦合;
- 提高系统可扩展性。
如果说工厂模式解决“对象怎么来”,建造者模式解决“复杂对象怎么搭”,原型模式解决“对象怎么复制”,那么适配器模式解决的就是:
你说你的,我说我的,中间找个人翻译一下。
这个“翻译官”,就是适配器。
参考资料
- Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software.
- Robert C. Martin. Agile Software Development, Principles, Patterns, and Practices.
- Joshua Bloch. Effective Java.
- Spring Framework Documentation: Core Technologies - The IoC Container.
- Spring Framework Documentation: ObjectProvider and Bean Injection.
- Refactoring Guru: Adapter Pattern.
- SourceMaking: Adapter Design Pattern.
启示录
富贵岂由人,时会高志须酬。
能成功于千载者,必以近察远。