设计模式之适配器模式:接口对接丝般顺滑(图代码解析面面俱到)

目录

  • 概要
    • 概念
    • 组成
    • 类图
    • 工作原理
    • 应用场景
    • 优点
  • 类型
    • 类适配器模式
    • 对象适配器模式
    • 两者区别
    • 示例代码
  • 实现(对象适配器详解)
    • 业务背景
    • 代码
  • 常见问题
    • 为什么有适配器模式
    • 适配器模式告诉我们什么
    • 适配器模式体现了哪些设计原则
    • 关联方式实现了逻辑继承
    • 适配器模式在SpringMVC框架应用
  • 总结

概要

概念

    适配器模式是一种结构型设计模式,用于将一个类的接口转换成客户端所期望的另一个接口。适配器模式通过创建一个适配器类,将原始接口转换为目标接口,使得两个不兼容的类可以协同工作。

组成

适配器模式由以下几个主要组件构成:

  • 目标接口(Target ):客户端期望的接口,适配器将原始接口转换为目标接口。
  • 原始接口(Adaptee ):需要被适配的类的接口。
  • 适配器(Adapter):实现目标接口,同时持有一个原始接口的引用,在目标接口方法中调用原始接口方法来完成适配。

类图

在这里插入图片描述

工作原理

  1. 适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容
  2. 从用户的角度看不到被适配者,是解耦的
  3. 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
  4. 用户收到反馈结果,感觉只是和目标接口交互,如图(关键词:src,adapter,dst )
    在这里插入图片描述

应用场景

    当需要使用一个已存在的类,但其接口与其他类不兼容时,可以使用适配器模式进行接口转换。
    当想要创建一个可复用的类,该类能与多个不相关的类或类层次结构协同工作时,可以使用适配器模式。

优点

1、可以让已存在的类与其他类协同工作,无需重写已有代码。
2、可以将实现细节封装在适配器中,对客户端隐藏具体的实现细节。
3、可以提高代码的可复用性和灵活性,适配器可以用于多种不同的上下文。

类型

类适配器模式

    在类适配器模式中,适配器实现目标接口并继承原始类,通过重写目标方法,调用原始类的方法来完成适配。这种方式需要多重继承支持,方便但局限性较大。

对象适配器模式

    在对象适配器模式中,适配器持有一个原始类的引用,并实现目标接口,目标方法调用原始类的方法来完成适配。这种方式不需要多重继承支持,更加灵活。

两者区别

    类适配器和对象适配器的区别在于它们如何与被适配类(Adaptee)进行连接。

    类适配器使用多重继承,同时继承目标接口(Target)和被适配类(Adaptee)。通过继承的方式,适配器类可以通过调用被适配类的方法来实现目标接口的方法。

    对象适配器使用关联(组合),在适配器类内部持有一个被适配类的实例。适配器类通过调用被适配类实例的方法来实现目标接口的方法。

    总结起来是:类适配器会继承被适配类的接口以及实现,然后将其转换为目标接口;而对象适配器则是利用组合的方式将被适配类的实例嵌入到适配器中,进而实现目标接口

二维表展示:

类适配器对象适配器
实现方式使用类的继承关系实现适配使用对象的组合关系实现适配
关联关系适配器类同时继承目标接口和被适配者类适配器持有被适配者对象的引用
可扩展性不支持适配多个被适配者类可以适配多个被适配者对象
适配方式通过继承实现适配,可以重写被适配者的方法通过组合实现适配,可以调用被适配者的方法
依赖关系类适配器依赖于被适配者类对象适配器依赖于被适配者对象

    注意:在C++等支持多重继承的语言中,适配器类可以同时继承目标接口和被适配类,从而实现对两者的继承。而在Java等不支持多重继承的语言中,可以通过实现目标接口和关联被适配类的方式来实现相同的逻辑。

    但是,由于多重继承可能会带来一些问题,如命名冲突、菱形继承等,因此在使用类适配器模式时需要注意继承关系的设计和维护,避免潜在的问题。

    还有一种说法是适配器分成三类,多了一个接口适配器,这三种方式,是根据adeptee在Adapter里的形式来分类,也是命名的由来。

  • 类适配器:在Adapter里,将adeptee当做类,来继承;
  • 对象适配器:在Adapter里,将adeptee作为一个对象,来持有
  • 接口适配器:在Adapter里,将adeptee作为一个接口,来实现

示例代码

    类适配器模式示例(java 单继承,用接口和类模仿继承自两个类的效果)

