java设计模式五、适配器模式

文章没有视频清晰,推荐大家去某站的生生大佬的手写,讲的很清晰

什么是适配器模式

适配器模式(Adapter Pattern)是一种结构型设计模式,它允许不兼容的接口之间进行协作。简单来说,适配器模式就像是一个"转换器",在两个不兼容的系统之间架起一座桥梁,让它们能够正常通信。

生活中的适配器例子

想象一下这样的场景:你有一台只支持USB接口的电脑,但你的充电器却是Type-C接口。这时候你就需要一个转换器:

复制代码
Type-C接口 → 转换器 → USB接口

这个转换器就是现实生活中的适配器,它让两个原本不兼容的设备能够正常工作。

适配器模式的核心思想

适配器模式主要包含三个角色:

  1. 目标接口(Target):客户端期望的接口
  2. 适配器(Adapter):实现目标接口,并持有被适配者的引用
  3. 被适配者(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. 实现步骤

  1. 定义目标接口:确定客户端期望的接口
  2. 识别被适配者:找出需要被适配的现有类或接口
  3. 创建适配器类
    • 实现目标接口
    • 持有被适配者的引用
    • 在目标接口方法中调用被适配者的方法

3. 适配器模式的变体

  • 类适配器:使用继承(在Java中不常用,因为Java不支持多继承)
  • 对象适配器:使用组合(推荐使用,更灵活)

4. 适用场景

  • 系统集成:集成第三方库或遗留系统
  • 接口统一:统一多个相似但不兼容的接口
  • 版本兼容:新版本接口需要兼容老版本
  • 测试模拟:在测试中使用模拟对象替代真实对象

5. 优点

  • 解耦合:客户端与被适配者解耦
  • 复用性:可以复用现有的类
  • 灵活性:可以适配多个不同的被适配者
  • 符合开闭原则:易于扩展新的适配器

6. 注意事项

  • 不要过度使用:如果接口可以统一修改,直接修改接口可能更简单
  • 性能考虑:适配器会增加额外的调用层次,可能影响性能
  • 复杂性:过多的适配器会增加系统的复杂性
相关推荐
拂晓银砾2 小时前
EasyExcel 动态多级标题、合并单元格、修改单元格样式实现总结
java
玩毛线的包子2 小时前
Android Gradle学习(十)- java字节码指令集解读
java
华农第一蒟蒻2 小时前
谈谈跨域问题
java·后端·nginx·安全·okhttp·c5全栈
菜鸟plus+2 小时前
MinIO
java
艾菜籽3 小时前
JVM中的垃圾回收机制
java·jvm
敲代码的嘎仔3 小时前
JavaWeb零基础学习Day1——HTML&CSS
java·开发语言·前端·css·学习·html·学习方法
Terio_my9 小时前
Java bean 数据校验
java·开发语言·python
超级大只老咪9 小时前
何为“类”?(Java基础语法)
java·开发语言·前端
我笑了OvO10 小时前
C++类和对象(1)
java·开发语言·c++·类和对象