适配器模式详解和源码中应用

news2024/11/26 4:22:49

适配器模式

适配器模式(Adapter Pattern)是一种结构型设计模式,它的作用是将一个类的接口转换为客户端所期望的另一种接口。适配器模式让原本接口不兼容的类能够合作无间,常用于将新系统集成到旧系统中。

形象的例子:电源插座适配器

设想一下,你去旅游的时候发现你带的电子设备插头是两插的,而当地的插座是三插的,这时候你就需要一个插头适配器,它能够将你的两插头转换为三插头,从而使设备能够正常使用。

在这个例子中:

  • 两插头:代表一个已经存在的接口(老接口)。
  • 三插头插座:代表一个新系统的接口(新接口)。
  • 插头适配器:是适配器,将老接口转化为新接口,使得设备能够正常工作。

类适配器

适配器类继承了要适配的类,并且实现了目标接口。

优点:由于使用了继承,可以直接重写父类中的方法,代码简洁。

缺点:Java 中不支持多继承,所以如果适配的类已经继承了其他类,类适配器就无法使用。

// 目标接口:三插电器
interface ThreePlugDevice {
    void powerWithThreePlug();
}

// 被适配的类:两插电器
class TwoPlugDevice {
    public void powerWithTwoPlug() {
        System.out.println("使用两插电源供电");
    }
}

// 类适配器:两插适配三插
class TwoToThreePlugAdapter extends TwoPlugDevice implements ThreePlugDevice {
    @Override
    public void powerWithThreePlug() {
        // 调用两插电器的供电方法
        this.powerWithTwoPlug();
    }
}

// 测试类
public class ClassAdapterTest {
    public static void main(String[] args) {
        ThreePlugDevice device = new TwoToThreePlugAdapter();
        device.powerWithThreePlug(); // 输出:使用两插电源供电
    }
}

类图:

在这里插入图片描述

对象适配器

实现方式:适配器类持有一个要适配的对象,并通过调用这个对象的接口来实现目标接口。

优点:不依赖于多继承,适配器可以适配多个类,只需要持有不同的对象即可,灵活性较高。

缺点:需要通过对象调用接口,层次较深,代码可能稍微复杂一些。

// 目标接口:三插电器
interface ThreePlugDevice {
    void powerWithThreePlug();
}

// 被适配的类:两插电器
class TwoPlugDevice {
    public void powerWithTwoPlug() {
        System.out.println("使用两插电源供电");
    }
}

// 对象适配器:两插适配三插
class TwoToThreePlugAdapter implements ThreePlugDevice {
    private TwoPlugDevice twoPlugDevice;

    public TwoToThreePlugAdapter(TwoPlugDevice twoPlugDevice) {
        this.twoPlugDevice = twoPlugDevice;
    }

    @Override
    public void powerWithThreePlug() {
        // 调用两插电器的供电方法
        twoPlugDevice.powerWithTwoPlug();
    }
}

// 测试类
public class ObjectAdapterTest {
    public static void main(String[] args) {
        TwoPlugDevice twoPlug = new TwoPlugDevice();
        ThreePlugDevice adapter = new TwoToThreePlugAdapter(twoPlug);
        adapter.powerWithThreePlug(); // 输出:使用两插电源供电
    }
}

类图:

在这里插入图片描述

接口适配器

接口适配器模式(也叫作缺省适配器模式默认适配器模式)是适配器模式的另一种变体,适用于当我们只需要实现接口的一部分功能时,而不想为接口中的每个方法都提供实现。

场景举例

比如,你有一个接口定义了多个方法,但是你只对其中几个方法感兴趣,其他的方法对你来说并不需要。那么,如果直接实现接口,你就必须实现所有的方法,即使有的方法是空实现。这时接口适配器模式就很有用,它可以提供一个默认的实现,这样你就不需要为每个方法都写实现,只需要覆盖你关心的那些方法

形象的例子:活动监听器

设想一下,你在编写一个窗口应用程序,用户可以通过鼠标或键盘进行操作。你需要监听这些事件,但你只对鼠标点击感兴趣,而鼠标移动、键盘按键等事件对你无关紧要。这时,如果你实现一个包含所有事件监听方法的接口,你需要实现很多和你不相关的方法。接口适配器模式就像是一个“监听器适配器”,帮你提供了默认的实现,让你只需要专注于鼠标点击事件

接口适配器模式实现方式

接口适配器模式通过创建一个抽象类,这个抽象类实现接口,并为接口中的所有方法提供一个默认实现(通常是空实现)。然后,具体的子类可以根据需要选择性地覆盖其中的某些方法。