// 目标接口
interface Target {void request();
}// 原始类
class Adaptee {void specificRequest() {System.out.println("执行特殊请求");}
}// 类适配器
class ClassAdapter extends Adaptee implements Target {public void request() {specificRequest();}
}// 客户端代码
public class Client {public static void main(String[] args) {Target target = new ClassAdapter();target.request();}
}

     对象适配器模式示例

// 目标接口
interface Target {void request();
}// 原始类
class Adaptee {void specificRequest() {System.out.println("执行特殊请求");}
}// 对象适配器
class ObjectAdapter implements Target {private Adaptee adaptee;//持有一个被适配类的实例ObjectAdapter(Adaptee adaptee) {this.adaptee = adaptee;}public void request() {adaptee.specificRequest();}
}// 客户端代码
public class Client {public static void main(String[] args) {Adaptee adaptee = new Adaptee();Target target = new ObjectAdapter(adaptee);target.request();}
}

实现(对象适配器详解)

    以姚明在NBA打球为例讲解一下适配器模式

业务背景

    在姚明(外籍中锋)加入NBA之处,英文不熟悉,这时候就需要一位翻译在他和教练之间进行沟通。

代码

//抽象球员,包含attack和defense的方法
abstract class Player {protected String name;public Player(String name){this.name=name;}public Player(){};//空构造为的是表现翻译者的隐身public abstract void attack();//进攻public abstract void defense();//防守
}//外籍中锋
public class ForeignCenter {private String name;public String getName(){return this.name;}public void setName(String name){this.name=name;}public void 进攻(){System.out.println("外籍中锋"+name+"进攻");}public void 防守(){System.out.println("外籍中锋"+name+"防守");}
}//翻译(适配器)
public class Translator extends Player {private ForeignCenter foreignCenter = new ForeignCenter();public Translator(String name) {//super(name);foreignCenter.setName(name);//给姚明配的翻译,翻译一出生就转配给了姚明}@Overridepublic void attack() {foreignCenter.进攻();}@Overridepublic void defense() {foreignCenter.防守();}
}//客户端
public class Client {public static void main(String[] args) {Player center=new Translator("姚明");//翻译隐身System.out.println(center.name);//这里会显示nullcenter.attack();center.defense();//姚明其实听不懂这两句,需要翻译}
}

    这里有两个不容易觉察又很重要的地方:
    1、在翻译者类当中,attach和defense两个方法,实际分别调用的都是外籍中锋的进攻和防守方法,这里是用关联的方式实现的逻辑继承,关于这点在后面会细说。

    2、这里做了个小埋伏,让我们看到翻译者的“隐身”

在这里插入图片描述

在这里插入图片描述

    客户端给翻译者传的名字是姚明,结合业务就是教练叫姚明进攻,姚明听不懂,但是翻译听懂了,翻译再翻译给姚明,“姚明”通过翻译者的构造函数传给谁了呢?看下面的代码

在这里插入图片描述

    可以看到这个名字是赋给了外籍中锋,这里我特意在球员类里面添加了一个空的构造函数

在这里插入图片描述

    现在来看,客户端中的Translator表面看是Player类型,通过构造函数传进去的名字,也给了ForeignCenter,所以,Center.name就是null,这也是适配器的魅力所在,在无形中解决了接口不相容的问题。

常见问题

为什么有适配器模式

在这里插入图片描述

    适配器模式的主要目的是解决两个不兼容的接口之间的兼容性问题。在软件开发过程中,经常会遇到以下情况导致接口不兼容:

    系统需要使用已存在的类,但其接口与系统要求的接口不一致:当我们需要使用某个类的功能,但其接口与我们现有的系统接口不同,无法直接对接,这时候适配器模式可以通过创建适配器来将已存在的类的接口转换为系统要求的接口,从而使这个类能够被系统使用。

    需要复用一些功能类,但这些类的接口与系统接口不兼容:在系统设计过程中可能会存在一些功能优秀的类,我们希望能够将它们复用于系统中,但是由于这些类的接口与系统接口不一致,无法直接复用,这时候适配器模式可以通过创建适配器来将这些类的接口转换为系统接口,从而让它们能够被复用。

    适配器模式的出现可以降低代码的耦合性,使得不兼容的接口能够协调工作。它能够提高代码的可复用性和灵活性,并且将适配过程封装在适配器中,对客户端隐藏了具体的实现细节,符合面向对象设计原则中的封装和抽象原则。

适配器模式告诉我们什么

    在软件设计中尽量避免使用适配器模式,而是在接口设计阶段就考虑兼容性问题并设计好接口。会的目的是不出现这样的问题

    在软件设计中,应该尽可能提前考虑这些因素,并遵循良好的接口设计原则,以避免后期出现兼容性问题。这包括:

  • 接口设计要明确、简洁、易于理解和使用,遵循单一职责原则和接口隔离原则。
  • 合理规划系统的接口,预见可能的变化和需求,避免频繁修改接口。
  • 使用设计模式和设计原则来约束和指导接口设计,例如依赖倒置原则、开闭原则等。

    总之,尽管适配器模式在某些情况下是非常有用的,但在软件设计过程中,应该尽量避免使用适配器模式,而是通过良好的接口设计来解决兼容性问题。这样可以提高代码的可读性、可维护性和可扩展性,减少后期修改和重构的工作量。

适配器模式体现了哪些设计原则

    单一职责原则(Single Responsibility Principle):每个类都只有一个职责,Adaptee 是适配器模式中的已有类,Adapter 是适配器类,Target 是目标接口,它们各自承担不同的职责。适配器类的职责就是进行接口转换,只有一个职责。

    开闭原则(Open-Closed Principle):适配器模式通过引入适配器类,可以在不修改原有代码的情况下,实现与新接口的兼容,符合开闭原则的要求。

    接口隔离原则(Interface Segregation Principle):抽象目标接口 Target 只包含客户端所需的方法,避免了客户端直接依赖于不需要的方法。

关联方式实现了逻辑继承

    Adapter 类通过关联一个 Adaptee 对象来实现适配器功能。当 Adapter 的 request() 方法被调用时,它实际上会调用被适配类 Adaptee 的 specificRequest() 方法。(请配合上面的类图看)。适配器类(Adapter)包含一个被适配的类(Adaptee)的实例,并在目标接口的方法中调用被适配类的相应方法来实现适配。作为一个中间层,适配器类(Adapter)不仅提供了目标接口的实现,还继承了被适配类(Adaptee)的功能,实现了逻辑上的继承关系。当客户端调用适配器类的方法时,实际上是通过适配器类来调用被适配类的方法,这就体现了适配器类对被适配类的逻辑继承。

适配器模式在SpringMVC框架应用

    SpringMvc 中的 HandlerAdapter , 就使用了适配器模式
在这里插入图片描述
    使用 HandlerAdapter 的原因:处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用Controller方法,需要调用的时候就得不断是使用if else来进行判断是哪一种子类然后执行。那么如果后面要扩展Controller,就得修改原来的代码,这样违背了开闭原则。
    Spring 定义了一个适配接口,使得每一种 Controller 有一种对应的适配器实现类,适配器代替controller执行相应的方法,扩展 Controller 时,只需要增加一个适配器类就完成了SpringMVC的扩展了。

总结

