《设计模式》第七篇:适配器模式

本期内容为自己总结归档,共分十一章,本人遇到过的面试问题会重点标记。

《设计模式》第一篇:初识

《设计模式》第二篇:单例模式

《设计模式》第三篇:工厂模式

《设计模式》第四篇:观察模式

《设计模式》第五篇:策略模式

《设计模式》第六篇:装饰器模式

《设计模式》第七篇:适配器模式

《设计模式》第八篇:创建型模式

《设计模式》第九篇:结构型模式

《设计模式》第十篇:行为型模式

《设计模式》第十一篇:总结&常用案例

(若有任何疑问,可在评论区告诉我,看到就回复)

第一部分:适配器模式的核心概念

1.1 什么是适配器模式?

适配器模式是一种结构型设计模式,它通过将一个类的接口转换成客户期望的另一个接口,让那些原本接口不兼容的类能够一起工作。

核心思想:包装一个对象,使其接口与另一个接口兼容,而不改变原始对象的实现。

1.2 适配器模式的三个角色

  1. Target(目标接口):客户端期望的接口

  2. Adaptee(被适配者):需要被适配的现有接口

  3. Adapter(适配器):将Adaptee的接口转换为Target接口

1.3 适配器模式的两种实现方式

方式一:对象适配器(推荐)

通过组合方式实现,更灵活,符合"组合优于继承"原则。

java 复制代码
// 1. 目标接口:文件阅读器接口
public interface FileReader {
    String read(String fileName);
}

// 2. 被适配者:现有的CSV文件读取器
public class CSVReader {
    public String readCSV(String csvFile) {
        System.out.println("读取CSV文件: " + csvFile);
        // 模拟读取CSV文件
        return "CSV数据: 姓名,年龄,职业";
    }
}

// 3. 适配器:将CSV读取器适配为文件阅读器(通过组合实现)
public class CSVReaderAdapter implements FileReader {
    private CSVReader csvReader;
    
    public CSVReaderAdapter(CSVReader csvReader) {
        this.csvReader = csvReader;
    }
    
    @Override
    public String read(String fileName) {
        // 验证文件扩展名
        if (!fileName.endsWith(".csv")) {
            throw new IllegalArgumentException("仅支持CSV文件");
        }
        
        System.out.println("适配器:将通用文件读取转换为CSV读取");
        // 调用被适配者的方法
        return csvReader.readCSV(fileName);
    }
}

// 4. 被适配者:现有的JSON文件读取器
public class JSONReader {
    public String parseJSON(String jsonFile) {
        System.out.println("解析JSON文件: " + jsonFile);
        return "JSON数据: {\"name\": \"张三\"}";
    }
}

// 5. JSON适配器
public class JSONReaderAdapter implements FileReader {
    private JSONReader jsonReader;
    
    public JSONReaderAdapter(JSONReader jsonReader) {
        this.jsonReader = jsonReader;
    }
    
    @Override
    public String read(String fileName) {
        if (!fileName.endsWith(".json")) {
            throw new IllegalArgumentException("仅支持JSON文件");
        }
        
        System.out.println("适配器:将通用文件读取转换为JSON解析");
        return jsonReader.parseJSON(fileName);
    }
}

// 6. 客户端:统一的文件处理系统
public class FileProcessor {
    public void processFile(FileReader reader, String fileName) {
        System.out.println("\n开始处理文件: " + fileName);
        String content = reader.read(fileName);
        System.out.println("文件内容: " + content);
        // 进一步处理内容...
    }
}

// 7. 测试
public class ObjectAdapterDemo {
    public static void main(String[] args) {
        FileProcessor processor = new FileProcessor();
        
        // 处理CSV文件
        CSVReader csvReader = new CSVReader();
        FileReader csvAdapter = new CSVReaderAdapter(csvReader);
        processor.processFile(csvAdapter, "data.csv");
        
        // 处理JSON文件
        JSONReader jsonReader = new JSONReader();
        FileReader jsonAdapter = new JSONReaderAdapter(jsonReader);
        processor.processFile(jsonAdapter, "config.json");
        
        // 尝试处理不支持的文件类型
        try {
            processor.processFile(csvAdapter, "data.txt");
        } catch (IllegalArgumentException e) {
            System.out.println("错误: " + e.getMessage());
        }
    }
}
方式二:类适配器

通过多重继承实现(在Java中通过继承和接口实现)。

java 复制代码
// 1. 目标接口:客户端期望的充电器接口
public interface USBCharger {
    void chargeWithUSB();
}

