一文彻底带你搞懂什么是适配器模式!!

news2024/11/15 12:00:46

一文彻底带你搞懂什么是适配器模式!!

    • 什么是适配器模式?
    • 适配器的两种实现方式
    • 适用情况
    • 代码示例
      • 背景
      • 类适配器
      • 对象适配器
    • IO流中的实际应用
        • 应用扩展
    • 总结

什么是适配器模式?

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。

主要目的是解决在不改变现有代码的情况下,使不兼容的接口之间能够正常工作,通过创建一个中间转换的适配器来将一个对象转换成我们所需要的接口

例如下图的 USB 转 Type-C 就是一个适配器
在这里插入图片描述

适配器的两种实现方式

通常有两种方式实现适配器模式,一种是类适配器,类适配器目前已不太使用,另一种实现方式是对象适配器,通常情况下采用对象适配器会使得代码更易扩展与维护。

不管采用何种方式,其基本的实现思想都是:对现有接口的实现类进行扩展,使其实现客户期望的目标接口。

类适配器通过继承现有接口类并实现目标接口,这样的话会使得现有接口类完全对适配器暴露,使得适配器具有现有接口类的全部功能,破坏了封装性。此外从逻辑上来说,这也是不符合常理的,适配器要做的是扩展现有接口类的功能而不是替代。

对象适配器持有现有接口类一个实例,并扩展其功能,实现目标接口。这是推荐的方式,优先采用组合而不是继承,会使得代码更利于维护。

适用情况

  1. 接口不兼容:你有两个接口或类,它们原本不兼容,但你又想让它们能够一起工作。

  2. 希望复用代码:你已经有一个功能完整的类或接口,但接口不符合新的需求。通过适配器,你可以重用现有的代码,而不是重新实现一个新的版本。

  3. 第三方集成:你可能需要使用第三方库或类,但第三方库的接口与你自己的接口不兼容。使用适配器可以轻松地将第三方库集成到你的系统中。

代码示例

背景

美国的正常供电电压为110V,一个中国人带了一款中国制造电器去美国,这个电器必须要在220V电压下才能充电使用。

这种情况下,客户(中国人)的期望接口是有一个220V的电压为电器充电,但实际的接口是仅有一个110V的电压供电器充电,所以就需要采用一根电压转换器(适配器)使得110V的电压能够转换为220V的电压,供客户使用。

  • Target:客户期望获得的功能接口(220V电压供电)。
  • Cilent:客户,期望访问Target接口(客户期望能有220V电压)。
  • Adaptee:现有接口,这个接口需要被适配(现有110V电压供电,需要被适配至220V)。
  • Adapter:适配器类,适配现有接口使其符合客户需求接口(适配110V电压,使其变为220V电压)。

在适配器模式中,Adapter扩展Adaptee以实现对应功能,Cilent调用Adapter以获得相应功能。

类适配器

类适配器结构图:

在这里插入图片描述

详细附代码图:

在这里插入图片描述

// 定义一个目标接口,它声明了期望实现的充电方法chargeBy220V
interface Target {
    void chargeBy220V();
}

// 定义一个被适配的类接口,它有chargeBy110V充电方法
interface Adaptee {
    void chargeBy110V();
}

// 创建一个美国供电器类,它实现了被适配的类接口,提供110V充电方法
class AmericanCharger implements Adaptee {
    @Override
    public void chargeBy110V() {
        System.out.println("美国供电器,为您服务,正在给您充 110V 电压");
    }
}

// 创建一个适配器类,它继承了美国供电器类,并且实现了目标接口
class Adapter extends AmericanCharger implements Target {
    // 适配器重写目标接口的chargeBy220V方法,
    // 调用被适配类的chargeBy110V方法,并额外转换电压
    @Override
    public void chargeBy220V() {
        super.chargeBy110V(); // 调用被适配类的110V充电方法
        System.out.println("再加 110V,现在给您充 220V 电压!!"); // 实现额外的电压转换逻辑
    }
}

// 测试类,测试适配器模式
public class Client{
    public static void main(String[] args) {
        // 创建适配器对象,并通过目标接口调用chargeBy220V方法
        new Adapter().chargeBy220V();
    }
}

在这里插入图片描述

这种类适配器可能会让人误以为适配器本身就是提供110V充电功能的美国供电器,而忽略了它实际上是一个转换器,逻辑上容易混乱和让人混淆

对象适配器

在这里插入图片描述

在这里插入图片描述

/**
 * 定义一个目标接口Target,包含一个充电方法chargeBy220V,该方法用于充电220V电压。
 */
interface Target {
    void chargeBy220V(); // 定义接口方法,使用220V电压充电
}

