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

news2024/9/24 21:43:45

📢 大家好,我是 【战神刘玉栋】,有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;
    }

    @Override
    public 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 {
    @Override
    public boolean supports(Object handler) {
        return (handler instanceof RequestMappingHandler);
    }

    @Override
    public 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()
     * 因此适配器补充上这个方法
     */
    @Override
    public 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.coloradmin.cn/o/2161528.html

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

相关文章

C++(学习)2024.9.24

目录 容器 1.标准模板库STL 2.概念 3.顺序容器 (1)array数组 (2)vector向量 (3)list列表 (4)deque 队列 4.关联容器 5.迭代器 面向对象核心 继承 概念 构造函数 1.派生…

软件需求规格说明书编制模板参考(Doc原件)

1 范围 1.1 系统概述 1.2 文档概述 1.3 术语及缩略语 2 引用文档 3 需求 3.1 要求的状态和方式 3.2 系统能力需求 3.3 系统外部接口需求 3.3.1 管理接口 3.3.2 业务接口 3.4 系统内部接口需求 3.5 系统内部数据需求 3.6 适应性需求 3.7 安全性需求 3.8 保密性需…

项目实战总结-Kafka实战应用核心要点

Kafka实战应用核心要点 一、前言二、Kafka避免重复消费2.1 消费者组机制2.2 幂等生产者2.3 事务性生产者/消费者2.4 手动提交偏移量2.5 外部存储管理偏移量2.6 去重逻辑2.7 幂等消息处理逻辑2.8 小结 三、Kafka持久化策略3.1 持久化文件3.2 segment 分段策略3.3 数据文件刷盘策…

Leetcode面试经典150题-39.组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限制重复被选取 。如…

加固与脱壳01 - 环境搭建

虚拟机 VMWare 多平台可用,而且可以直接激活,需要先注册一个账号 https://support.broadcom.com/group/ecx/productdownloads?subfamilyVMwareWorkstationPro KALI 类Ubuntu系统,官方提供了 vmware 版本,直接下载就可以使用。…

【Python机器学习】NLP信息提取——提取人物/事物关系

目录 词性标注 实体名称标准化 实体关系标准化和提取 单词模式 文本分割 断句 断句的方式 使用正则表达式进行断句 词性标注 词性(POS)标注可以使用语言模型来完成,这个语言模型包含词及其所有可能词性组成的字典。然后,该…

三子棋小游戏

使用C语言编写代码,实现一个简单小游戏---三子棋 这里创建1个game.h文件,用来声明函数、宏的文件,一个game.c文件用来实现函数game(),一个play.h文件用来作为该游戏的源文件。 具体代码如下: …

利用大型语言模型轻松打造浪漫时刻

当情人节年年如约而至,每每都需费尽心思为对方营造一场令人难忘的仪式,却因缺乏创意与思路而倍感困扰。今天,我决定让大型语言模型为我们提供一些灵感和建议,让我们能够轻松实现这一目标。让我们开始行动吧!此前&#…

问卷是否要做信效度分析,5类信度与4类效度常用指标及评价标准

论文问卷进行分析时,大家是否有这样的疑惑—— 我收集的问卷是否需要进行信效度分析呢? 下面一文给大家梳理问卷信效度分析的相关内容,包括什么样的题目需要进行信效度分析、5类信度分析与4类效度分析常用指标及评价标准。 一、问卷是否需…

JW01二氧化碳传感器(串行通信 STM32)

目录 一、介绍 二、传感器原理 1.工作原理介绍 2.串口数据流格式 三、程序设计 main.c文件 usart3.h文件 usart3.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 JW01-CO2检测模块是一种用于检测空气中二氧化碳浓度的传感器模块。它可以广泛应用于室内空气质量…

java算法OJ(1)位运算

目录 1.前言 2.正文 2.1位运算符号 2.1俩数相除 2.1.1题目 2.1.2示例 2.1.3题解 2.2二进制求和 2.2.1题目 2.2.2示例 2.2.3题解 2.3只出现一次的数字 2.3.1题目 2.3.2示例 2.3.3题解 2.4只出现一次的数字(进阶版) 2.4.1题目 2.4.2示例…

glb数据格式

glb数据格式 glb 文件格式只包含一个glb 文件,文件按照二进制存储,占空间小 浏览 浏览glb工具的很多,ccs,3D查看器等都可以,不安装软件的话用下面网页加载就可以,免费 glTF Viewer (donmccurdy.com) glb…

uniapp小程序中通过uni.setClipboardData实现复制功能无效的原因和解决方案

// 复制下载链接const shareFile (filePath) > {const pdfUrl 复制内容uni.showModal({title: 下载提示,content: 请复制链接到浏览器中下载,confirmColor: #eb2444,confirmText: 复制链接,success(res) {if (res.confirm) {uni.setClipboardData({data: pdfUrl, // url地…

C++: unordered系列关联式容器

目录 1. unordered系列关联式容器1.1 unordered_map1.2 unordered_set 2. 哈希概念3. 哈希冲突4. 闭散列5. 开散列 博客主页: 酷酷学 感谢关注!!! 正文开始 1. unordered系列关联式容器 在C98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时…

【笔记】自动驾驶预测与决策规划_Part4_时空联合规划

文章目录 0. 前言1. 时空联合规划的基本概念1.1 时空分离方法1.2 时空联合方法 2.基于搜索的时空联合规划 (Hybrid A* )2.1 基于Hybrid A* 的时空联合规划建模2.2 构建三维时空联合地图2.3 基于Hybrid A*的时空节点扩展2.4 Hybrid A* :时空节…

多线程——“死锁”

目录 前言 一、一个线程,一把锁 1.问题介绍 2.可重入锁 二、两个线程,两把锁 1.问题介绍 2.解决方式 三、N个线程,M把锁 1.哲学家就餐问题 2.解决方式 结尾 前言 “死锁”是多线程代码中一类常见的问题,加锁是能解决线…

plt的简单使用

目录 介绍示例 介绍 plt 是 Python 中 Matplotlib 库的一个常用别名,它表示 pyplot,这是一个用于创建图形和图形的可视化表示的工具。下面是一些 plt 函数的详解和示例,以帮助大家理解和使用。 示例 示例1: import matplotlib…

AV1 Bitstream Decoding Process Specification--[9]:语法结构语义-5

原文地址:https://aomediacodec.github.io/av1-spec/av1-spec.pdf 没有梯子的下载地址:AV1 Bitstream & Decoding Process Specification摘要:这份文档定义了开放媒体联盟(Alliance for Open Media)AV1视频编解码…

loadrunner个人笔记

创建场景配置: 两个同时 去四:日志、时间、模拟、其他自动事务 加一:首选项 1、写脚本,沟通官方、文件打印扫描 MFI-sw.support.gsd.imsc.sda.globalopentext.com support.casemicrofocus.com 支持资源 | Micro Focus | OpenT…

【毕业论文+源码】基于ASP的课程指导平台的开发

引 言 随着全球信息化技术的兴起,特别是Internet的日益普及,解决了信息Internet上传递的问题,建立了一个组织得很好的信息结构框架,使得Internet用户能够在Internet上的任何一个终端,以一种简单、统一的方式来访问超…