《程序猿之设计模式实战 · 适配器模式》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗

🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

      • 写在前面的话
      • 基础介绍
      • 代码实现
      • Spring 中的适配器模式
      • 适配器VS装饰者
      • 适配器实操补充
      • 总结陈词

写在前面的话

本篇文章继续介绍一下适配器模式,单词为Adapter。日常生活中适配器的场景也随处可见,例如USB、插座等转换头,或电压转换处理,总之,起中转适配作用的,都可以考虑用适配器模式。

工作中的场景就更不用说了,新老服务之间中转的桥梁、服务、工具,都可以称之为Adapter,那这个适配器模式到底是什么样的,且听我娓娓道来。

相关文章:
《程序猿之设计模式实战 · 策略模式》
《程序猿之设计模式实战 · 装饰者模式》
《程序猿之设计模式实战 · 池化思想》
《程序猿之设计模式实战 · 观察者模式》
《程序猿之设计模式实战 · 责任链模式》
《程序猿之设计模式实战 · 模板方法》


基础介绍

基础概念:

适配器模式(Adapter Pattern)是一种结构型设计模式,它允许将一个接口转换成客户端所期望的另一个接口。

适配器模式使得原本由于接口不兼容而无法一起工作的类可以一起工作。

主要组成:

适配器模式通常由以下几个角色组成:

  1. 目标接口(Target):客户端所期望的接口。
  2. 源类(Adaptee):需要被适配的类,通常是一个已有的类。
  3. 适配器(Adapter):实现目标接口,并持有源类的实例,负责将源类的接口转换为目标接口。

使用场景:

  • 当你希望使用一些现有的类,但它们的接口与您所需要的接口不兼容时。
  • 当你希望创建一个可重用的类,它可以与一些不相关的类(即接口不兼容的类)一起工作时。
  • 当你希望通过多个类的组合来实现某个功能,而这些类的接口不一致时。

总结一下:

适配器模式通过将不兼容的接口进行适配,使得不同的类可以协同工作。在 Spring 框架中,适配器模式的应用使得不同类型的处理器和视图能够以统一的方式进行处理,从而提高了系统的灵活性和可扩展性。


代码实现

挺简单的一段示例,也是对原有类的增强,使之可以间接成为目标接口。

// 目标接口
interface Target {void request();
}// 源类
class Adaptee {public void specificRequest() {System.out.println("Called specificRequest()");}
}// 适配器
class Adapter implements Target {private Adaptee adaptee;public Adapter(Adaptee adaptee) {this.adaptee = adaptee;}@Overridepublic void request() {// 调用源类的方法adaptee.specificRequest();}
}// 客户端代码
public class Client {public static void main(String[] args) {Adaptee adaptee = new Adaptee();Target target = new Adapter(adaptee);target.request(); // 输出: Called specificRequest()}
}

Spring 中的适配器模式

在 Spring 框架中,适配器模式被广泛应用于多个地方,尤其是在 MVC 模块中。以下是一些主要的应用场景:

1、HandlerMapping:Spring MVC 中的 HandlerMapping 使用适配器模式来将请求映射到处理器(Controller)。不同类型的处理器(如注解驱动的控制器、传统的控制器等)可以通过适配器进行统一处理。

2、HandlerAdapter:Spring MVC 中的 HandlerAdapter 是适配器的具体实现,它允许不同类型的处理器(如 SimpleControllerHandlerAdapter、AnnotationMethodHandlerAdapter 等)以统一的方式处理请求。

3、ViewResolver:在视图解析过程中,Spring 使用适配器模式来支持不同类型的视图(如 JSP、Thymeleaf 等)。

以HandlerMapping为例,是如何利用到适配器模式?

在 Spring 框架中,HandlerMapping 是一个重要的组件,它负责将 HTTP 请求映射到相应的处理器(Handler)。为了实现这一点,Spring 使用了适配器模式来处理不同类型的请求处理器(如控制器)。

适配器模式在 HandlerMapping 中的应用,步骤如下:

1、接口与实现

在 Spring 中,HandlerMapping 会将请求映射到一个处理器(Handler),而这个处理器可能是不同类型的对象,比如:

  • 控制器类(例如,使用 @Controller 注解的类)
  • 函数式处理器(例如,使用 @RequestMapping 注解的方法)

为了能够处理这些不同类型的处理器,Spring 使用了适配器模式。

2、适配器接口

Spring 定义了一个适配器接口,例如 HandlerAdapter,它为不同类型的处理器提供了统一的调用方式。这个接口定义了一个方法,比如 handle(),用于处理请求。

public interface HandlerAdapter {boolean supports(Object handler);ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}

3、具体适配器

Spring 提供了多个具体的适配器实现,例如:

RequestMappingHandlerAdapter:用于处理使用 @RequestMapping 注解的控制器方法。

SimpleControllerHandlerAdapter:用于处理实现了 Controller 接口的传统控制器。

每个适配器实现了 HandlerAdapter 接口,并提供了对特定类型处理器的支持。

public class RequestMappingHandlerAdapter implements HandlerAdapter {@Overridepublic boolean supports(Object handler) {return (handler instanceof RequestMappingHandler);}@Overridepublic ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 处理请求}
}

4、HandlerMapping 的工作流程

请求到达:当一个 HTTP 请求到达时,HandlerMapping 会根据请求的 URL 找到对应的处理器。

适配器选择:HandlerMapping 会遍历所有注册的 HandlerAdapter,找到一个支持找到的处理器的适配器。

请求处理:一旦找到合适的适配器,HandlerMapping 会调用适配器的 handle() 方法,传递请求和响应对象,以及处理器。

返回结果:适配器处理请求并返回结果(如 ModelAndView),最终将响应返回给客户端。

总结一下,通过适配器模式,Spring 能够灵活地支持多种类型的请求处理器,而不需要在 HandlerMapping 中硬编码每种处理器的处理逻辑。这种设计使得框架具有良好的扩展性和可维护性,允许开发者轻松添加新的处理器类型和适配器。


适配器VS装饰者

观察了示例代码,可以发现,和装饰者模式有点像,都是扩展增强了原有对象,那两者有什么区别呢?

适配器模式和装饰者模式虽然在结构上有相似之处,但它们的目的和使用场景是不同的,下面是它们的主要区别:

1、先看适配器模式

1)目的:接口转换,适配器模式的主要目的是将一个类的接口转换为客户端所期望的另一个接口。它使得原本由于接口不兼容而无法一起工作的类可以一起工作。

2)使用场景:当你希望使用一些现有的类,但它们的接口与您所需要的接口不兼容时。例如,将一个旧的 API 适配到新的接口,以便可以在新的系统中使用。

3)结构:适配器通常包含一个源类的实例,并实现目标接口。适配器负责将目标接口的方法调用转发到源类的方法。

2、再看装饰者模式

1、目的:功能扩展,装饰者模式的主要目的是在不改变对象自身的情况下,动态地为对象添加额外的功能。它允许在运行时对对象进行扩展。

2、使用场景:当你希望在不修改现有类的情况下,给对象添加新的行为或功能时。例如,为一个图形对象添加边框、阴影等装饰效果。

3、结构装饰者通常包含一个被装饰对象的实例,并实现与被装饰对象相同的接口。装饰者可以在调用被装饰对象的方法之前或之后添加额外的行为。

3、总结一下

适配器模式:关注于接口的转换,使得不兼容的接口可以协同工作。

装饰者模式:关注于功能的扩展,通过组合的方式为对象添加新的行为。

【代理、桥接、装饰器、适配器的区别】

  • 代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
  • 桥接模式:桥接模式的目的是将接口部分和实现部分分离,而让它们可以较为容易、独立地加以改变。
  • 装饰器模式:装饰器模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。
  • 适配器模式:适配器模式是一种时候的补救策略,适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原来类相同的接口。

适配器实操补充

经典案例一


