java设计模式五、适配器模式
文章比如视频好,推荐大家去某站的生生大佬的手写,讲的很清晰
什么是适配器模式
适配器模式(Adapter Pattern)是一种结构型设计模式,它允许不兼容的接口之间进行协作。简单来说,适配器模式就像是一个"转换器",在两个不兼容的系统之间架起一座桥梁,让它们能够正常通信。
生活中的适配器例子
想象一下这样的场景:你有一台只支持USB接口的电脑,但你的充电器却是Type-C接口。这时候你就需要一个转换器:
Type-C接口 → 转换器 → USB接口
这个转换器就是现实生活中的适配器,它让两个原本不兼容的设备能够正常工作。
适配器模式的核心思想
适配器模式主要包含三个角色:
- 目标接口(Target):客户端期望的接口
- 适配器(Adapter):实现目标接口,并持有被适配者的引用
- 被适配者(Adaptee):需要被适配的现有接口
简单示例:Type-C转USB适配器
让我们通过一个具体的代码示例来理解适配器模式的工作原理。
接口定义
首先定义两种不同的接口:
package com.YA33.desgin.adapter;/*** Type-C接口定义* 这是现代设备常用的接口标准*/
public interface TypeCSocket {/*** Type-C连接方法*/void connectTypeC();
}/*** USB接口定义* 这是传统设备常用的接口标准*/
public interface UsbSocket {/*** USB连接方法*/void connectUsb();
}
具体设备实现
package com.YA33.desgin.adapter;/*** MacBook设备,只支持Type-C接口*/
public class MacBook implements TypeCSocket {@Overridepublic 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接口充电");}
}
核心:适配器实现
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;}@Overridepublic void connectUsb() {// 调用被适配者的方法typeCSocket.connectTypeC();System.out.println("适配器:正在将Type-C信号转换为USB信号");System.out.println("适配器:转换完成,调用USB接口");// 这里可以添加复杂的转换逻辑// 比如电压转换、数据格式转换等}
}
使用适配器
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转换为USBTypeC2UsbAdapter 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. 业务控制器(现有代码)
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配置属性
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. 配置类
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客户端实例*/@Beanpublic Client etcdClient(EtcdProperties etcdProperties) {return Client.builder().endpoints(etcdProperties.getUrl()).build();}/*** 创建Etcd值操作实例* @param client Etcd客户端* @return 值操作实例*/@Beanpublic EtcdValueOperations operations(Client client) {return new EtcdValueOperations(client);}/*** 创建Redis模板适配器* 这是适配器模式的核心,用Etcd实现RedisTemplate的接口* @param operations Etcd值操作* @return Redis模板适配器*/@Beanpublic RedisTemplate<String, String> redisTemplate(EtcdValueOperations operations) {return new EtcdRedisTemplateAdaptor(operations);}/*** 创建空的Redis键值适配器* 用于满足Spring Data Redis的依赖* @return 空适配器*/@Beanpublic RedisKeyValueAdapter redisKeyValueAdapter() {return new VoidRedisKeyValueAdapter();}
}
4. Redis模板适配器
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);}@Overridepublic ValueOperations<String, String> opsForValue() {return operations;}@Overridepublic void afterPropertiesSet() {// 重写此方法,避免父类的初始化逻辑// 因为我们的适配器不需要Redis连接工厂等组件}
}
5. Etcd值操作适配器
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操作*/@Overridepublic 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操作*/@Overridepublic 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);}}// 以下方法为了简化示例,只提供空实现或默认实现// 在实际项目中,需要根据业务需求实现这些方法@Overridepublic void set(String key, String value, long timeout, TimeUnit unit) {// Etcd原生不支持带过期时间的设置,可以通过租约机制实现// 这里为了简化,直接调用普通set方法set(key, value);}@Overridepublic Boolean setIfAbsent(String key, String value) {// 分布式锁或原子操作,可以使用Etcd的事务机制实现return null;}// 其他方法省略空实现...@Overridepublic RedisOperations<String, String> getOperations() {return null;}
}
6. 空适配器
package com.YA33.desgin.adapter.spring.etcd;import org.springframework.data.redis.core.RedisKeyValueAdapter;/*** 空Redis键值适配器* 用于满足Spring Data Redis的自动配置要求*/
public class VoidRedisKeyValueAdapter extends RedisKeyValueAdapter {@Overridepublic void afterPropertiesSet() {// 空实现,避免父类的初始化逻辑}
}
7. 应用启动类
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连接:
ya33:etcd:url: http://localhost:2379spring:data:redis:# 禁用Spring Boot自带的Redis自动配置repositories:enabled: false
日志系统适配器示例
在实际项目中,我们经常需要集成不同的日志框架。下面通过一个日志适配器的例子,展示如何统一不同日志框架的接口。
场景描述
假设我们有一个老系统使用java.util.logging
,但新系统使用Log4j2
。我们希望在不修改老系统代码的情况下,将日志输出统一到Log4j2
。
代码实现
1. 目标接口(统一的日志接口)
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)
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. 适配器实现
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;}@Overridepublic void debug(String message) {// 将debug级别映射到Java Util Logging的FINE级别julLogger.log(Level.FINE, message);}@Overridepublic void info(String message) {// 将info级别映射到Java Util Logging的INFO级别julLogger.log(Level.INFO, message);}@Overridepublic void error(String message, Throwable throwable) {// 将error级别映射到Java Util Logging的SEVERE级别julLogger.log(Level.SEVERE, message, throwable);}@Overridepublic void warn(String message) {// 将warn级别映射到Java Util Logging的WARNING级别julLogger.log(Level.WARNING, message);}
}
4. Log4j2 适配器
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);}@Overridepublic void debug(String message) {log4jLogger.debug(message);}@Overridepublic void info(String message) {log4jLogger.info(message);}@Overridepublic void error(String message, Throwable throwable) {log4jLogger.error(message, throwable);}@Overridepublic void warn(String message) {log4jLogger.warn(message);}
}
5. 日志工厂
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. 使用示例
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 LoggingLoggerFactory.setLoggerType(LoggerFactory.LoggerType.JAVA_UTIL_LOGGING);System.out.println("\n使用Java Util Logging后端:");example.processData();}
}
支付系统适配器示例
另一个常见的适配器模式应用场景是支付系统集成。不同的支付渠道(支付宝、微信支付、银联等)有不同的接口,我们可以使用适配器模式统一这些接口。
代码实现
1. 统一支付接口
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, PROCESSINGprivate BigDecimal amount;// 构造函数、getter、setter省略
}/*** 退款结果*/
class RefundResult {private boolean success;private String message;private String refundId;// 构造函数、getter、setter省略
}
2. 支付宝支付适配器
package com.YA33.desgin.adapter.payment;import java.math.BigDecimal;/*** 支付宝支付适配器* 将支付宝的接口适配到统一的支付接口*/
public class AlipayAdapter implements PaymentGateway {// 模拟支付宝SDKprivate final AlipayService alipayService;public AlipayAdapter(String appId, String privateKey) {this.alipayService = new AlipayService(appId, privateKey);}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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. 微信支付适配器
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);}@Overridepublic 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()); // 微信支付返回的是二维码URLreturn result;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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. 支付服务工厂
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. 统一支付服务
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. 使用示例
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. 注意事项
- 不要过度使用:如果接口可以统一修改,直接修改接口可能更简单
- 性能考虑:适配器会增加额外的调用层次,可能影响性能
- 复杂性:过多的适配器会增加系统的复杂性