内功心法专题-设计模式-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/1056023597. 实战经验
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]]