1. 模式定义

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

适配器模式分为==类适配器模式==和==对象适配器模式==,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

2. 模式结构

适配器模式(Adapter)包含以下主要角色:

  • 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
  • 适配者(Adaptee)类:它是被访问和适配的现有组件库中的组件接口。
  • 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

3. 工作原理

image.png

适配者类就是系统中现有的接口,需要被适配拥有其他能力

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 的适配

image.png

4.1.2. 优缺点

  1. 缺点:
  • 违背了合成复用原则
  • Java 是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点, 因为这要求 dst 必须是接口,有一定局限性;
  • src 类的方法在 Adapter 中都会暴露出来,也增加了使用的成本。
  1. 优点:由于其继承了 src 类,所以它可以根据需求重写 src 类的方法,使得 Adapter 的灵活性增强了。

4.2. 对象适配器

4.2.1. 实现逻辑⭐️🔴

  1. 基本思路和类的适配器模式相同,只是将 Adapter 类作修改,不是继承 src 类,而是持有 src 类的实例,以解决兼容性的问题。 即:持有 src 类,实现 dst 类接口,完成 src->dst 的适配
  2. 根据“合成复用原则”,在系统中尽量使用关联关系(聚合)来替代继承关系。
  3. 对象适配器模式是适配器模式常用的一种

4.2.1.1. ⭐️🔴UML 图示

image.png

4.2.1.2. 实现逻辑⭐️🔴

实现 dst+ 组合 src+ 构造导入 src%%🏡⭐️◼️适配器模式:聚合 src+ 实现 dst◼️⭐️-point-202301152324%%

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//适配器类【实现】被适配者类  
public class VoltageAdapter implements IVoltage5V {
//适配器类【组合】被适配者类
private Voltage220V voltage220V;
//通过【构造导入】一个 Voltage220V 实例
public VoltageAdapter(Voltage220V voltage220v) {
this.voltage220V = voltage220v;
}

@Override
public int output5V() {
int dst = 0;
if (null != voltage220V) {
int src = voltage220V.output220V();//获取220V 电压
System.out.println("使用对象适配器,进行适配~~");
dst = src / 44;
System.out.println("适配完成,输出的电压为=" + dst);
}
return dst;
}
}

4.2.2. 优缺点⭐️🔴

  1. 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。 根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承 src 的局限性问题,也不再要求 dst 必须是接口。
  2. 使用成本更低,更灵活。

4.3. 接口适配器

  1. 一些书籍称为:适配器模式 (Default Adapter Pattern) 或缺省适配器模式。
  2. 核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
  3. 适用于一个接口不想使用其所有的方法的情况。

类似于接口隔离的设计原则

5. 应用场景⭐️🔴

  • 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
  • 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。

6. JDK 源码分析⭐️🔴

6.1. InputStreamReader

%%🏡⭐️◼️InputStreamDecoder 是适配器类,持有 src:InputStream 并继承了 dst:Reader,实现字节流到字符流的转换◼️⭐️-point-202301150726%%
Reader(字符流)、InputStream(字节流)的适配使用的是 InputStreamReader。

InputStreamReader 继承自 java.io 包中的 Reader,对他中的抽象的未实现的方法给出实现。如:

1
2
3
4
5
6
7
public int read() throws IOException {
return sd.read();
}

public int read(char cbuf[], int offset, int length) throws IOException {
return sd.read(cbuf, offset, length);
}

如上代码中的 sd(StreamDecoder 类对象),在 Sun 的 JDK 实现中,实际的方法实现是对 sun.nio.cs.StreamDecoder 类的同名方法的调用封装。类结构图如下:

image.png

从上图可以看出:

  • 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⭐️🔴

image.png

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

  1. SpringMvc 中的 HandlerAdapter 使用了适配器模式
  2. 使用 HandlerAdapter 的原因分析: 可以看到处理器的类型不同,有多种实现方式,那么调用方式就不是确定的,如果需要直接调用 Controller 方法,需要调用的时候就得不断是使用 ifelse 来进行判断是哪一种子类然后执行。那么如果后面要扩展 Controller,就得修改原来的代码,这样违背了 OCP 原则

image.png