// 类适配器: 基于继承
// 自己扩展的,客户端期望得到的接口,里面还可以包含被适配者没有的接口
public interface ITarget {void f1();void f2();void fc();
}//被适配者(外部系统)
//理解为外国的电源插座,这些方法不会暴露给客户端直接调用,因此不支持或者说不好用
public class Adaptee {public void fa() { //... }public void fb() { //... }public void fc() { //... }
}//适配器类,用于替换被适配者完成功能
//理解为转接头,负责两个事情,提供方法给客户端调用,方法内部会调用目标(国外电源插座)的方法
public class Adaptor extends Adaptee implements ITarget {public void f1() {super.fa();}public void f2() {//...重新实现f2()...}// 这里fc()不需要实现,直接继承自Adaptee,这是跟对象适配器最大的不同点
}// 对象适配器:基于组合
public interface ITarget {void f1();void f2();void fc();
}public class Adaptee {public void fa() { //... }public void fb() { //... }public void fc() { //... }
}public class Adaptor implements ITarget {private Adaptee adaptee;public Adaptor(Adaptee adaptee) {this.adaptee = adaptee;}public void f1() {adaptee.fa(); //委托给Adaptee}public void f2() {//...重新实现f2()...}public void fc() {adaptee.fc();}
}

经典案例二(类适配器模式)

在上图中可以看出,Adaptee类(被适配者、外国插座)并没有sampleOperation2()方法,而客户端则期待这个方法。为使客户端能够使用 Adaptee 类,提供一个中间环节,即类 Adapter(适配器、转换头),把 Adaptee 的 API与 Target 类的API衔接起来。Adapter 与 Adaptee 是继承关系,这决定了这个适配器模式是类的:

模式所涉及的角色有:

1、目标(Target)角色:这就是所期待得到的接口。

2、源(Adapee)角色:现在需要适配的接口。

3、适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。

public interface Target {/*** 这是源类Adaptee也有的方法*/public void sampleOperation1(); /*** 这是源类Adapteee没有的方法*/public void sampleOperation2(); 
}//上面给出的是目标角色的源代码,这个角色是以一个JAVA接口的形式实现的。
//可以看出,这个接口声明了两个方法:sampleOperation1()和sampleOperation2()。
//而源角色Adaptee是一个具体类,它有一个sampleOperation1()方法,但是没有sampleOperation2()方法。
public class Adaptee {public void sampleOperation1(){}
}//适配器角色Adapter扩展了Adaptee,同时又实现了目标(Target)接口。由于Adaptee没有提供sampleOperation2()方法,而目标接口又要求这个方法
//因此适配器角色Adapter实现了这个方法。
public class Adapter extends Adaptee implements Target {/*** 由于源类Adaptee没有方法sampleOperation2()* 因此适配器补充上这个方法*/@Overridepublic void sampleOperation2() {//写相关的代码}}

经典案例三(对象适配器模式)

与类的适配器模式一样,对象的适配器模式把被适配的类的API转换成为目标类的API,与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用委派关系连接到Adaptee类。

PS:其实基本一样,就是继承改为组合。

从上图可以看出,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法。为使客户端能够使用Adaptee类,需要提供一个包装(Wrapper)类Adapter。这个包装类包装了一个Adaptee的实例,从而此包装类能够把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee是委派关系,这决定了适配器模式是对象的。

public interface Target {/*** 这是源类Adaptee也有的方法*/public void sampleOperation1(); /*** 这是源类Adapteee没有的方法*/public void sampleOperation2(); 
}public class Adaptee {public void sampleOperation1(){}   
}public class Adapter {private Adaptee adaptee;public Adapter(Adaptee adaptee){this.adaptee = adaptee;}/*** 源类Adaptee有方法sampleOperation1* 因此适配器类直接委派即可*/public void sampleOperation1(){this.adaptee.sampleOperation1();}/*** 源类Adaptee没有方法sampleOperation2* 因此由适配器类需要补充此方法*/public void sampleOperation2(){//写相关的代码}
}

总结陈词

怎么说呢,适配器模式还是很强大的,它为我们带来:

1、更好的复用性:系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。

2、更好的扩展性:在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。

但它也有一些缺点:过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

最终是否采用,还是根据实际情况决定,纸上得来终觉浅,绝知此事要躬行

💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

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

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

相关文章

《深入解析:水果销售数据库操作与查询技巧》

文章目录 一、数据库结构与数据源插入1.1 创建数据库与表1.2 插入数据 二、基础数据查询2.1 查询客户信息2.2 查询供应商信息 三、查询优化与技巧3.1 使用LIMIT子句 四、高级查询技巧4.1 使用聚合函数4.2 连接查询4.3 使用子查询 五、案例分析5.1 客户订单详情查询 一、数据库结…

MySQL篇(窗口函数/公用表达式(CTE))(持续更新迭代)

目录 讲解一:窗口函数 一、简介 二、常见操作 1. sumgroup by常规的聚合函数操作 2. sum窗口函数的聚合操作 三、基本语法 1. Function(arg1,..., argn) 1.1. 聚合函数 sum函数:求和 min函数 :最小值 1.2. 排序函数 1.3. 跨行函数…

DeiT(ICML2021):Data-efficient image Transformer,基于新型蒸馏且数据高效的ViT!

Training data-efficient image transformers & distillation through attention:通过注意力训练数据高效的图像转换器和蒸馏 论文地址: https://arxiv.org/abs/2012.12877 代码地址: https://github.com/facebookresearch/deit 这篇论文…

14.第二阶段x86游戏实战2-C++语言开发环境搭建-VisualStudio2017

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 本次游戏没法给 内容参考于:微尘网络安全 本人写的内容纯属胡编乱造,全都是合成造假,仅仅只是为了娱乐,请不要…

vue-入门速通

setup是最早的生命周期,在vue2里边的data域可以使用this调用setup里面的数据,但是在setup里边不能使用thisvue项目的可执行文件是index,另外运行前端需要npm run vue的三个模块内需要三个不同的结构,里边放置js代码,注…

2024/9/21 leetcode 21.合并两个有序链表 2.两数相加

目录 21.合并两个有序链表 题目描述 题目链接 解题思路与代码 2.两数相加 题目描述 题目链接 解题思路与代码 --------------------------------------------------------------------------- 21.合并两个有序链表 题目描述 将两个升序链表合并为一个新的 升序 链表并返…

CVPT: Cross-Attention help Visual Prompt Tuning adapt visual task

论文汇总 当前的问题 图1:在VTAB-1k基准测试上,使用预训练的ViT-B/16模型,VPT和我们的CVPT之间的性能和Flops比较。我们将提示的数量分别设置为1、10、20、50,100,150,200。 如图1所示,当给出大量提示时,VPT显示了性能的显著下降…

【MySQL 04】数据类型

目录 1.数据类型分类 2.数值类型 2.1 tinyint 类型 2.2 bit类型 2.3 float类型 2.4decimal 3.字符串类型 3.1 char类型 3.2 varchar类型 4.日期和时间类型 6. enum和set类型 6.1.enum和set类型简介: 6.2.enum和set的一般使用方法 6.3.用数字的方式…

如何用AI实现自动更新文章?(全自动更新网站)

AI的诞生确实给我们的生活和工作都带来了很大的改变,从我自身来讲,也渐渐习惯了遇到事情先问问AI,不管是翻译、专业性问题、PPT制作、总结写作这些,确实帮我迅速理清了思路,也可以有很多内容的借鉴。 作为一个业余爱好…

kismet和war driving具体准备(仅供无线安全学习)

war driving准备 一台笔记本 一个最好是双频的网卡,单频搜集信号少 我自己买的是http://e.tb.cn/h.grI4EmkDLOqQXHG?tkKZ5g3RVeH6f 如果经济条件允许可以去买大功率天线(我买的车载的 大概40db这样子 范围广) http://e.tb.cn/h.grCM0CQ6L…

Davinci 大数据可视化分析

Davinci 大数据可视化分析 一、Davinci 架构设计1.1 Davinci定义1.2 Davinci 应用场景 二、Davinci 安装部署2.1 部署规划2.2 前置环境准备2.3 Davinci部署2.3.1 物料准备2.3.2 安装配置 2.4 环境变量配置2.5 初始化数据库2.5.1 创建数据库及用户 2.5.2 建表2.6 初始化配置 三、…

7天速成前端 ------学习日志 (继苍穹外卖之后)

前端速成计划总结: 全26h课程,包含html,css,js,vue3,预计7天内学完。 起始日期:9.16 预计截止:9.22 每日更新,学完为止。 学前计划 课…

使用IDA Pro动态调试Android APP

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/ 关于 android_server android_server 是 IDA Pro 在 Android 设备上运行的一个调试服务器。 通过在 Android 设备上运行android_server,IDA Pro …

IDEA 2024.3 EAP新特征早览!

0 前言 IntelliJ IDEA 2024.3 第一个 EAP 版本已发布,提前体验 下一个重大版本的一部分改进。 持续关注 EAP 更新,未来几周内将推出更多 IntelliJ IDEA 新功能。尝试这些新功能,分享您的反馈,共同完善 IDE。 1 AI 助手 1.1 内…

计算机毕业设计之:基于微信小程序的电费缴费系统(源码+文档+讲解)

博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台…

【自动化测试】Appium Server如何安装和Appium Server安装困难的原因和解决方法以及常见的一些安装失败的错误和解决方法

引言 Appium Server安装过程时常出现问题,以下是安装Appium Server过程一些原因、常见错误和解决方法 文章目录 引言一、Appium Server如何安装1.1 Node.js 安装1.2 使用NPM安装Appium1.3 验证Appium安装1.4 运行Appium Server1.5 使用Appium Desktop(可…

Flutter 安装,配置,运行第一个app 1

起因, 目的: flutter, 其实几年前,我就写过。 当时纯属是个人兴趣,随意探索。 当时我也写了几篇笔记: 比如这一篇还有这个 flutter,其实不难,比较繁琐,小的知识点很多. flutter, 又是环境配…

独立站技能树/工具箱1.0 总纲篇丨出海笔记

正所谓要把一件事做到90分很难,但做到60分基本上照着SOP做到位都没问题,如果我们能把每件事都做到60分,那绝对比至少60%的人都强,除非你的对手不讲武德——那就是他很可能看了我这篇文章,不但每方面都超过及格线&#…

【Linux探索学习】第一弹——Linux的基本指令(上)——开启Linux学习第一篇

前言: 在进入Linux学习之前,我们首先要先做好以下两点:1、已经基本掌握C语言或C,2、已经配置好了Linux的环境,做完以上两点后我们就开始Linux的学习,今天我们首先要学习的就是Linux中最基础的操作&#xff…

网络安全-ssrf

目录 一、环境 二、漏洞讲解 三、靶场讲解 四、可利用协议 4.1 dict协议 4.2 file协议 4.3 gopher协议 五、看一道ctf题吧(长亭的比赛) 5.1环境 5.2开始测试 ​编辑 一、环境 pikachu,这里我直接docker拉取的,我只写原…