/**
 * 定义一个适配者接口Adaptee,包含一个充电方法chargeBy110V,该方法用于充电110V电压。
 */
interface Adaptee {
    void chargeBy110V(); // 定义接口方法,使用110V电压充电
}

/**
 * 实现适配者接口Adaptee的AmericanCharger类,代表美国的充电器,提供110V的充电服务。
 */
class AmericanCharger implements Adaptee {
    @Override
    public void chargeBy110V() { // 覆写chargeBy110V方法,输出充电信息
        System.out.println("美国供电器,为您服务,正在给您充 110V 电压");
    }
}

/**
 * 实现目标接口Target的Adapter类,通过持有Adaptee对象来适配不同的电压充电需求。
 */
class Adapter implements Target {
    private Adaptee adaptee; // 私有变量,用于持有Adaptee类型的对象

    /**
     * 构造器,接收一个Adaptee对象以便在内部调用其方法。
     * @param adaptee 适配者对象
     */
    public Adapter(Adaptee adaptee) { // 构造器初始化适配器,并传入适配者对象
        this.adaptee = adaptee;
    }

    @Override
    public void chargeBy220V() { // 覆写chargeBy220V方法,实现适配功能
        adaptee.chargeBy110V(); // 首先使用110V充电
        System.out.println("再加 110V,现在给您充 220V 电压!!"); // 然后输出适配后的信息
    }
}

/**
 * 测试类Test,用于演示对象适配模式的使用。
 */
public class Client{
    public static void main(String[] args) {
    
        Adaptee adaptee = new AmericanCharger(); // 创建一个AmericanCharger对象

        Adapter adapter = new Adapter(adaptee); // 使用AmericanCharger对象来创建Adapter对象

        adapter.chargeBy220V(); // 通过Adapter对象调用chargeBy220V方法,输出适配后的充电信息
    }
}

在这里插入图片描述

对象适配器采用组合的方式实现对现有接口的扩展以达到客户期望的接口。

IO流中的实际应用

让我们来看JavaIO流中的一个实例:

FileInputStream fis = new FileInputStream("qe");
InputStreamReader isrAdapter = new InputStreamReader(fis);
BufferedReader bf = new BufferedReader(isrAdapter);

BufferedReader (此处也就是客户)需要读取文件字符流进行工作,读取文件字符流就是客户的需求部分,但是根据现有的接口,想要读取文件就只能读取字节流

FileInputStream 就是现有接口的一个具体实现类,为了满足客户的需求,我们要对现有的接口进行适配,

InputStreamReader 就是一个适配器,它持有一个现有接口类的实例,通过这个实例读取文件字节流并将其扩展为字符流以满足客户的需求,这是标准的对象适配器模式。

如果仔细研究源码,发现JavaIO库将适配器定义为抽象的,并由具体的适配器继承该抽象适配器,如这里的 InputStreamReader 就是具体的适配器之一。

应用扩展

如果实现适配有多种方式的话,我们可以将适配器类Adapter声明为抽象类,并由子类扩展它:

在这里插入图片描述

在这里插入图片描述

/**
 * 定义一个目标接口Target,
 * 包含一个充电方法chargeBy220V,该方法用于充电220V电压。
 */
interface Target {
    void chargeBy220V(); // 目标接口的充电方法,使用220V电压充电
}

/**
 * 定义一个适配者接口Adaptee,
 * 包含一个充电方法chargeBy110V,该方法用于充电110V电压。
 */
interface Adaptee {
    void chargeBy110V(); // 适配者接口的充电方法,使用110V电压充电
}

/**
 * 实现适配者接口Adaptee的AmericanCharger类,
 * 代表美国的充电器,提供110V的充电服务。
 */
class AmericanCharger implements Adaptee {
    @Override
    public void chargeBy110V() {
        System.out.println("美国供电器,为您服务,正在给您充 110V 电压");
    }
}

/**
 * 抽象类Adapter,作为适配器的基础类,实现目标接口Target。
 */
abstract class Adapter implements Target {
    Adaptee adaptee; // 私有变量,用于持有Adaptee类型的对象

    /**
     * 构造器,接收一个Adaptee对象以便在内部调用其方法。
     * @param adaptee 适配者对象
     */
    public Adapter(Adaptee adaptee) { // 构造器初始化适配器,并传入适配者对象
        this.adaptee = adaptee;
    }
}

/**
 * 实现Adapter类的具体子类ChinaMakeAdapter,
 * 用于将美国充电器的110V充电适配为中国标准的220V充电。
 */
class ChinaMakeAdapter extends Adapter{

    public ChinaMakeAdapter(Adaptee adaptee){
        super(adaptee); // 调用父类的构造器
    }