6.2.1.1. HandlerAdapter 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface HandlerAdapter {

// 判断当前的这个HandlerAdapter 是否 支持给与的handler
// 因为一般来说:每个适配器只能作用于一种处理器
boolean supports(Object handler);

// 核心方法:利用 Handler 处理请求,然后返回一个ModelAndView
// DispatcherServlet最终就是调用此方法,来返回一个ModelAndView的
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
// 同HttpServlet 的 getLastModified方法
// Can simply return -1 if there's no support in the handler class.
long getLastModified(HttpServletRequest request, Object handler);

}
6.2.1.1.1. supports⭐️🔴

image.png

不同的 HandlerAdapter 分别预先定义了不同的 supports 方法,通过 instanceof 分别与不同的类型的 controller 对应 %%
▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230325-1028%%
❕ ^qh1uma

image.png

image.png

RequestMappingHandlerAdapter 继承自 AbstractHandlerMethodAdapter
image.png

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

6.2.1.1.2. handle

handle 方法入参中有 mappedHandler.getHandler(),因此可以看出 SpringMVC 的适配器模式是对象适配器模式

image.png

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

执行真正开发者开发的处理方法的地方。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 的 handler
  • HandlerAdapter 的作用在于将 request 中的各个属性,如 request param 适配为 handler 能够处理的形式 - 参数绑定、数据校验、内容协商…几乎所有的 web 层问题都在在这里完成的。

6.2.1.3. getHandlerAdapter- 入参 hadler 是 Object- 匹配过程⭐️🔴

%%
▶5.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230324-1444%%
❕ ^xcsdza

遍历 handlerAdapters 集合,调用 supports 方法根据 handler 的类型 instanceof 获取到对应的 HandlerAdapter 子类
image.png

对应的子类有如下类型
image.png

6.2.1.4. doDispatch 分发流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
DispatcherServlet:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
//1、根据URL(当然不一定非得是URL)匹配到一个处理器
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
// 若匹配不到Handler处理器,就404了
noHandlerFound(processedRequest, response);
return;
}

//2、从Chain里拿出Handler(注意是Object类型哦~ )然后找到属于它的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
//3、执行作用在此Handler上的所有拦截器的Pre方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//4、真正执行handle方法(也就是你自己书写的逻辑方法),得到一个ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

//5、视图渲染
applyDefaultViewName(processedRequest, mv);
//6、执行拦截器的post方法(可见它是视图渲染完成了才会执行的哦~)
mappedHandler.applyPostHandle(processedRequest, response, mv);
...
//7、执行拦截器的afterCompletion方法(不管抛出与否)
}

从执行步骤中可以看到:HandlerAdapter 对于执行流程的通用性起到了非常重要的作用,它能把任何一个处理器(Object)都适配成一个 HandlerAdapter,从而可以做统一的流程处理,这也是为何DispatcherServlet它能作为其它 web 处理框架的分发器的原因(因为它没有耦合具体的处理器,我们完全可以自己去实现)

6.2.2. 扩展性分析⭐️🔴⭐️🔴

image.png

6.2.2.1. 适配器 HandlerAdapter⭐️🔴

为什么要用适配器 HandlerAdapter

Spring MVCHandler 有四种不同的表现形式,包括 Controller 接口,HttpRequestHandler,Servlet、@RequestMapping,那么调用方式就不是确定的,如果需要直接调用 Controller 方法,需要调用的时候就得不断使用 if else 来进行判断是哪一种子类然后执行。
如果后面要扩展 Controller,就得修改原来的代码,这样违背了开闭原则。❕%%
1710-🏡⭐️◼️SpringMVC 为什么要用适配器模式 ?🔜MSTM📝 ◼️⭐️-point-202302071710%%

1
2
3
4
5
6
7
if(mappedHandler.getHandler() instanceof MultiActionController){  
((MultiActionController)mappedHandler.getHandler()).xxx
}else if(mappedHandler.getHandler() instanceof XXX){
...
}else if(...){
...
}

而 HandlerAdapter 设计模式下,getHandlerAdapter- 入参是 Object 就能模糊掉具体的实现,后面通过supports 方法中的 instanceof进行判断,就替换了如果不用设计模式下的一坨坨 ifelse。