    适配器模式是一种非常实用的设计模式,通过将不兼容的接口转换为目标接口,使得原本无法协同工作的类能够协作。它能够提高代码的复用性和灵活性,并且将实现细节封装在适配器中,对客户端隐藏具体的实现细节。但是,重点是学习适配器模式的目的是让我们尽量避免使用它,在软件设计中,应该尽可能提前考虑这些因素,并遵循良好的接口设计原则,以避免后期出现兼容性问题。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解C语言(1):数据在内存中的存储

文章主题:数据在内存中的存储🌏所属专栏:深入理解C语言📔作者简介:更新有关深入理解C语言知识的博主一枚,记录分享自己对C语言的深入解读。😆个人主页:[₽]的个人主页🏄&…

JavaSE | 初识Java(六) | 数组 (上)

数组的创建及初始化 T[] 数组名 new T[N]; //T:表示数组中存放元素的类型 //T[]:表示数组的类型 //N:表示数组的长度 int[] array1 new int[10]; // 创建一个可以容纳10个int类型元素的数组 double[] array2 new double[5]; // 创建一个可…

SDI-12协议与STM32 进行uart通信

场景是用stm32与一款温湿度传感器通信,不过是基于SDI-12协议,SDI-12时序和UART类似,故采用UART传输,原理图如下 其中DIR_OUT_SDI是一个IO引脚,控制UART_TX_SDI是否使能,U10是三态门IC,即拉低DIR…

Redis是否要分库的实践

Redis的分库其实没有带来任何效率上的提升,只是提供了一个命名空间,而这个命名空间可以完全通过key的设计来避开这个问题。 一个优雅的Redis的key的设计如下

YoloV5实时推理最短的代码

YoloV5实时推理最简单代码 import cv2 import torch# 加载YOLOv5模型 model torch.hub.load(ultralytics/yolov5, yolov5s)# 使用CPU或GPU进行推理 device cuda if torch.cuda.is_available() else cpu model.to(device)# 打开摄像头(默认摄像头) cap…

适合在校学生的云服务器有哪些?

随着云计算技术的发展,越来越多的学生开始使用云服务器来进行学习和实践。对于学生来说,选择一款便宜的云服务器不仅可以帮助他们降低成本,还可以提高学习和实践的效率。本文将介绍几款适合学生使用的便宜云服务器。 1、腾讯云学生服务器【点…

<Xcode> Xcode IOS无开发者账号打包和分发

关于flutter我们前边聊到的初入门、数据解析、适配、安卓打包、ios端的开发和黑苹果环境部署,但是对于苹果的打包和分发,我只是给大家了一个链接,作为一个顶级好男人,我认为这样是对大家的不负责任,那么这篇就主要是针…

[论文必备]最强科研绘图分析工具Origin(1)——安装教程

之前在论文中pr曲线和loss曲线对比用到了Origin这个最强科研绘图分析工具,被导师狠狠夸了,下面来分享一下~ 本篇先带你手把手安装这个软件,可以先点再慢慢看哦~ 目录 📢一、软件简介 🌻二、安装教程 &#x1f384…

Docker命令起别名

1.打开.bashrc文件 vi ~/.bashrc 2. 起别名 alias dpsdocker ps --format "table{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}" alias disdocker images 3. 文件生效 source ~/.bashrc 4.展示

CF505B Mr. Kitayuta‘s Colorful Graph

Mr. Kitayuta’s Colorful Graph 题面翻译 给出一个 n n n 个点, m m m 条边的无向图,每条边上是有颜色的。有 q q q 组询问 对于第 i i i 组询问,给出点对 u i , v i u_i,v_i ui​,vi​。求有多少种颜色 c c c 满足:有至…

在线OJ项目核心思路

文章目录 在线OJ项目核心思路1. 项目介绍2.预备知识理解多进程编程为啥采用多进程而不使用多线程?标准输入&标准输出&标准错误 3.项目实现题目API实现相关实体类定义新增/修改题目获取题目列表 编译运行编译运行流程 4.统一功能处理 在线OJ项目核心思路 1. 项目介绍 …

使用腾讯云服务器安装宝塔Linux面板教程_图文全流程

使用腾讯云服务器搭建网站全流程,包括轻量应用服务器和云服务器CVM建站教程,轻量可以使用应用镜像一键建站,云服务器CVM可以通过安装宝塔面板的方式来搭建网站,腾讯云服务器网分享使用腾讯云服务器建站教程,新手站长搭…

Linux基本指令(二)

💓博主个人主页:不是笨小孩👀 ⏩专栏分类:数据结构与算法👀 C👀 刷题专栏👀 C语言👀 🚚代码仓库:笨小孩的代码库👀 ⏩社区:不是笨小孩👀 🌹欢迎大…

MySQL 索引优化实践(单表)

目录 一、前言二、表数据准备三、常见业务无索引查询耗时测试3.1、通过订单ID / 订单编号 查询指定订单3.2、查询订单列表 四、订单常见业务索引优化实践4.1、通过唯一索引和普通索引优化通过订单编号查询订单信息4.2、通过普通联合索引优化订单列表查询4.2.1、分析查询字段的查…

YAMLException : java.nio.charset.MalformedInputException : Input length = 1

场景还原 有小伙伴反应SpringBoot项目启动异常,但是同组其他伙伴的无问题! ERROR org.springframework.boot.SpringApplication - Application run failedorg.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException : Inpu…

决策树C4.5算法的技术深度剖析、实战解读

目录 一、简介决策树(Decision Tree)例子: 信息熵(Information Entropy)与信息增益(Information Gain)例子: 信息增益比(Gain Ratio)例子: 二、算…

【初识Linux】上

初识Linux上 一、Linux背景1.1 UNIX发展的历史1.2 UNIX发展的历史 二、开源三、官网Linux官网 四、企业应用现状五、发行版本六、 os概念,定位 本博客简介 初始Linux操作系统初识shell命令 ,了解若干背景知识。使用常用Linux命令了解Linux权限概念与思想,能深度理解…

调用gethostbyname实现域名解析(附源码)

VC常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...&a…

世界前沿技术发展报告2023《世界信息技术发展报告》(七)网络与信息安全技术

(七)网络与信息安全技术 1. 概述2. 网络安全关键技术2.1 美国特种作战司令部举办网络挑战赛以寻找边缘安全技术2.2 英国卡迪夫大学开发出在1秒内阻止网络攻击的方法2.3 国研究人员开发出检测恶意网页的新方法2.4 韩国仁川大学研发出一种基于5G的人工智能…

管道-有名管道

一、有名管道 有名管道与匿名管道的不同: 有名管道提供了一个路径名,并以FIFO的文件形式存在于文件系统中。与匿名管道不同,有名管道可以被不相关的进程使用,只要它们可以访问该路径,就能够通过有名管道进行通信。 FI…