// 定义一个接口,包含多个用户行为的方法
interface UserActionListener {
    void onClick();
    void onDoubleClick();
    void onRightClick();
    void onMove();
}

// 定义一个抽象类,实现接口并提供空的默认实现
abstract class UserActionAdapter implements UserActionListener {
    @Override
    public void onClick() {}
    
    @Override
    public void onDoubleClick() {}
    
    @Override
    public void onRightClick() {}
    
    @Override
    public void onMove() {}
}

// 创建一个子类,只关心 onClick() 事件
class ClickAction extends UserActionAdapter {
    @Override
    public void onClick() {
        System.out.println("鼠标单击事件被触发");
    }
}

// 测试类
public class InterfaceAdapterTest {
    public static void main(String[] args) {
        UserActionListener action = new ClickAction();
        action.onClick();          // 输出:鼠标单击事件被触发
        action.onMove();           // 什么也不会发生,因为没有重写 onMove()
    }
}

类图:

在这里插入图片描述

优点

简化类的实现:通过提供一个抽象适配器类,避免实现类必须覆盖所有接口方法,只需要关注自己感兴趣的部分即可。

灵活性高:子类可以按需选择性地覆盖接口中的某些方法,适应不同的需求。

易于扩展:如果接口中方法增多,通过适配器可以更方便地实现

使用场景

  1. 多方法接口的部分实现:当我们需要实现一个包含很多方法的接口,但只关注其中少部分方法时,接口适配器模式非常有用。
  2. 简化回调类:在事件驱动编程中,尤其是像 GUI 或监听器编程,适配器可以简化代码,使我们只关心自己需要处理的事件。

三种适配器模式的区别对比

比较维度类适配器对象适配器接口适配器
实现方式通过继承来实现适配通过组合来实现适配通过抽象类提供默认实现适配
适用场景当需要适配的类和目标接口有较强的关联当需要适配的类和目标接口没有关联,且需要灵活适配多个类当接口有多个方法,而子类只需实现部分方法
灵活性较低,因为 Java 不支持多继承较高,可以适配多个不同类非常高,子类可选择实现任意接口方法
代码复杂度较低,代码直接继承和重写较高,需要组合对象并实现接口适中,使用抽象类提供空实现
是否支持多继承不支持(因为 Java 不支持多继承)支持(通过组合方式实现)不涉及多继承问题,通过抽象类适配
实现对象适配一个类可以适配多个类适配接口,允许选择性实现部分方法
是否修改原类代码不需要修改原类代码,但使用继承不需要修改原类代码,使用对象持有不修改原类或接口,只需继承抽象类
适配器本质适配器是被适配类的子类适配器是独立的类,通过组合持有被适配类适配器是抽象类的子类

适配器模式选择的场景总结:

  1. 类适配器
    • 当你只需要适配一个类,并且你可以使用继承进行扩展时,这种方式最简单。
    • 缺点是受限于 Java 的单继承限制,无法同时继承多个类。
  2. 对象适配器
    • 当你需要适配多个类,或适配类之间没有继承关系时,使用对象适配器更加灵活。
    • 通过组合方式,可以避免多继承的局限性,适应复杂场景。
  3. 接口适配器
    • 当接口中有多个方法,而你只需要实现其中一部分时,这种方式非常有用。
    • 通过抽象类提供默认实现,子类可以选择性实现感兴趣的方法,而不必关心其他方法。

源码中的应用

Java I/O (InputStream 和 Reader)

Java 的 I/O 库广泛使用了适配器模式来适配不同的输入输出流。

例子: InputStreamReader

  • 作用InputStreamReaderInputStream(字节流)适配为 Reader(字符流),这是典型的适配器模式。
  • 实现原理InputStreamReader 通过组合 InputStream 对象,并将字节流转换为字符流,适配了字符流的接口

代码示例:

InputStream input = new FileInputStream("input.txt");
Reader reader = new InputStreamReader(input, "UTF-8");

SpringMVC 框架

例子 1:HandlerAdapter

  • 作用:在 Spring MVC 中,HandlerAdapter 是典型的适配器模式,用于将不同类型的处理器(如 Controller)适配为统一的 Handler 接口,使得框架可以使用统一的方式处理多种类型的请求。
  • 实现原理:不同的控制器类型(如 ControllerHttpRequestHandler 等)通过对应的 HandlerAdapter 实现适配,屏蔽了多种控制器处理请求的差异。

