内功心法专题-设计模式-8、适配器模式
1. 模式定义
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
适配器模式分为==类适配器模式==和==对象适配器模式==,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
2. 模式结构
适配器模式(Adapter)包含以下主要角色:
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者(Adaptee)类:它是被访问和适配的现有组件库中的组件接口。
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
3. 工作原理

适配者类就是系统中现有的接口,需要被适配拥有其他能力
4. 分类
4.1. 类适配器
示例代码:[[pages/002-schdule/001-Arch/001-Subject/013-DemoCode/DesignPattern/src/com/atguigu/adapter/classadapter/VoltageAdapter.java]]
4.1.1. 实现逻辑
基本介绍:Adapter 类,通过继承 src 类 (源头类),实现 dst 类接口 (目标接口),完成 src->dst 的适配

4.1.2. 优缺点
- 缺点:
- 违背了合成复用原则
- Java 是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点, 因为这要求 dst 必须是接口,有一定局限性;
- src 类的方法在 Adapter 中都会暴露出来,也增加了使用的成本。
- 优点:由于其继承了 src 类,所以它可以根据需求重写 src 类的方法,使得 Adapter 的灵活性增强了。
4.2. 对象适配器
4.2.1. 实现逻辑⭐️🔴
- 基本思路和类的适配器模式相同,只是将 Adapter 类作修改,不是继承 src 类,而是持有 src 类的实例,以解决兼容性的问题。 即:持有 src 类,实现 dst 类接口,完成 src->dst 的适配
- 根据“合成复用原则”,在系统中尽量使用关联关系(聚合)来替代继承关系。
- 对象适配器模式是适配器模式常用的一种
4.2.1.1. ⭐️🔴UML 图示

4.2.1.2. 实现逻辑⭐️🔴
实现 dst+ 组合 src+ 构造导入 src
1 | |
4.2.2. 优缺点⭐️🔴
- 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。 根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承 src 的局限性问题,也不再要求 dst 必须是接口。
- 使用成本更低,更灵活。
4.3. 接口适配器
- 一些书籍称为:适配器模式 (Default Adapter Pattern) 或缺省适配器模式。
- 核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
- 适用于一个接口不想使用其所有的方法的情况。
类似于接口隔离的设计原则
5. 应用场景⭐️🔴
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
6. JDK 源码分析⭐️🔴
6.1. InputStreamReader
Reader(字符流)、InputStream(字节流)的适配使用的是 InputStreamReader。
InputStreamReader 继承自 java.io 包中的 Reader,对他中的抽象的未实现的方法给出实现。如:
1 | |
如上代码中的 sd(StreamDecoder 类对象),在 Sun 的 JDK 实现中,实际的方法实现是对 sun.nio.cs.StreamDecoder 类的同名方法的调用封装。类结构图如下:

从上图可以看出:
- InputStreamReader 是对同样实现了 Reader 的 StreamDecoder 的封装。
- StreamDecoder 不是 Java SE API 中的内容,是 Sun JDK 给出的自身实现。但我们知道他们对构造方法中的字节流类(InputStream)进行封装,并通过该类进行了字节流和字符流之间的解码转换。
结论:
适配者类(StreamDecoder)持有src(适配者类InputStream),实现或者继承dst(目标类Reader)
从表层来看,InputStreamReader 做了 InputStream 字节流类到 Reader 字符流之间的转换。而从如上 Sun JDK 中的实现类关系结构中可以看出,是 StreamDecoder 的设计实现在实际上采用了适配器模式。
6.2. SpringMVC 的 HandlerAdapter⭐️🔴

6.2.1. 源码分析
源码:
/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/013-DemoCode/SpringMVC/srclib/spring-webmvc-4.0.0.RELEASE.jar!/org/springframework/web/servlet/HandlerAdapter.class
- SpringMvc 中的 HandlerAdapter 使用了适配器模式
- 使用 HandlerAdapter 的原因分析: 可以看到处理器的类型不同,有多种实现方式,那么调用方式就不是确定的,如果需要直接调用 Controller 方法,需要调用的时候就得不断是使用 ifelse 来进行判断是哪一种子类然后执行。那么如果后面要扩展 Controller,就得修改原来的代码,这样违背了 OCP 原则。

6.2.1.1. HandlerAdapter 实现
1 | |
6.2.1.1.1. supports⭐️🔴

不同的 HandlerAdapter 分别预先定义了不同的 supports 方法,通过 instanceof 分别与不同的类型的 controller 对应 ❕ ^qh1uma


RequestMappingHandlerAdapter 继承自 AbstractHandlerMethodAdapter
HandlerAdapter.supports() 方法的主要作用在于判断当前的 HandlerAdapter 是否能够支持当前的 handler 的适配。这里的 handler 是由 HandlerExecutionChain HandlerMapping.getHandler(HttpServletRequest) 方法获取到的。从这里可以看出:
每一种 Controller 或者其他请求方式都有一个 HandlerAdapter 与之对应,通过在遍历中调用 supports 方法中 instanceof 的方式进行判断匹配
6.2.1.1.2. handle
handle 方法入参中有 mappedHandler.getHandler(),因此可以看出 SpringMVC 的适配器模式是对象适配器模式

在进入到 handle 方法中时,传入是 Object,但因为对应的 HandlerAdapter 子类已经是确定的,可以直接强转,然后调用 handleRequest 方法