// 2. 被适配者:现有的Type-C充电器
public class TypeCCharger {
    public void chargeWithTypeC() {
        System.out.println("使用Type-C接口充电...");
    }
}

// 3. 适配器:将Type-C转换为USB(通过继承实现)
public class TypeCToUSBAdapter extends TypeCCharger implements USBCharger {
    @Override
    public void chargeWithUSB() {
        System.out.println("适配器:将USB转换为Type-C");
        // 调用被适配者的方法
        chargeWithTypeC();
    }
}

// 4. 客户端:只能使用USB充电的设备
public class USBDevice {
    public void charge(USBCharger charger) {
        System.out.println("设备开始充电...");
        charger.chargeWithUSB();
        System.out.println("设备充电完成");
    }
}

// 5. 测试
public class ClassAdapterDemo {
    public static void main(String[] args) {
        USBDevice device = new USBDevice();
        
        // 使用适配器充电
        TypeCToUSBAdapter adapter = new TypeCToUSBAdapter();
        device.charge(adapter);
    }
}

1.4 核心特点

  1. 接口转换:不改变原有功能,只改变访问方式

  2. 透明性:客户端不知道适配器的存在

  3. 复用性:可以让不兼容的类协同工作

  4. 解耦:分离了客户端与被适配者

第二部分:适配器模式的实现

2.1 基础示例:媒体播放器

假设我们有一个音频播放器,只能播放MP3格式,但需要支持更先进的播放器功能。

java 复制代码
// 目标接口:音频播放器
public interface MediaPlayer {
    void play(String audioType, String fileName);
}

// 被适配者:高级媒体播放器
public class AdvancedMediaPlayer {
    public void playVlc(String fileName) {
        System.out.println("播放VLC文件: " + fileName);
    }
    
    public void playMp4(String fileName) {
        System.out.println("播放MP4文件: " + fileName);
    }
}

// 适配器:让高级播放器适配基本播放器接口
public class MediaAdapter implements MediaPlayer {
    private AdvancedMediaPlayer advancedPlayer;
    
    public MediaAdapter(AdvancedMediaPlayer advancedPlayer) {
        this.advancedPlayer = advancedPlayer;
    }
    
    @Override
    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("vlc")) {
            advancedPlayer.playVlc(fileName);
        } else if (audioType.equalsIgnoreCase("mp4")) {
            advancedPlayer.playMp4(fileName);
        } else {
            System.out.println("不支持的文件格式: " + audioType);
        }
    }
}

// 客户端使用
public class AudioPlayer {
    private MediaPlayer player;
    
    public AudioPlayer(MediaPlayer player) {
        this.player = player;
    }
    
    public void playAudio(String type, String file) {
        player.play(type, file);
    }
}

2.2 实际应用:支付网关集成

电商系统需要集成多个支付渠道,每个渠道的API都不同。

java 复制代码
// 统一支付接口
public interface PaymentGateway {
    boolean processPayment(double amount, String currency);
}

// PayPal支付(被适配者)
public class PayPalService {
    public boolean makePayment(double usdAmount) {
        System.out.println("PayPal处理支付: $" + usdAmount);
        return true;
    }
}

// PayPal适配器
public class PayPalAdapter implements PaymentGateway {
    private PayPalService paypalService;
    
    public PayPalAdapter(PayPalService paypalService) {
        this.paypalService = paypalService;
    }
    
    @Override
    public boolean processPayment(double amount, String currency) {
        // 转换货币(简单示例)
        double usdAmount = convertToUSD(amount, currency);
        return paypalService.makePayment(usdAmount);
    }
    
    private double convertToUSD(double amount, String currency) {
        // 汇率转换逻辑
        return amount * 0.15; // 假设转换率
    }
}

第三部分:Java和Spring中的适配器模式

3.1 Java标准库中的应用

java 复制代码
// 1. 字节流到字符流的适配器
InputStream inputStream = new FileInputStream("file.txt");
Reader reader = new InputStreamReader(inputStream, "UTF-8");

// 2. 数组到列表的适配器
String[] array = {"A", "B", "C"};
List<String> list = Arrays.asList(array); // 返回的是适配器列表

// 3. 枚举到迭代器的适配器
Vector<String> vector = new Vector<>();
Enumeration<String> enumeration = vector.elements();
Iterator<String> iterator = Collections.asIterator(enumeration);

3.2 Spring框架中的应用

Spring MVC的HandlerAdapter

Spring MVC使用适配器模式处理不同类型的控制器,让DispatcherServlet可以统一调用不同的处理器。

java 复制代码
// Spring中的简化示例
public interface HandlerAdapter {
    boolean supports(Object handler);
    ModelAndView handle(HttpServletRequest request, 
                       HttpServletResponse response, 
                       Object handler) throws Exception;
}