例子 2:DispatcherServlet

  • 作用DispatcherServlet 作为 Spring MVC 的核心,将各种请求路由到不同的控制器,而这些控制器可能具有不同的接口和功能。Spring 通过适配器模式来统一处理这些控制器请求

JDBC

JDBC 是 Java 访问数据库的 API,提供了统一的接口来处理不同的数据库系统。

例子:DriverManager

  • 作用:JDBC 的 DriverManager 是通过适配器模式将不同数据库驱动的实现类适配为统一的 Driver 接口。通过这种方式,不同的数据库(如 MySQL、PostgreSQL、Oracle)可以通过相同的 JDBC API 进行访问。

实现原理:不同的数据库厂商提供自己的 Driver 实现,通过 JDBC 的适配机制,开发者可以用相同的 API 进行操作

代码示例:

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");

SLF4J(Simple Logging Facade for Java)

SLF4J 是另一个常用的日志框架,类似于 Apache Commons Logging,但更为简洁和高效。

例子:LoggerAdapter

  • 作用:SLF4J 通过适配器模式,将不同的日志实现(如 Log4jLogback 等)统一适配为一个通用的日志接口 Logger。开发者通过统一的接口进行日志调用,而不需要关心底层实现。
  • 实现原理:不同的日志系统提供各自的实现,SLF4J 通过 LoggerAdapter 进行适配,从而实现日志系统的无缝切换。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2124801.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

文心快码前端工程师观点分享:如何保证在企业内落地?

🎁👉点击进入文心快码 Baidu Comate 官网,体验智能编码之旅,还有超多福利!🎁 本系列视频来自百度工程效能部的前端研发经理杨经纬,她在由开源中国主办的“AI编程革新研发效能”OSC源创会杭州站1…

【3.3 激活函数(下)】

3.3 激活函数(下) 深度学习中的激活函数 在深度学习中,激活函数扮演着至关重要的角色,它们为神经网络引入了非线性特性,从而增强了模型的表达能力。以下是几种常见的激活函数及其简介、代码实现、优缺点等详细介绍。 …

深入理解AI Agent架构,史上最全解析!赶紧码住!

AI Agent框架(LLM Agent):LLM驱动的智能体如何引领行业变革,应用探索与未来展望 1. AI Agent(LLM Agent)介绍 1.1. 术语 Agent:“代理” 通常是指有意行动的表现。在哲学领域,Agen…

计算机网络 ---- 电路交换、报文交换、分组交换

目录 零、前言 一、计算机网络发展初期面临的问题 1.1 电路交换的主要特点【电话网络采用电路交换技术】 1.1.1 电路交换的基本知识介绍 1.1.2 电路交换的优缺点 1.3 报文交换技术的特点【电报网络采用报文交换技术】 1.3.1 报文交换的基本知识介绍 1.3.2 报文交换技术…

中国企业500强!最新名单揭晓→

9月11日,在2024中国500强企业高峰论坛上,中国企业联合会、中国企业家协会连续第23次向社会发布了“中国企业500强”榜单。 01 营收规模迈上新台阶 2024中国企业500强营业收入迈上新台阶,突破了110万亿元大关,达110.07万亿元&…

【算法】哈希表相关

【ps】本篇有 5 道 leetcode OJ。 一、算法简介 哈希表是一种存储数据的容器,可以快速查找某个元素,其查找的时间复杂度为 O(1),非常合适需要频繁查找某一个元素的场景。其具体用法为: 直接使用底层为哈希表的 STL 容器。用数组…

【一起学Rust | 进阶篇】使用Bon快速生成builder,提高代码质量

文章目录 前言一、安装Bon二、使用步骤1.为方法实现builder2.实现关联方法的builder3. 为结构体实现builder4. Option<T>字段成为可选项5. 实现Into转化 参考文档 前言 在 Rust 编程语言中&#xff0c;除了广为人知的单例模式之外&#xff0c;工厂模式也是极为容易见到的…

智能制造云平台---附源码79117

目 录 摘要 1 绪论 1.1 研究背景和意义 1.2开发技术 1.2.1 Flask框架 1.2.2 Python简介 1.2.3 MySQL数据库 1.3论文结构与章节安排 2系统分析 2.1 可行性分析 2.2总体设计原则 2.3 系统流程分析 2.3.1 用户登录流程 2.3.2 删除信息流程 2.4 系统角色分析 2.5 系…

微信小程序 - 最新将页面可分享到朋友圈功能,小程序实现分享到朋友圈功能,开启分享给好友及分享朋友圈功能(微信小程序怎么分享到朋友圈?详细流程及示例代码教程)