    /**
     * 实现目标接口Target的chargeBy220V方法,
     * 通过调用适配者接口Adaptee的chargeBy110V方法来实现。
     */
    @Override
    public void chargeBy220V() {
        adaptee.chargeBy110V(); // 调用适配者的110V充电方法
        System.out.println("再加110V,达到220V,认准中国制造!"); // 输出说明适配器正在工作
    }
}

/**
 * 测试类Client,用于演示对象适配器模式的使用。
 */
public class Client{
    public static void main(String[] args) {
    	// 创建一个AmericanCharger对象
        Adaptee adaptee = new AmericanCharger(); 
         // 使用AmericanCharger对象来创建ChinaMakeAdapter对象
        Adapter adapter = new ChinaMakeAdapter(adaptee);
        // 通过ChinaMakeAdapter对象调用chargeBy220V方法,输出适配后的充电信息
        adapter.chargeBy220V(); 
    }
}


在这里插入图片描述

总结

优点:

  1. 解耦合:适配器模式允许不相关的类或接口协同工作,这为系统的不同部分提供了更大的灵活性。
  2. 复用:通过适配器,可以复用现有的类,而不需要修改它们的源代码。
  3. 透明性:适配器提供了一种封装机制,允许客户端代码与目标接口交互,而不必关心实际的实现类。
  4. 灵活性:适配器模式提供了很大的灵活性,因为它允许在运行时动态地结合不同的类和接口。

缺点:

  1. 过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
  2. 适配可能复杂或不可能:有时,适配两个不兼容的接口可能非常困难,甚至不可能,比如让母猪飞上天。

当我们有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。

注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题,即现有接口可能无法改变(去美国不可能把人家110V电压供给改成220V电压供给)。

往期设计模式专题文:

一文带你彻底搞懂什么是代理模式!!

一文带你彻底搞懂设计模式之单例模式!!由浅入深,图文并茂,超超超详细的单例模式讲解!!

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

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

相关文章

web自动化(六)unittest 四大组件实战(京东登录搜索加入购物车)

Unittest框架 Unittest框架:框架测试模块测试管理模块测试统计模块,python的内置模块 import unittest Unittest框架四大组件: 1、TestCase 测试用例 2.TestFixture 测试用例夹具 测试用例需要执行的前置和后置 3.TestSuite 测试套件 把需要执行的测试用例汇总在一…

什么是企业服务总线?它包含哪些技术组件?

我们每个人都会去医院,您描述下我们去医院的场景,然后引出这个挂号流程,通过挂号流程中的一个问题或者什么东西来吸引他的好奇心,这样呢?会比现在的预设场景好一些。我举个例子,人工智能怎么帮人看病。如果…

前端面试题23(css3)

关于CSS3的面试题,我们可以从多个维度来探讨,包括但不限于选择器、盒模型、布局技术、动画与过渡、响应式设计等。下面我会列举一些典型的CSS3面试问题,并尽可能提供详细的解答或示例代码。 1. CSS3中新增了哪些选择器? 答案: C…

【Java安装】windows10+JDK21+IDEA

文章目录 一、JDK安装1. 下载完成后按照自己需要的位置安装2. 配置环境变量2.1 JAVA_HOME变量2.2 PATH配置 3. 验证4. helloworld 二、IDEA安装三、IDEA-HelloWorld 一、JDK安装 JDK安装链接 1. 下载完成后按照自己需要的位置安装 2. 配置环境变量 2.1 JAVA_HOME变量 安装…

手机飞行模式是什么意思?3个方法教你如何开启

在现代生活中,手机已经成为我们日常生活中不可或缺的一部分。然而,有时我们需要暂时切断手机的通信功能,比如在飞机上、开会时或需要安静休息的时候。这时候,苹果手机上的“飞行模式”功能就派上了用场。 那么,手机飞…

【从零到一,如何搭建本地AI大模型】

摘要: 本文主要记录这一段时间对本地大模型搭建的心得。 作为一个资深程序员,在AI席卷全球的时候,深深感觉到了一丝危机感,不禁有一个想法不断在脑海闪现:我会不会真的哪一天被AI给取代了? 从哪入手 程序员出生的我,掌握了很多语言,从前端到数据库,再到运维,基本都…

uniapp-小程序获取用户位置

1. 需要在微信公众平台进行接口的申请。选择自己需要用的接口。 2. 在app.json文件中配置permission和requiredPrivateInfos。requiredPrivateInfos里面是你需要使用的接口。 3. 配置完成后,就可以使用了。 相关获取位置API的链接 4. 如果要获取当前位置到某一个指…

VS 附加进程调试