// 使用适配器处理不同类型的控制器
@Controller
public class UserController {
    @GetMapping("/user/{id}")
    public String getUser(@PathVariable Long id) {
        return "user";
    }
}

Spring AOP的适配器

Spring AOP使用适配器将不同的Advice类型适配到统一的MethodInterceptor接口。

java 复制代码
// AOP联盟的标准接口
public interface org.aopalliance.intercept.MethodInterceptor {
    Object invoke(MethodInvocation invocation) throws Throwable;
}

// Spring的Advice接口
public interface org.springframework.aop.Advice {}

// 不同的Advice类型
public interface MethodBeforeAdvice extends BeforeAdvice {
    void before(Method method, Object[] args, Object target) throws Throwable;
}

public interface AfterReturningAdvice extends AfterAdvice {
    void afterReturning(Object returnValue, Method method, 
                        Object[] args, Object target) throws Throwable;
}

// 适配器:将Spring Advice适配为AOP联盟的MethodInterceptor
public class MethodBeforeAdviceAdapter implements AdvisorAdapter {
    
    @Override
    public boolean supportsAdvice(Advice advice) {
        return advice instanceof MethodBeforeAdvice;
    }
    
    @Override
    public MethodInterceptor getInterceptor(Advisor advisor) {
        MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
        // 创建适配器,将MethodBeforeAdvice适配为MethodInterceptor
        return new MethodBeforeAdviceInterceptor(advice);
    }
}

// 具体的适配器实现
public class MethodBeforeAdviceInterceptor implements MethodInterceptor {
    private final MethodBeforeAdvice advice;
    
    public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
        this.advice = advice;
    }
    
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        // 在执行目标方法前执行advice
        this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
        return mi.proceed();
    }
}

总结

4.1 何时使用适配器模式

使用适配器模式的典型场景:

  1. 系统集成:需要集成遗留系统或第三方库,但接口不兼容

  2. 接口升级:系统接口升级,但需要保持向后兼容

  3. 统一接口:需要为多个类似但接口不同的类提供统一接口

  4. 复用现有类:想使用现有类,但其接口不符合需求

4.2 优缺点

优点

  1. 提高代码复用性:可以让原本无法使用的代码得到复用

  2. 符合开闭原则:可以在不修改现有代码的情况下引入新功能

  3. 提高系统灵活性:可以动态适配不同的实现

  4. 解耦客户端与被适配者:客户端只依赖目标接口

  5. 保持兼容性:可以在升级接口时保持向后兼容

缺点

  1. 增加系统复杂度:引入额外的适配器类

  2. 可能降低性能:额外的调用层次可能带来性能开销

  3. 可能隐藏设计问题:过度使用可能掩盖接口设计的问题

  4. 调试困难:多层适配可能使调试更复杂

4.3 避免适配器模式的误用

适配器模式不应被滥用:

  1. 不要过早使用适配器:在设计阶段应尽量设计兼容的接口

  2. 避免过度包装:过多的适配层会增加系统复杂度

  3. 考虑性能影响:适配器可能带来额外的性能开销

反例:不必要的适配器

java 复制代码
// 反例:接口本来就很相似,不需要适配器
public interface OldService {
    void process(String data);
}

public interface NewService {
    void handle(String input);
}

// 不需要适配器,直接修改NewService接口或重构客户端
// 适配器应该用于无法修改的接口

OK 六大核心模式系列到此结束,接下来是三大设计模式类型中的其他设计模式

相关推荐
数智工坊2 小时前
【数据结构-栈】3.1栈的顺序存储-链式存储
java·开发语言·数据结构
DFT计算杂谈2 小时前
VASP+Wannier90 计算位移电流和二次谐波SHG
java·服务器·前端·python·算法
多多*2 小时前
2月3日面试题整理 字节跳动后端开发相关
android·java·开发语言·网络·jvm·adb·c#
无名的小白2 小时前
openclaw使用nginx反代部署过程 与disconnected (1008): pairing required解决
java·前端·nginx
.ZGR.2 小时前
认识数据结构:图——无人机防空平台的“衍生品”
java·开发语言·数据结构
huidu012 小时前
基于AQS实现的ReentrantLock
java
冰敷逆向2 小时前
京东h5st纯算分析
java·前端·javascript·爬虫·安全·web
Coder_preston3 小时前
Java集合框架详解
java·开发语言
多多*3 小时前
2026年最新 测试开发工程师相关 Linux相关知识点
java·开发语言·javascript·算法·spring·java-ee·maven