前言 没有设置过分享朋友圈功能的小程序,分享朋友圈按钮是灰色且无法点击。 在微信小程序开发中,详解实现把页面开启朋友圈分享功能,解决 “当前页面未设置分享” 且灰色无法点击问题,微信小程序开发中的微信分享和朋友圈分享设置,提供详细代码。 小程序 vue2 | vue3 版本…

yum下载软件失败:‘Could not resolve host: mirrorlist .centos .org; Unknowm error

Loaded plugins: fastestmirror, ovl Determining fastest mirrors Could not retrieve mirrorlist http://mirrorlist.centos.org/?release7&archx86_64&repoos&infracontainer error was 14: curl#6 - “Could not resolve host: mirrorlist.centos.org; Unknow…

Games101图形学学习笔记——图形学基础

这里写目录标题 图形学基础线性代数Vector向量向量的点乘向量的叉乘 矩阵 Transform3D变换视图/相机变换 正交投影和透视投影正交投影透视投影 图形学基础 基础数学&#xff1a;线性代数&#xff0c;几何 基础物理&#xff1a;力学&#xff0c;光学 其他&#xff1a;信号处理&…

代码随想录训练营 Day56打卡 图论part06 108. 冗余连接 109. 冗余连接II

代码随想录训练营 Day56打卡 图论part06 一、卡码108. 冗余连接 题目描述 有一个图&#xff0c;它是一棵树&#xff0c;他是拥有 n 个节点&#xff08;节点编号1到n&#xff09;和 n - 1 条边的连通无环无向图&#xff08;其实就是一个线形图&#xff09;&#xff0c;如图&…

Java面试篇基础部分-Java的类加载机制

JVM的类加载 JVM在运行Java文件的时候,类加载分为5个阶段:加载、验证、准备、解析、初始化。在类初始化加载完成之后,就可以使用这个类的信息了。当这个类不需要使用的时候,就可以从JVM进行卸载。 加载 加载是指JVM读取Class文件的操作,并且根据Class的文件描述创建对应的…

工厂安灯系统在优化生产流程上的优势丨深圳讯鹏科技

工厂安灯系统通过可视化的方式&#xff0c;帮助工厂管理者和操作工人及时了解生产状态&#xff0c;快速响应问题&#xff0c;从而优化生产流程。 一、安灯系统实时监控与反馈 安灯系统的核心功能是实时监控生产线的状态。通过在生产现场设置灯光、显示屏等设备&#xff0c;工人…

单片机拍照_将采集的RGB图像封装为BMP格式保存到SD卡

文章目录 一、前言二、BMP文件结构2.1 BMP图片的格式说明 2.2 RGB888与RGB565格式是什么&#xff1f;&#xff08;1&#xff09;RGB565&#xff08;2&#xff09;RGB888&#xff08;3&#xff09;区别&#xff08;4&#xff09;如何构成&#xff08;5&#xff09;示例 三、实现…

yolo训练出现Could not load library libcudnn_cnn_train.so.8问题及解决方法

问题场景&#xff1a; 训练yolov5或者yolov8时候会报错&#xff1a; Could not load library libcudnn_cnn_train.so.8. Error: /usr/local/cuda-12.1/lib64/libcudnn_cnn_train.so.8: uined symbol: _ZN5cudnn3cnn34layerNormFwd_execute_internal_implERKNS_7backend11Vari…

【EasyExcel】@ColumnWidth(value = 20) EasyExcel设置列宽不生效

经过测试发现&#xff0c;只有XLS&#xff0c;ColumnWidth注解才会生效&#xff0c;选择CSV和XLSX都不会生效 //对应的导出实体类 EasyExcel.write(outputStream, Result.class)//excel文件类型&#xff0c;包括CSV、XLS、XLSX.excelType(ExcelTypeEnum.XLS)

当你学会了Python,随手爬取电影榜单!

一、爬电影TOP250 python爬取电影TOP250数据&#xff01; 首先&#xff0c;打开电影TOP250的页面&#xff1a; https://movie.douban.com/top250 开发好python代码后&#xff0c;成功后的csv数据&#xff0c;如下&#xff1a; 代码是怎样实现的爬取呢&#xff1f;下面逐一讲…

计算机网络相关概念

名词解释&#xff1a; 1.ARPANET ARPANET&#xff08;Advanced Research Projects Agency Network&#xff09;是由美国国防部高级研究计划局&#xff08;ARPA&#xff09;在1969年启动的一个实验性计算机网络项目。它是世界上第一个分组交换网络&#xff0c;也是互联网的前身…