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

📢 大家好,我是 【战神刘玉栋】,有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.xdnf.cn/news/1544628.html

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

相关文章

【后端开发】JavaEE初阶—线程安全问题与加锁原理(超详解)

前言: 🌈上期博客:【后端开发】JavaEE初阶—Theard类及常见方法—线程的操作(超详解)-CSDN博客 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🌈小编会在后端开发的学习中不…

关于javascript中防抖和节流的使用详解

防抖(Debounce)和节流(Throttle)是两种常见的优化技巧,通常用于控制函数在短时间内频繁触发的场景,尤其是在处理用户输入、滚动、窗口大小调整等事件时。它们的主要目的是减少不必要的函数调用,…

超详细超实用!!!AI编程之cursor编写设计模式开闭原则实例(四)

云风网 云风笔记 云风知识库 一、设计模式开闭原则定义 当应用的需求改变时,在不修改软件实体(项目模块、类、接口方法)的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。即软件实体应当对扩展开…

【Linux】nginx连接前端项目

文章目录 一、项目编译1.编译文件2.dist文件 二、Linux nginx配置三、启动nginx 一、项目编译 1.编译文件 2.dist文件 二、Linux nginx配置 在Xshell软件中,点击CtrlAltF进入文件传输找到地址:/usr/local/nginx/html将dist文件传入 找到nginx.conf&…

git add成功后忘记commit的文件丢了?

本文目标:开发人员,在了解git fsck命令用法的条件下,进行git add成功但由于误操作导致丢失的文件找回,达到找回丢失文件的程度。 文章目录 1 痛点2 解决方案3 总结/练习 1 痛点 开发过程中,分支太多(基线分…

CREO教程——2 绘制标准图纸

CREO教程——2 绘制标准图纸 说明:继承第一章设置好的配置文件,这一章进行学习分享如何定制自己的图纸图框,参考国家标准距,定制属于设计师或单位的通用图框。 1.设置工作目录 1.1设置工作目录 1.打开软件设置工作目录&#x…

u盘格式化怎么恢复数据?四款工具来救急!

工作中真的没少碰到过那些让人头疼的数据丢失问题,特别是U盘里的宝贝数据一不小心就“蒸发”了,简直让人欲哭无泪。不过别担心,我作为一个数据恢复的新手小白,最近可是亲测了几款超给力的数据恢复软件,今天就来跟大家分…

19c-TNS-12541: TNS:no listener

有套19c单机,没应用任何的补丁,使用lsnrctl status查看监听是异常的,但是lsnrctl start发现监听已运行,当前业务连接都正常, orcl:/home/oracledb> lsnrctl status LSNRCTL for Linux: Version 19.0.0.0.0 - Pro…

打造灵活DateTimePicker日期时间选择器组件:轻松实现时间的独立清除功能

element ui中日期和时间选择器(DateTimePicker)是一个常见且重要的组件。它允许用户轻松地选择日期和时间,极大地提升了用户体验。然而,在某些场景下,用户可能需要更细粒度的控制,例如单独清除已选择的时间…

下载与安装|Inventor 2025百度云资源分享附教程

如大家所了解的,Inventor是一款专业的三维可视化实体建模软件,主要用于各类二维机械制图、三维制图的设计和开发等操作,可以广泛地应用于零件设计、钣金设计、装配设计等领域。 不同领域的应用证明了Inventor具有强大的兼容性,基…

监控易监测对象及指标之:全面监控Oracle ODBC数据库

在数字化时代,数据库作为存储和管理企业核心数据的基石,其稳定性和性能直接关系到业务的连续性和效率。Oracle数据库以其强大的功能和稳定性,广泛应用于各行各业。为了确保Oracle数据库的稳定运行和高效性能,对其进行全面监控显得…

备战软考Day04-计算机网络

1、计算机网络的分类 2、七层网络体系结构 3、网络的设备与标准 4、TCP/IP协议族 TCP/IP作为Internet的核心协议,被广泛应用于局域网和广域网中,目前已成为事实上的国际标准 1、TCP/IP分层模型 TCP/IP协议是Internet的基础和核心,和OSI参考…

git命令将已经commit的代码push到其他分支

文章目录 一:对于多分支的代码库,将提交记录从一个分支转移到另一个分支是常见需求方法1:撤销commit操作方法2:实用命令git cherry-pick 来移动commit 二、不小心revert导致代码消失的问题 一:对于多分支的代码库&…

【Diffusion分割】FDiff-Fusion:基于模糊学习的去噪扩散融合网络

FDiff-Fusion: Denoising diffusion fusion network based on fuzzy learning for 3D medical image segmentation 摘要: 近年来,去噪扩散模型在图像分割建模中取得了令人瞩目的成就。凭借其强大的非线性建模能力和优越的泛化性能,去噪扩散模…

好用的todolist待办清单软件下载推荐

在快节奏的现代生活中,时间管理变得尤为重要。todolist待办清单管理软件,作为一种高效的任务管理工具,它帮助我们记录、跟踪和管理日常任务,从而提升个人效率。 在众多的待办软件中,敬业签以其出色的用户体验脱颖而出…

Vue2电商项目(四) Detail模块

文章目录 一、配置Detail路由1. 将Detail组件配置为路由组件2. 将路由配置文件拆分3. 声明式导航跳转到Detail跳转时存在的问题:页面滚动条还在下边 二、配置API及vuex三、放大镜及下方轮播图1. Detail组件传递放大镜数据2. 读取vuex数据的经典错误undefined3. 放大…

力扣234 回文链表 Java版本

文章目录 题目描述代码 题目描述 给你一个单链表的头节点 head ,请你判断该链表是否为 回文链表 。如果是,返回 true ;否则,返回 false 。 示例 1: 输入:head [1,2,2,1] 输出:true 示例 2&…

【集合拆分+多线程并行处理,拿来即用】

文章目录 一.业务场景二.拆分流程三. 简单上个List拆分的demo四.测试结果五.小结 一.业务场景 节假日后第一天,上完班有点累,回到家稍微写点简单的东西。 我们项目里面有这样一业务场景,要计算全公司所有人某几个月内每天的考勤机打卡加班工时…

docker从0到1运行mysql(最详细且绝对成功版)

前置环境 CentOS7.8 安装docker yum install -y docker 启动docker并检查docker状态 systemctl start docker systemctl status docker 这样即正常 设置镜像加速 修改 /etc/docker/daemon.json 文件并添加上 registry-mirrors 键值 vim /etc/docker/daemon.json …

set的使用

序列式容器和关联式容器 序列式容器: 前⾯我们已经接触过STL中的部分容器如:string、vector、list、deque、array、forward_list等,这些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间…