文章没有视频清晰,推荐大家去某站的生生大佬的手写,讲的很清晰
什么是适配器模式
适配器模式(Adapter Pattern)是一种结构型设计模式,它允许不兼容的接口之间进行协作。简单来说,适配器模式就像是一个"转换器",在两个不兼容的系统之间架起一座桥梁,让它们能够正常通信。
生活中的适配器例子
想象一下这样的场景:你有一台只支持USB接口的电脑,但你的充电器却是Type-C接口。这时候你就需要一个转换器:
Type-C接口 → 转换器 → USB接口
这个转换器就是现实生活中的适配器,它让两个原本不兼容的设备能够正常工作。
适配器模式的核心思想
适配器模式主要包含三个角色:
- 目标接口(Target):客户端期望的接口
- 适配器(Adapter):实现目标接口,并持有被适配者的引用
- 被适配者(Adaptee):需要被适配的现有接口
简单示例:Type-C转USB适配器
让我们通过一个具体的代码示例来理解适配器模式的工作原理。
接口定义
首先定义两种不同的接口:
java
package com.YA33.desgin.adapter;
/**
* Type-C接口定义
* 这是现代设备常用的接口标准
*/
public interface TypeCSocket {
/**
* Type-C连接方法
*/
void connectTypeC();
}
/**
* USB接口定义
* 这是传统设备常用的接口标准
*/
public interface UsbSocket {
/**
* USB连接方法
*/
void connectUsb();
}
具体设备实现
java
package com.YA33.desgin.adapter;
/**
* MacBook设备,只支持Type-C接口
*/
public class MacBook implements TypeCSocket {
@Override
public void connectTypeC() {
System.out.println("MacBook的Type-C接口已连接");
}
}
/**
* 充电器设备,只支持USB接口
*/
public class Charger {
/**
* 充电方法,只能接受USB接口
* @param usbSocket USB接口
*/
public void charge(UsbSocket usbSocket) {
usbSocket.connectUsb();
System.out.println("开始使用USB接口充电");
}
}
核心:适配器实现
java
package com.YA33.desgin.adapter;
/**
* Type-C转USB适配器
* 这是适配器模式的核心,它实现了目标接口(UsbSocket)
* 并持有被适配者(TypeCSocket)的引用
*/
public class TypeC2UsbAdapter implements UsbSocket {
private final TypeCSocket typeCSocket;
/**
* 构造函数,传入需要适配的Type-C设备
* @param typeCSocket Type-C设备
*/
public TypeC2UsbAdapter(TypeCSocket typeCSocket) {
this.typeCSocket = typeCSocket;
}
@Override
public void connectUsb() {
// 调用被适配者的方法
typeCSocket.connectTypeC();
System.out.println("适配器:正在将Type-C信号转换为USB信号");
System.out.println("适配器:转换完成,调用USB接口");
// 这里可以添加复杂的转换逻辑
// 比如电压转换、数据格式转换等
}
}
使用适配器
java
package com.YA33.desgin.adapter;
/**
* 主程序,演示适配器模式的使用
*/
public class Main {
public static void main(String[] args) {
// 创建MacBook(Type-C接口)
MacBook macBook = new MacBook();
// 创建充电器(USB接口)
Charger charger = new Charger();
// 创建适配器,将Type-C转换为USB
TypeC2UsbAdapter adapter = new TypeC2UsbAdapter(macBook);
// 通过适配器让充电器给MacBook充电
charger.charge(adapter);
}
}
运行结果:
MacBook的Type-C接口已连接
适配器:正在将Type-C信号转换为USB信号
适配器:转换完成,调用USB接口
开始使用USB接口充电
复杂示例:Etcd适配Redis模板
在实际的企业级开发中,我们经常会遇到需要将新系统集成到现有框架中的情况。下面是一个更复杂的示例:将Etcd键值存储适配到Spring的Redis模板接口。
场景背景
假设我们有一个现有的Spring Boot应用,它使用Redis作为缓存,代码中大量使用了RedisTemplate
。现在由于某些原因(比如性能、成本等),我们想要将存储后端从Redis切换到Etcd,但是不希望修改现有的业务代码。
项目结构
src/main/java/com/YA33/desgin/adapter/spring/
├── controller/
│ └── KVController.java # 现有的业务控制器
├── etcd/
│ ├── EtcdProperties.java # Etcd配置属性
│ ├── EtcdRedisConfiguration.java # 配置类
│ ├── EtcdRedisTemplateAdaptor.java # Redis模板适配器
│ ├── EtcdValueOperations.java # 值操作适配器
│ └── VoidRedisKeyValueAdapter.java # 空适配器
└── AdapterApplication.java # 启动类
详细代码实现
1. 业务控制器(现有代码)
java
package com.YA33.desgin.adapter.spring.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
/**
* 键值对控制器
* 这是现有的业务代码,使用RedisTemplate进行操作
* 我们希望通过适配器模式,在不修改这些代码的情况下切换到底层的Etcd
*/
@RestController
@RequestMapping("/redis")
public class KVController {
private static final Logger log = LoggerFactory.getLogger(KVController.class);
private final RedisTemplate<String, String> redisTemplate;
public KVController(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 获取键对应的值
* @param key 键
* @return 值
*/
@GetMapping("/get/{key}")
public String getValue(@PathVariable("key") String key) {
log.info("获取键值对,key: {}", key);
return redisTemplate.opsForValue().get(key);
}
/**
* 设置键值对
* @param key 键
* @param value 值
* @return 操作结果
*/
@PutMapping("/put/{key}/{value}")
public String putValue(@PathVariable("key") String key,
@PathVariable("value") String value) {
log.info("设置键值对,key: {}, value: {}", key, value);
redisTemplate.opsForValue().set(key, value);
return "SUCCESS";
}
}
2. Etcd配置属性
java
package com.YA33.desgin.adapter.spring.etcd;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Etcd配置属性类
* 用于从application.yml中读取Etcd的连接配置
*/
@Data
@ConfigurationProperties(prefix = "ya33.etcd")
public class EtcdProperties {
/**
* Etcd服务地址
* 例如: http://localhost:2379
*/
private String url;
}
3. 配置类
java
package com.YA33.desgin.adapter.spring.etcd;
import io.etcd.jetcd.Client;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisKeyValueAdapter;
import org.springframework.data.redis.core.RedisTemplate;
/**
* Etcd配置类
* 负责创建Etcd客户端和相关的Bean
*/
@Configuration
@EnableConfigurationProperties(EtcdProperties.class)
public class EtcdRedisConfiguration {
/**
* 创建Etcd客户端
* @param etcdProperties Etcd配置属性
* @return Etcd客户端实例
*/
@Bean
public Client etcdClient(EtcdProperties etcdProperties) {
return Client.builder()
.endpoints(etcdProperties.getUrl())
.build();
}
/**
* 创建Etcd值操作实例
* @param client Etcd客户端
* @return 值操作实例
*/
@Bean
public EtcdValueOperations operations(Client client) {
return new EtcdValueOperations(client);
}
/**
* 创建Redis模板适配器
* 这是适配器模式的核心,用Etcd实现RedisTemplate的接口
* @param operations Etcd值操作
* @return Redis模板适配器
*/
@Bean
public RedisTemplate<String, String> redisTemplate(EtcdValueOperations operations) {
return new EtcdRedisTemplateAdaptor(operations);
}
/**
* 创建空的Redis键值适配器
* 用于满足Spring Data Redis的依赖
* @return 空适配器
*/
@Bean
public RedisKeyValueAdapter redisKeyValueAdapter() {
return new VoidRedisKeyValueAdapter();
}
}
4. Redis模板适配器
java
package com.YA33.desgin.adapter.spring.etcd;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
/**
* Redis模板适配器
* 继承RedisTemplate,但底层使用Etcd进行操作
*/
public class EtcdRedisTemplateAdaptor extends RedisTemplate<String, String> {
private final EtcdValueOperations operations;
/**
* 构造函数
* @param operations Etcd值操作实例
*/
public EtcdRedisTemplateAdaptor(EtcdValueOperations operations) {
this.operations = operations;
// 设置值操作器
setValueOperations(operations);
}
@Override
public ValueOperations<String, String> opsForValue() {
return operations;
}
@Override
public void afterPropertiesSet() {
// 重写此方法,避免父类的初始化逻辑
// 因为我们的适配器不需要Redis连接工厂等组件
}
}
5. Etcd值操作适配器
java
package com.YA33.desgin.adapter.spring.etcd;
import io.etcd.jetcd.ByteSequence;
import io.etcd.jetcd.Client;
import io.etcd.jetcd.kv.GetResponse;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.ValueOperations;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* Etcd值操作适配器
* 实现ValueOperations接口,但底层使用Etcd客户端
* 这是适配器模式中最复杂的部分,需要适配所有的方法
*/
public class EtcdValueOperations implements ValueOperations<String, String> {
private final Client client;
public EtcdValueOperations(Client client) {
this.client = client;
}
/**
* 设置键值对 - 核心方法
* 将Redis的set操作适配到Etcd的put操作
*/
@Override
public void set(String key, String value) {
ByteSequence etcdKey = ByteSequence.from(key, StandardCharsets.UTF_8);
ByteSequence etcdValue = ByteSequence.from(value, StandardCharsets.UTF_8);
try {
// 调用Etcd客户端的put方法
client.getKVClient().put(etcdKey, etcdValue).get();
} catch (Exception ex) {
throw new RuntimeException("Etcd设置键值对失败", ex);
}
}
/**
* 获取值 - 核心方法
* 将Redis的get操作适配到Etcd的get操作
*/
@Override
public String get(Object key) {
ByteSequence etcdKey = ByteSequence.from(key.toString(), StandardCharsets.UTF_8);
CompletableFuture<GetResponse> future = client.getKVClient().get(etcdKey);
try {
GetResponse response = future.get();
if (response.getCount() > 0) {
// 返回获取到的值
return response.getKvs().get(0).getValue().toString(StandardCharsets.UTF_8);
} else {
return null;
}
} catch (Exception ex) {
throw new RuntimeException("Etcd获取值失败", ex);
}
}
// 以下方法为了简化示例,只提供空实现或默认实现
// 在实际项目中,需要根据业务需求实现这些方法
@Override
public void set(String key, String value, long timeout, TimeUnit unit) {
// Etcd原生不支持带过期时间的设置,可以通过租约机制实现
// 这里为了简化,直接调用普通set方法
set(key, value);
}
@Override
public Boolean setIfAbsent(String key, String value) {
// 分布式锁或原子操作,可以使用Etcd的事务机制实现
return null;
}
// 其他方法省略空实现...
@Override
public RedisOperations<String, String> getOperations() {
return null;
}
}
6. 空适配器
java
package com.YA33.desgin.adapter.spring.etcd;
import org.springframework.data.redis.core.RedisKeyValueAdapter;
/**
* 空Redis键值适配器
* 用于满足Spring Data Redis的自动配置要求
*/
public class VoidRedisKeyValueAdapter extends RedisKeyValueAdapter {
@Override
public void afterPropertiesSet() {
// 空实现,避免父类的初始化逻辑
}
}
7. 应用启动类
java
package com.YA33.desgin.adapter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 应用启动类
*/
@SpringBootApplication
public class AdapterApplication {
public static void main(String[] args) {
SpringApplication.run(AdapterApplication.class, args);
}
}
配置文件
在application.yml
中配置Etcd连接:
yaml
ya33:
etcd:
url: http://localhost:2379
spring:
data:
redis:
# 禁用Spring Boot自带的Redis自动配置
repositories:
enabled: false
日志系统适配器示例
在实际项目中,我们经常需要集成不同的日志框架。下面通过一个日志适配器的例子,展示如何统一不同日志框架的接口。
场景描述
假设我们有一个老系统使用java.util.logging
,但新系统使用Log4j2
。我们希望在不修改老系统代码的情况下,将日志输出统一到Log4j2
。
代码实现
1. 目标接口(统一的日志接口)
java
package com.YA33.desgin.adapter.logging;
/**
* 统一的日志接口
* 这是我们希望所有系统都使用的接口
*/
public interface UnifiedLogger {
/**
* 记录调试级别日志
* @param message 日志消息
*/
void debug(String message);
/**
* 记录信息级别日志
* @param message 日志消息
*/
void info(String message);
/**
* 记录错误级别日志
* @param message 日志消息
* @param throwable 异常信息
*/
void error(String message, Throwable throwable);
/**
* 记录警告级别日志
* @param message 日志消息
*/
void warn(String message);
}
2. 被适配者(Java Util Logging)
java
package com.YA33.desgin.adapter.logging;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Java Util Logging 实现
* 这是老系统使用的日志框架
*/
public class JavaUtilLogger {
private final Logger logger;
public JavaUtilLogger(String name) {
this.logger = Logger.getLogger(name);
}
/**
* Java Util Logging 的日志方法
* 方法签名与我们的统一接口不同
*/
public void log(Level level, String message) {
logger.log(level, message);
}
public void log(Level level, String message, Throwable thrown) {
logger.log(level, message, thrown);
}
/**
* 其他Java Util Logging特有的方法
*/
public void fine(String message) {
logger.fine(message);
}
public void severe(String message) {
logger.severe(message);
}
}
3. 适配器实现
java
package com.YA33.desgin.adapter.logging;
import java.util.logging.Level;
/**
* Java Util Logging 到统一日志接口的适配器
* 让老系统的日志框架能够适配新的统一接口
*/
public class JavaUtilLoggingAdapter implements UnifiedLogger {
private final JavaUtilLogger julLogger;
/**
* 构造函数,传入需要适配的Java Util Logger
* @param loggerName 日志器名称
*/
public JavaUtilLoggingAdapter(String loggerName) {
this.julLogger = new JavaUtilLogger(loggerName);
}
/**
* 构造函数,传入现有的Java Util Logger实例
* @param julLogger Java Util Logger实例
*/
public JavaUtilLoggingAdapter(JavaUtilLogger julLogger) {
this.julLogger = julLogger;
}
@Override
public void debug(String message) {
// 将debug级别映射到Java Util Logging的FINE级别
julLogger.log(Level.FINE, message);
}
@Override
public void info(String message) {
// 将info级别映射到Java Util Logging的INFO级别
julLogger.log(Level.INFO, message);
}
@Override
public void error(String message, Throwable throwable) {
// 将error级别映射到Java Util Logging的SEVERE级别
julLogger.log(Level.SEVERE, message, throwable);
}
@Override
public void warn(String message) {
// 将warn级别映射到Java Util Logging的WARNING级别
julLogger.log(Level.WARNING, message);
}
}
4. Log4j2 适配器
java
package com.YA33.desgin.adapter.logging;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger;
/**
* Log4j2 到统一日志接口的适配器
* 让Log4j2框架能够适配我们的统一接口
*/
public class Log4j2Adapter implements UnifiedLogger {
private final Logger log4jLogger;
public Log4j2Adapter(String loggerName) {
this.log4jLogger = (Logger) LogManager.getLogger(loggerName);
}
public Log4j2Adapter(Class<?> clazz) {
this.log4jLogger = (Logger) LogManager.getLogger(clazz);
}
@Override
public void debug(String message) {
log4jLogger.debug(message);
}
@Override
public void info(String message) {
log4jLogger.info(message);
}
@Override
public void error(String message, Throwable throwable) {
log4jLogger.error(message, throwable);
}
@Override
public void warn(String message) {
log4jLogger.warn(message);
}
}
5. 日志工厂
java
package com.YA33.desgin.adapter.logging;
/**
* 日志工厂类
* 根据配置创建不同类型的日志适配器
*/
public class LoggerFactory {
/**
* 日志类型枚举
*/
public enum LoggerType {
JAVA_UTIL_LOGGING,
LOG4J2
}
private static LoggerType currentType = LoggerType.LOG4J2;
/**
* 设置当前使用的日志类型
* @param type 日志类型
*/
public static void setLoggerType(LoggerType type) {
currentType = type;
}
/**
* 获取日志器
* @param clazz 类
* @return 统一日志接口实例
*/
public static UnifiedLogger getLogger(Class<?> clazz) {
return getLogger(clazz.getName());
}
/**
* 获取日志器
* @param name 日志器名称
* @return 统一日志接口实例
*/
public static UnifiedLogger getLogger(String name) {
switch (currentType) {
case JAVA_UTIL_LOGGING:
return new JavaUtilLoggingAdapter(name);
case LOG4J2:
return new Log4j2Adapter(name);
default:
return new Log4j2Adapter(name);
}
}
}
6. 使用示例
java
package com.YA33.desgin.adapter.logging;
/**
* 日志适配器使用示例
*/
public class LoggingExample {
// 使用统一日志接口,不关心底层实现
private static final UnifiedLogger logger =
LoggerFactory.getLogger(LoggingExample.class);
public void processData() {
logger.debug("开始处理数据");
try {
// 模拟业务逻辑
logger.info("数据处理中...");
// 模拟异常情况
if (Math.random() > 0.8) {
throw new RuntimeException("模拟的业务异常");
}
logger.info("数据处理完成");
} catch (Exception e) {
logger.error("数据处理失败", e);
}
}
public static void main(String[] args) {
LoggingExample example = new LoggingExample();
// 使用Log4j2作为日志后端
LoggerFactory.setLoggerType(LoggerFactory.LoggerType.LOG4J2);
System.out.println("使用Log4j2后端:");
example.processData();
// 切换到Java Util Logging
LoggerFactory.setLoggerType(LoggerFactory.LoggerType.JAVA_UTIL_LOGGING);
System.out.println("\n使用Java Util Logging后端:");
example.processData();
}
}
支付系统适配器示例
另一个常见的适配器模式应用场景是支付系统集成。不同的支付渠道(支付宝、微信支付、银联等)有不同的接口,我们可以使用适配器模式统一这些接口。
代码实现
1. 统一支付接口
java
package com.YA33.desgin.adapter.payment;
import java.math.BigDecimal;
/**
* 统一支付接口
* 定义所有支付渠道都需要实现的方法
*/
public interface PaymentGateway {
/**
* 支付方法
* @param orderId 订单ID
* @param amount 支付金额
* @param extraParams 额外参数
* @return 支付结果
*/
PaymentResult pay(String orderId, BigDecimal amount, String extraParams);
/**
* 查询支付状态
* @param orderId 订单ID
* @return 支付状态
*/
PaymentStatus queryStatus(String orderId);
/**
* 退款方法
* @param orderId 订单ID
* @param amount 退款金额
* @return 退款结果
*/
RefundResult refund(String orderId, BigDecimal amount);
/**
* 获取支付渠道名称
* @return 渠道名称
*/
String getChannelName();
}
/**
* 支付结果
*/
class PaymentResult {
private boolean success;
private String message;
private String transactionId;
private String payUrl; // 用于扫码支付
// 构造函数、getter、setter省略
}
/**
* 支付状态
*/
class PaymentStatus {
private String orderId;
private String status; // SUCCESS, FAILED, PROCESSING
private BigDecimal amount;
// 构造函数、getter、setter省略
}
/**
* 退款结果
*/
class RefundResult {
private boolean success;
private String message;
private String refundId;
// 构造函数、getter、setter省略
}
2. 支付宝支付适配器
java
package com.YA33.desgin.adapter.payment;
import java.math.BigDecimal;
/**
* 支付宝支付适配器
* 将支付宝的接口适配到统一的支付接口
*/
public class AlipayAdapter implements PaymentGateway {
// 模拟支付宝SDK
private final AlipayService alipayService;
public AlipayAdapter(String appId, String privateKey) {
this.alipayService = new AlipayService(appId, privateKey);
}
@Override
public PaymentResult pay(String orderId, BigDecimal amount, String extraParams) {
// 调用支付宝原生接口
AlipayResponse response = alipayService.createPayment(
orderId,
amount.doubleValue(),
extraParams
);
// 将支付宝响应适配为统一支付结果
PaymentResult result = new PaymentResult();
result.setSuccess("SUCCESS".equals(response.getCode()));
result.setMessage(response.getMsg());
result.setTransactionId(response.getTradeNo());
result.setPayUrl(response.getPayUrl());
return result;
}
@Override
public PaymentStatus queryStatus(String orderId) {
AlipayQueryResponse response = alipayService.queryOrder(orderId);
PaymentStatus status = new PaymentStatus();
status.setOrderId(orderId);
// 将支付宝状态映射为统一状态
switch (response.getTradeStatus()) {
case "TRADE_SUCCESS":
status.setStatus("SUCCESS");
break;
case "TRADE_CLOSED":
status.setStatus("FAILED");
break;
default:
status.setStatus("PROCESSING");
}
status.setAmount(BigDecimal.valueOf(response.getTotalAmount()));
return status;
}
@Override
public RefundResult refund(String orderId, BigDecimal amount) {
AlipayRefundResponse response = alipayService.refund(
orderId,
amount.doubleValue()
);
RefundResult result = new RefundResult();
result.setSuccess("10000".equals(response.getCode()));
result.setMessage(response.getMsg());
result.setRefundId(response.getRefundId());
return result;
}
@Override
public String getChannelName() {
return "支付宝";
}
}
/**
* 模拟支付宝服务类
*/
class AlipayService {
private String appId;
private String privateKey;
public AlipayService(String appId, String privateKey) {
this.appId = appId;
this.privateKey = privateKey;
}
public AlipayResponse createPayment(String orderId, double amount, String subject) {
// 模拟调用支付宝接口
System.out.println("调用支付宝支付接口: " + orderId + ", 金额: " + amount);
AlipayResponse response = new AlipayResponse();
response.setCode("SUCCESS");
response.setMsg("成功");
response.setTradeNo("ALIPAY_" + System.currentTimeMillis());
response.setPayUrl("https://alipay.com/pay/" + response.getTradeNo());
return response;
}
public AlipayQueryResponse queryOrder(String orderId) {
// 模拟查询订单状态
AlipayQueryResponse response = new AlipayQueryResponse();
response.setTradeStatus("TRADE_SUCCESS");
response.setTotalAmount(100.0);
return response;
}
public AlipayRefundResponse refund(String orderId, double amount) {
// 模拟退款
AlipayRefundResponse response = new AlipayRefundResponse();
response.setCode("10000");
response.setMsg("成功");
response.setRefundId("REFUND_" + System.currentTimeMillis());
return response;
}
}
/**
* 模拟支付宝响应类
*/
class AlipayResponse {
private String code;
private String msg;
private String tradeNo;
private String payUrl;
// getter、setter省略
}
class AlipayQueryResponse {
private String tradeStatus;
private double totalAmount;
// getter、setter省略
}
class AlipayRefundResponse {
private String code;
private String msg;
private String refundId;
// getter、setter省略
}
3. 微信支付适配器
java
package com.YA33.desgin.adapter.payment;
import java.math.BigDecimal;
/**
* 微信支付适配器
* 将微信支付的接口适配到统一的支付接口
*/
public class WechatPayAdapter implements PaymentGateway {
private final WechatPayService wechatPayService;
public WechatPayAdapter(String appId, String mchId, String apiKey) {
this.wechatPayService = new WechatPayService(appId, mchId, apiKey);
}
@Override
public PaymentResult pay(String orderId, BigDecimal amount, String extraParams) {
// 微信支付需要将金额转换为分
int totalFee = amount.multiply(BigDecimal.valueOf(100)).intValue();
WechatPayResponse response = wechatPayService.unifiedOrder(
orderId,
totalFee,
extraParams
);
PaymentResult result = new PaymentResult();
result.setSuccess("SUCCESS".equals(response.getReturnCode())
&& "SUCCESS".equals(response.getResultCode()));
result.setMessage(response.getReturnMsg());
result.setTransactionId(response.getTransactionId());
result.setPayUrl(response.getCodeUrl()); // 微信支付返回的是二维码URL
return result;
}
@Override
public PaymentStatus queryStatus(String orderId) {
WechatQueryResponse response = wechatPayService.orderQuery(orderId);
PaymentStatus status = new PaymentStatus();
status.setOrderId(orderId);
// 将微信支付状态映射为统一状态
switch (response.getTradeState()) {
case "SUCCESS":
status.setStatus("SUCCESS");
break;
case "REFUND":
status.setStatus("REFUNDED");
break;
default:
status.setStatus("PROCESSING");
}
// 微信支付返回的是分,需要转换为元
status.setAmount(BigDecimal.valueOf(response.getTotalFee() / 100.0));
return status;
}
@Override
public RefundResult refund(String orderId, BigDecimal amount) {
int refundFee = amount.multiply(BigDecimal.valueOf(100)).intValue();
WechatRefundResponse response = wechatPayService.refund(
orderId,
refundFee
);
RefundResult result = new RefundResult();
result.setSuccess("SUCCESS".equals(response.getReturnCode())
&& "SUCCESS".equals(response.getResultCode()));
result.setMessage(response.getReturnMsg());
result.setRefundId(response.getRefundId());
return result;
}
@Override
public String getChannelName() {
return "微信支付";
}
}
/**
* 模拟微信支付服务类
*/
class WechatPayService {
private String appId;
private String mchId;
private String apiKey;
public WechatPayService(String appId, String mchId, String apiKey) {
this.appId = appId;
this.mchId = mchId;
this.apiKey = apiKey;
}
public WechatPayResponse unifiedOrder(String orderId, int totalFee, String body) {
// 模拟调用微信支付接口
System.out.println("调用微信支付统一下单接口: " + orderId + ", 金额(分): " + totalFee);
WechatPayResponse response = new WechatPayResponse();
response.setReturnCode("SUCCESS");
response.setReturnMsg("OK");
response.setResultCode("SUCCESS");
response.setTransactionId("WECHAT_" + System.currentTimeMillis());
response.setCodeUrl("weixin://wxpay/bizpayurl?pr=" + System.currentTimeMillis());
return response;
}
public WechatQueryResponse orderQuery(String orderId) {
// 模拟查询订单
WechatQueryResponse response = new WechatQueryResponse();
response.setTradeState("SUCCESS");
response.setTotalFee(10000); // 100元
return response;
}
public WechatRefundResponse refund(String orderId, int refundFee) {
// 模拟退款
WechatRefundResponse response = new WechatRefundResponse();
response.setReturnCode("SUCCESS");
response.setReturnMsg("OK");
response.setResultCode("SUCCESS");
response.setRefundId("WECHAT_REFUND_" + System.currentTimeMillis());
return response;
}
}
/**
* 模拟微信支付响应类
*/
class WechatPayResponse {
private String returnCode;
private String returnMsg;
private String resultCode;
private String transactionId;
private String codeUrl;
// getter、setter省略
}
class WechatQueryResponse {
private String tradeState;
private int totalFee;
// getter、setter省略
}
class WechatRefundResponse {
private String returnCode;
private String returnMsg;
private String resultCode;
private String refundId;
// getter、setter省略
}
4. 支付服务工厂
java
package com.YA33.desgin.adapter.payment;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
/**
* 支付服务工厂
* 根据支付渠道创建对应的支付适配器
*/
public class PaymentServiceFactory {
private static final Map<String, PaymentGateway> gateways = new HashMap<>();
static {
// 初始化支付渠道
gateways.put("alipay", new AlipayAdapter("2021000116691234", "your_private_key"));
gateways.put("wechat", new WechatPayAdapter("wx1234567890", "1230000109", "your_api_key"));
}
/**
* 获取支付网关
* @param channel 支付渠道
* @return 支付网关实例
*/
public static PaymentGateway getPaymentGateway(String channel) {
PaymentGateway gateway = gateways.get(channel);
if (gateway == null) {
throw new IllegalArgumentException("不支持的支付渠道: " + channel);
}
return gateway;
}
/**
* 注册新的支付渠道
* @param channel 渠道标识
* @param gateway 支付网关
*/
public static void registerGateway(String channel, PaymentGateway gateway) {
gateways.put(channel, gateway);
}
}
5. 统一支付服务
java
package com.YA33.desgin.adapter.payment;
import java.math.BigDecimal;
/**
* 统一支付服务
* 业务层使用这个服务,不关心具体的支付渠道实现
*/
public class UnifiedPaymentService {
/**
* 创建支付
* @param orderId 订单ID
* @param amount 金额
* @param channel 支付渠道
* @param extraParams 额外参数
* @return 支付结果
*/
public PaymentResult createPayment(String orderId, BigDecimal amount,
String channel, String extraParams) {
PaymentGateway gateway = PaymentServiceFactory.getPaymentGateway(channel);
System.out.println("使用支付渠道: " + gateway.getChannelName());
return gateway.pay(orderId, amount, extraParams);
}
/**
* 查询支付状态
* @param orderId 订单ID
* @param channel 支付渠道
* @return 支付状态
*/
public PaymentStatus queryPaymentStatus(String orderId, String channel) {
PaymentGateway gateway = PaymentServiceFactory.getPaymentGateway(channel);
return gateway.queryStatus(orderId);
}
/**
* 执行退款
* @param orderId 订单ID
* @param amount 退款金额
* @param channel 支付渠道
* @return 退款结果
*/
public RefundResult refund(String orderId, BigDecimal amount, String channel) {
PaymentGateway gateway = PaymentServiceFactory.getPaymentGateway(channel);
return gateway.refund(orderId, amount);
}
}
6. 使用示例
java
package com.YA33.desgin.adapter.payment;
import java.math.BigDecimal;
/**
* 支付适配器使用示例
*/
public class PaymentExample {
public static void main(String[] args) {
UnifiedPaymentService paymentService = new UnifiedPaymentService();
String orderId = "ORDER_" + System.currentTimeMillis();
BigDecimal amount = new BigDecimal("100.00");
// 使用支付宝支付
System.out.println("=== 支付宝支付 ===");
PaymentResult alipayResult = paymentService.createPayment(
orderId, amount, "alipay", "购买商品"
);
System.out.println("支付结果: " + alipayResult.isSuccess());
System.out.println("支付URL: " + alipayResult.getPayUrl());
// 使用微信支付
System.out.println("\n=== 微信支付 ===");
PaymentResult wechatResult = paymentService.createPayment(
orderId, amount, "wechat", "购买商品"
);
System.out.println("支付结果: " + wechatResult.isSuccess());
System.out.println("支付URL: " + wechatResult.getPayUrl());
// 查询支付状态
System.out.println("\n=== 查询支付状态 ===");
PaymentStatus status = paymentService.queryPaymentStatus(orderId, "alipay");
System.out.println("订单状态: " + status.getStatus());
// 执行退款
System.out.println("\n=== 执行退款 ===");
RefundResult refundResult = paymentService.refund(orderId, amount, "alipay");
System.out.println("退款结果: " + refundResult.isSuccess());
}
}
适配器模式的关键要点总结
通过以上两个完整的示例,我们可以总结出适配器模式的关键要点:
1. 适配器模式的结构
客户端 → 目标接口 → 适配器 → 被适配者
2. 实现步骤
- 定义目标接口:确定客户端期望的接口
- 识别被适配者:找出需要被适配的现有类或接口
- 创建适配器类 :
- 实现目标接口
- 持有被适配者的引用
- 在目标接口方法中调用被适配者的方法
3. 适配器模式的变体
- 类适配器:使用继承(在Java中不常用,因为Java不支持多继承)
- 对象适配器:使用组合(推荐使用,更灵活)
4. 适用场景
- 系统集成:集成第三方库或遗留系统
- 接口统一:统一多个相似但不兼容的接口
- 版本兼容:新版本接口需要兼容老版本
- 测试模拟:在测试中使用模拟对象替代真实对象
5. 优点
- 解耦合:客户端与被适配者解耦
- 复用性:可以复用现有的类
- 灵活性:可以适配多个不同的被适配者
- 符合开闭原则:易于扩展新的适配器
6. 注意事项
- 不要过度使用:如果接口可以统一修改,直接修改接口可能更简单
- 性能考虑:适配器会增加额外的调用层次,可能影响性能
- 复杂性:过多的适配器会增加系统的复杂性