设计模式:适配器模式

欢迎你来读这篇博客,这篇博客主要是关于适配器模式
其中包括适配器模式的核心思想、适用场景、类适配器、对象适配器、接口适配器,以及 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)) {
// 调支付宝 SDK
}

if ("WX_PAY".equals(channel)) {
// 调微信支付 SDK
}

这会让业务代码直接依赖外部 SDK,系统会越来越乱。

适配器模式就是为了解决这种“接口不兼容”的问题。

它的核心思想是:

在旧接口和新接口之间加一层适配器,把一个类的接口转换成客户端期望的另一个接口。

生活中的例子也很直观。

你的电脑是 Type-C 接口,投影仪是 HDMI 接口,二者不能直接连接,于是你需要一个转接头。

这个转接头不会改变电脑,也不会改变投影仪,但它能让它们协同工作。

适配器模式就是代码世界里的“转接头”。

正文

chapter 1:什么是适配器模式

适配器模式,英文是 Adapter Pattern,属于结构型设计模式。

它的定义是:

将一个类的接口转换成客户端希望的另一个接口,使原本由于接口不兼容而不能一起工作的类可以协同工作。

简单说:

适配器模式就是把不兼容的接口包装成统一接口。

它通常包含三个角色:

  1. Target 目标接口:客户端期望使用的接口。
  2. Adaptee 被适配者:已经存在的类或接口,但它不符合客户端期望。
  3. 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:适配器模式的核心思想

适配器模式不是为了“创造新功能”,而是为了“转换接口”。

它一般做这几件事:

  1. 参数转换;
  2. 返回值转换;
  3. 异常转换;
  4. 状态码转换;
  5. 方法名转换;
  6. 调用流程适配;
  7. 新旧系统兼容;
  8. 第三方 SDK 屏蔽。

例如:

1
PaymentResult pay(PaymentRequest request);

这是系统内部统一接口。

支付宝 SDK 可能返回:

1
AliPayResponse

微信支付 SDK 可能返回:

1
WxPayResponse

适配器要做的事情就是把它们统一转换成:

1
PaymentResult

这样业务层就不需要知道底层到底是支付宝还是微信支付。

chapter 4:适配器模式的三种常见形式

适配器模式常见有三种形式:

  1. 类适配器;
  2. 对象适配器;
  3. 接口适配器。

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());
}
}

这个适配器做了三件事:

  1. 把系统内部 PaymentRequest 转换成支付宝 AliPayTradeRequest
  2. 调用支付宝 SDK;
  3. 把支付宝 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());
}
}

微信适配器和支付宝适配器实现了同一个目标接口:

1
PaymentClient

但是内部调用完全不同的 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:适配器里应该放什么逻辑

适配器不是业务服务,它应该主要负责“转换”和“屏蔽差异”。

适配器中适合放:

  1. 请求参数转换;
  2. 响应结果转换;
  3. 外部状态码转换;
  4. 外部异常转换;
  5. 第三方 SDK 调用;
  6. 外部字段命名映射;
  7. 外部单位转换;
  8. 协议格式转换。

例如金额单位转换:

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
PaymentResult

而不是让业务层直接处理:

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.

启示录

富贵岂由人,时会高志须酬。

能成功于千载者,必以近察远。


设计模式:适配器模式
https://allendericdalexander.github.io/2026/04/01/java/design/07adapter-pattern-blog/
作者
AtLuoFu
发布于
2026年4月1日
许可协议