从根本上说,这些 ifelse 并没有消失,而是转移了。转移到我们需要事先定义好 Handler 和 HandlerAdapter 的对应关系,即一个 Handler 要配对一个 HandlerAdapter,然后才能在 supports 方法中的 instanceof 进行判断%%🏡⭐️◼️对 HandlerAdapter 最终理解:ifelse 最后去哪了?◼️⭐️-point-202301151221%%

体现了 约定优于配置,配置优于编码 (硬代码)的思想

6.2.2.2. src:Adaptee(被适配者)

HandlerController 接口,HttpRequestHandler,Servlet、@RequestMapping)有四种不同的表现形式及调用方式

6.2.2.3. dst:Target(目标类)

Controller 具体实现类

6.2.2.4. 总结

  1. 首先是适配器接口 DispatchServlet 中有一个集合维护所有的 HandlerAdapter,如果配置文件中没有对适配器进行配置,那么 DispatchServlet 会在创建时对该变量进行初始化,注册所有默认的 HandlerAdapter。
  2. 当一个请求过来时,DispatchServlet 会根据传过来的 handler 类型从该集合中寻找对应的 HandlerAdapter 子类进行处理,并且调用它的 handler() 方法
  3. 对应的 HandlerAdapter 中的 handler() 方法又会执行对应 Controller 的 handleRequest() 方法

适配器与 handler 有对应关系,而各个适配器又都是适配器接口的实现类,因此,它们都遵循相同的适配器标准,所以用户可以按照相同的方式,通过不同的 handler 去处理请求。当然了,Spring 框架中也为我们定义了一些默认的 Handler 对应的适配器。

image.png

通过适配器模式我们将所有的 controller 统一交给 HandlerAdapter 处理,免去了写大量的 ifelse 语句对 Controller 进行判断,也更利于扩展新的 Controller 类型

AbstractHandlerMethodAdapter
从命名中其实就可以看出端倪,它主要是支持到了 org.springframework.web.method.HandlerMethod 这种处理器,显然这种处理器也是我们 最为常用的。它的子类 RequestMappingHandlerAdapterHandlerMethod 的实现精确到了 @RequestMapping 注解方案。这个 HandlerAdapter 可谓是 Spring MVC 的精华之所在

6.2.3. 大致流程

  1. 首先是适配器接口 DispatchServlet 中有一个集合维护所有的 HandlerAdapter,如果配置文件中没有对适配器进行配置,那么 DispatchServlet 会在创建时对该变量进行初始化,注册所有默认的 HandlerAdapter。

  2. 当一个请求过来时,DispatchServlet 会根据传过来的 handler 类型从该集合中寻找对应的 HandlerAdapter 子类进行处理,并且调用它的 handler() 方法

  3. 对应的 HandlerAdapter 中的 handler() 方法又会执行对应 Controller 的 handleRequest() 方法

6.2.4. 示例代码

/Users/Enterprise/0003-Architecture/架构师之路/尚硅谷 - 设计模式/代码/Spring/src/com/atguigu/spring/test/Adapter.java

6.3. AOP 中 Advice 适配案例

#todo

- [ ] 🚩 - SpringAOP 中 Advice 适配案例:https://blog.csdn.net/yuan882696yan/article/details/105602359 - 🏡 2023-02-09 21:59 https://blog.csdn.net/yuan882696yan/article/details/105602359

image.png

7. 实战经验

8. 参考与感谢

==适配器模式,韩顺平老师案例要好一些==
示例代码手写 mvc:/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/013-DemoCode/SpringMVC

设计模式-2、设计模式及设计原则

[[Spring MVC适配器模式实践之HandlerAdapter源码分析【享学Spring MVC】 - 腾讯云开发者社区-腾讯云]]

[[(210条消息) 从SpringMVC来看适配器模式__PPB的博客-CSDN博客_springmvc适配器模式]]

[[随遇而安的适配器模式 Spring 中的适配器_Java_大头星_InfoQ写作社区]]

[[设计模式 适配器模式及典型应用 - 掘金]]
![[设计模式 适配器模式及典型应用 - 掘金#^57xek8]]
![[设计模式 适配器模式及典型应用 - 掘金#^m50nku]]