背景: 此方式适合VS、代码和待调试的exe在同一台机器上。 一、还原代码到和正在跑的exe同版本 此操作可以保证能够调试生产环境的exe 二、设置符号路径 1.调试->选项 三、附加进程 方式1: 打开VS,调试->附加到进程,出…

【MySQL】MySQL连接池原理与简易网站数据流动是如何进行

MySQL连接池原理与简易网站数据流动是如何进行 1.MySQL连接池原理2.简易网站数据流动是如何进行 点赞👍👍收藏🌟🌟关注💖💖 你的支持是对我最大的鼓励,我们一起努力吧!😃&#x1f60…

【Go】常见的变量与常量

变量 常见的变量声明方式 一、声明单个变量的多种方式 1.声明一个变量初始化一个值 //声明变量 默认值是0,var a int//初始化一个值a 1fmt.Println(a) 2. 在初始化的时候省去数据类型,通过值自动匹配当前的变量的数据类型 var b 2fmt.Println(&quo…

7月6日 VueConf 技术大会即将在深圳举办

7月6日,VueConf 2024 即将在深圳召开,本次大会正值 Vue.js 十周年,旨在聚焦 Vue.js 社区的成员,分享最新的技术动态、经验以及创新实践。 本次参与 VueConf 大会的是来自全球 Vue.js 核心团队成员、行业专家及前端开发者。其中&a…

Java语言程序设计——篇二(1)

Java语言基础 数据类型关键字与标识符关键字标识符 常量与变量1、常量2、变量 类型转换自动类型转换强制类型转换 数据类型 数据的基本要素数据的性质(数据结构)数据的取值范围(字节大小)数据的存储方式参与的运算 Java是一门强类…

NodeJS蔬菜自产零售混合销售平台-计算机毕业设计源码10149

摘 要 随着移动互联网的快速发展,购物方式也发生了巨大的变化。蔬菜作为消费者生活中必不可少的商品之一,在移动互联网时代也迎来了新的购物方式——购物小程序。购物小程序是一种基于手机应用平台的轻量级应用程序,用户可以通过它方便地浏览…

C++第二弹 -- C++基础语法下(引用 内联函数 auto关键字 范围for 指针空值)

本篇大纲 前言一. 引用续讲1. 传值,传引用效率对比2. 类型转换和表达式传引用的注意事项3. 引用与指针 二. 内联函数1. 概念2. 特性3. 面试题 三. auto关键字(C11)1. 类型别名思考2. auto简介3. auto的使用细则4. auto不能推导的场景 四. 基于范围的for循环(C11)1. 范围for的语…

3DMAX软件如何导出和导入模型

在3DMAX软件中导出和导入模型的过程相对直观,以下是具体的步骤:导出模型:1、选择模型:首先,在3DMAX的视图中选择你想要导出的模型。2、导出设置:点击菜单栏中的“文件”(File)&#…

现货黄金技术出现这一信号赶紧止损!

很多现货黄金投资者都并不知道,移动平均线除了可以用于寻找进场的机会,还可以用来设置止损,让自己在交易中更好地进行防守。其实移动平均线止损,是常用的技术止损方法之一,本文将和大家分享怎样利用均线设置止损点&…

鸿蒙开发:Universal Keystore Kit(密钥管理服务)【加密导入密钥(C/C++)】

加密导入密钥(C/C) 以加密导入ECDH密钥对为例,涉及业务侧加密密钥的[密钥生成]、[协商]等操作不在本示例中体现。 具体的场景介绍及支持的算法规格。 在CMake脚本中链接相关动态库 target_link_libraries(entry PUBLIC libhuks_ndk.z.so)开发步骤 设备A&#xf…

【日记】我就是世界上最幸福的人!(1124 字)

正文 今天想写的内容有点多,就不写在纸上了。 首先,最高兴的,还是我们的《艾尔登法环》有了进展。我和兄长终于通过了 “火山官邸:地底拷问所”。我真是不知道,我和他在这个地方被那两个掳人少女人拷问了多少次了。不仅…

笔记本电脑投屏怎么操作?一看就会!

日常工作或办公都会用到笔记本电脑,但很多新手用户不知道笔记本电脑的投屏要怎么操作?接下来系统之家给大家介绍三种简单的操作方法,帮助大家轻松完成笔记本电脑投屏投屏操作,从而满足自己的办公或学习使用需求。 方法一 1. 直接W…

QDockWidget类详解

一.QDockWidget类概述 1.QDockWidget类 QDockWidget类提供了一个特殊的窗口部件,它可以是被锁在QMainWindow窗口内部或者是作为顶级窗口悬浮在桌面上。 QDockWidget类提供了dock widget的概念,dock widget也就是我们熟悉的工具面板或者是工具窗口。Do…