执行真正开发者开发的处理方法的地方。Spring MVC 自动帮我们完成数据绑定、视图渲染等等一切周边工作
6.2.1.1.3. getLastModified
获取当前请求的最后更改时间,主要用于供给浏览器判断当前请求是否修改过,从而判断是否可以直接使用之前缓存的结果
6.2.1.2. getHandler⭐️🔴
HandlerExecutionChain mappedHandler = this.getHandler(processedRequest)HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler())
HandlerMapping的作用主要是根据 request 请求匹配/映射上能够处理当前 request 的 handlerHandlerAdapter的作用在于将 request 中的各个属性,如request param适配为 handler 能够处理的形式 - 参数绑定、数据校验、内容协商…几乎所有的 web 层问题都在在这里完成的。
6.2.1.3. getHandlerAdapter- 入参 hadler 是 Object- 匹配过程⭐️🔴
❕ ^xcsdza
遍历 handlerAdapters 集合,调用 supports 方法,根据 handler 的类型 instanceof 获取到对应的 HandlerAdapter 子类
对应的子类有如下类型
6.2.1.4. doDispatch 分发流程
1 | |
从执行步骤中可以看到:HandlerAdapter 对于执行流程的通用性起到了非常重要的作用,它能把任何一个处理器(Object)都适配成一个 HandlerAdapter,从而可以做统一的流程处理,这也是为何DispatcherServlet它能作为其它 web 处理框架的分发器的原因(因为它没有耦合具体的处理器,我们完全可以自己去实现)
6.2.2. 扩展性分析⭐️🔴⭐️🔴

6.2.2.1. 适配器 HandlerAdapter⭐️🔴
为什么要用适配器 HandlerAdapter
Spring MVC 的 Handler 有四种不同的表现形式,包括 Controller 接口,HttpRequestHandler,Servlet、@RequestMapping,那么调用方式就不是确定的,如果需要直接调用 Controller 方法,需要调用的时候就得不断使用 if else 来进行判断是哪一种子类然后执行。
如果后面要扩展 Controller,就得修改原来的代码,这样违背了开闭原则。❕
1 | |
而 HandlerAdapter 设计模式下,getHandlerAdapter- 入参是 Object 就能模糊掉具体的实现,后面通过supports 方法中的 instanceof进行判断,就替换了如果不用设计模式下的一坨坨 ifelse。
从根本上说,这些 ifelse 并没有消失,而是转移了。转移到我们需要事先定义好 Handler 和 HandlerAdapter 的对应关系,即一个 Handler 要配对一个 HandlerAdapter,然后才能在 supports 方法中的 instanceof 进行判断
体现了 约定优于配置,配置优于编码 (硬代码)的思想
6.2.2.2. src:Adaptee(被适配者)
Handler(Controller 接口,HttpRequestHandler,Servlet、@RequestMapping)有四种不同的表现形式及调用方式
6.2.2.3. dst:Target(目标类)
Controller 具体实现类
6.2.2.4. 总结
- 首先是适配器接口 DispatchServlet 中有一个集合维护所有的 HandlerAdapter,如果配置文件中没有对适配器进行配置,那么 DispatchServlet 会在创建时对该变量进行初始化,注册所有默认的 HandlerAdapter。
- 当一个请求过来时,DispatchServlet 会根据传过来的 handler 类型从该集合中寻找对应的 HandlerAdapter 子类进行处理,并且调用它的 handler() 方法
- 对应的 HandlerAdapter 中的 handler() 方法又会执行对应 Controller 的 handleRequest() 方法
适配器与 handler 有对应关系,而各个适配器又都是适配器接口的实现类,因此,它们都遵循相同的适配器标准,所以用户可以按照相同的方式,通过不同的 handler 去处理请求。当然了,Spring 框架中也为我们定义了一些默认的 Handler 对应的适配器。

通过适配器模式我们将所有的 controller 统一交给 HandlerAdapter 处理,免去了写大量的 ifelse 语句对 Controller 进行判断,也更利于扩展新的 Controller 类型
AbstractHandlerMethodAdapter
从命名中其实就可以看出端倪,它主要是支持到了 org.springframework.web.method.HandlerMethod 这种处理器,显然这种处理器也是我们 最为常用的。它的子类 RequestMappingHandlerAdapter 把 HandlerMethod 的实现精确到了 @RequestMapping 注解方案。这个 HandlerAdapter 可谓是 Spring MVC 的精华之所在
6.2.3. 大致流程
首先是适配器接口 DispatchServlet 中有一个集合维护所有的 HandlerAdapter,如果配置文件中没有对适配器进行配置,那么 DispatchServlet 会在创建时对该变量进行初始化,注册所有默认的 HandlerAdapter。
当一个请求过来时,DispatchServlet 会根据传过来的 handler 类型从该集合中寻找对应的 HandlerAdapter 子类进行处理,并且调用它的 handler() 方法
对应的 HandlerAdapter 中的 handler() 方法又会执行对应 Controller 的 handleRequest() 方法
6.2.4. 示例代码
/Users/Enterprise/0003-Architecture/架构师之路/尚硅谷 - 设计模式/代码/Spring/src/com/atguigu/spring/test/Adapter.java
6.3. AOP 中 Advice 适配案例
#todo
https://blog.csdn.net/yuan882696yan/article/details/105602359
7. 实战经验
8. 参考与感谢
==适配器模式,韩顺平老师案例要好一些==
示例代码手写 mvc:/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/013-DemoCode/SpringMVC
[[Spring MVC适配器模式实践之HandlerAdapter源码分析【享学Spring MVC】 - 腾讯云开发者社区-腾讯云]]
[[(210条消息) 从SpringMVC来看适配器模式__PPB的博客-CSDN博客_springmvc适配器模式]]
[[随遇而安的适配器模式 Spring 中的适配器_Java_大头星_InfoQ写作社区]]
[[设计模式 适配器模式及典型应用 - 掘金]]
![[设计模式 适配器模式及典型应用 - 掘金#^57xek8]]
![[设计模式 适配器模式及典型应用 - 掘金#^m50nku]]



