设计模式(十三):适配器模式(Adapter)
在现实生活中,经常出现两个对象因接口不兼容而不能在一起工作的实例,这时需要第三者进行适配(转换)。例如,使用电脑电源适配器将220V家用电转换为电脑需要的电。
在软件设计中,也可能出现需要的功能在现有的组件库中已存在,但与现有系统并不兼容,这时使用适配器模式就可以将并不兼容的接口转换为客户希望的目标接口。
使用适配器模式来处理像货物的包装过程,被包装的货物的真实样子被包装所掩盖和改变,因此也被叫做包装(Wrapper)模式。
模式定义
适配器模式(Adapter Pattern) :将一个接口转换成客户希望的另一个接口,使原本因不兼容而无法一起工作的那些接口能在一起工作。其别名为包装器(Wrapper)。
适配器模式目的是解决接口不兼容问题,通过增加中间适配器使源和目标能对接。例如,出国旅游需要准备插座转换头以给手机充电器用,这个转换头就相当于适配器,不同国家的插座标准不一样,需要转换头将国内的手机充电头与旅游国插座进行适配。本人去过印尼巴厘岛,其电源插座是德标,双圆孔插座,要给手机充电需要带德标的转换插头。
适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
类结构型模式
类的适配器模式把被适配的类(源类)的 API 转换成为目标类的 API。适配器类把 源类(Adaptee)的 API 与 目标类(Target)的 API 衔接起来。
适配器类与源类(Adaptee)是继承关系,这决定了这个适配器模式是类的。
对象结构型模式
对象的适配器模式把被适配的类的 API 转换成为目标类的 API,与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到 Adaptee 类,而是使用委派关系连接到 Adaptee 类。
提供一个包装(Wrapper)类 Adapter,这个包装类包装了一个 Adaptee 的实例,从而此包装类能够把 Adaptee 的 API 与 Target 类的 API 衔接起来。Adapter 与 Adaptee 是委派关系,这决定了此适配器模式是对象的。
适配器模式的用意是将接口不同而功能相同或相近的两个接口加以转换,这里面包括适配器角色补充了源角色没有的方法,但是目标接口需要的方法。
模式分析
模式结构
适配器模式(Adapter)所涉及的主要角色:
- 源(Adaptee)角色:现有需要适配的接口。
- 目标(target)角色:就是所期待得到的接口,目标可以是具体的或抽象的类。
- 适配器(Adapter)角色:适配器类,继承或引用 源角色,重定义接口适配到目标接口。
适配器类是该模式的核心,不可以是接口,必须是具体类。
优缺点
优点
- 可以把多种不同的源适配到同一个目标一起工作,将目标类与源类解耦,解决目标类和源类接口不一致的问题。
- 增加了类的透明性和复用性,将具体的实现封装在源类中,对客户端来说是透明的,而且提高了适配器类的复用性。
- 灵活性好。要覆盖源类的方法并不容易,但要增加一些新的方法则很方便,且适用于所有的源。类适配器模式中的适配器是源的子类,可以在适配器中覆盖掉源的一些方法。
缺点
- 过多地使用适配器,会让系统非常零乱,不易整体把握。
- 由于 JAVA 至多继承一个类,所以至多只能适配一个源类,而且目标类必须是抽象类。
结构图与代码
类适配器模式
类适配器模式:适配器继承 源 角色,实现目标接口,在实现的目标接口中调用源角色的接口。
源角色
1 | public abstract class Adaptee { |
目标角色
1 | public interface Target { |
适配器角色
1 | public class Adapter extends Adaptee implements Target { |
客户端调用
1 | public class Client { |
对象适配器模式
类适配器模式:适配器引用 源 角色,实现目标接口,在实现的目标接口中委托 源 对象调用接口。
源角色
1 | public class Adaptee { |
目标角色
1 | public interface Target { |
适配器角色
1 | public class Adapter implements Target { |
客户端调用
1 | public class Client { |
应用场景
- 系统需要使用现有的类,而此类的接口不符合系统的需求。
- 构建一个可以重复使用的类,用于将一些没有太大关联的类衔接起来一起工作,这些源类不一定是很复杂的接口。
- 当一个客户端只知道一个特定的接口,但又必须与具有不同接口的类打交道时,就应当使用适配器模式。
缺省适配器
即一个抽象类实现了目标接口,抽象类中对某些方法提供默认实现,适配器类继承抽象类,只需重写部分方法,而不是目标接口的所有方法,即适配器类不必实现不需要的那些接口。。
注意事项
- 目标接口可以省略。此时,目标接口和源接口就是相同的。因源是一个接口,而适配器是一个类(或抽象类),就不必要实现不需要的方法。
- 适配器类可以是抽象类。
- 带参的的适配器模式。使用此方式,适配器可以根据参数返还一个合适的实例。
典型应用
JDBC驱动与适配器模式
JDK 提供的数据库连接工具 JDBC是一套通用接口标准,使用 Java 语言程序可以连接到数据库上。每一个数据库引擎的 JDBC 驱动软件都是一个介于 JDBC 接口和数据库引擎接口之间的适配器软件。
源:各个数据库引擎提供的 API。
目标:JDK 提供的抽象的 JDBC 接口。例如:Connection 接口,DataSource 接口。
适配器:各个数据库提供的 JDBC 驱动软件(库)。例如:MySQL 的 mysql-connector-java, 提供的 ConnectionWrapper 对连接进行了包装,MysqlDataSource 实现的是 DataSource 接口。
所以开发在实际使用的时候,很少关注具体的数据库驱动软件的接口,全是基于 JDK 的 JDBC 操作,也就是各个数据库引擎的 API 接口差异被屏蔽了,是通过驱动软件采用的适配器模式适配到目标 JDK 的 JDBC 接口。
JDK中的InputStreamAdapter
JDK 库中的也有一些适配器类,如 InputStreamAdapter,继承了 InputStream 接口,包装了 ImageInputStream 接口及其子类对象。
InputStreamAdapter 中采用的是类适配器模式结构,源码如下:
- 源:ImageInputStream
- 目标:InputStream
- 适配器:InputStreamAdapter
1 | public class InputStreamAdapter extends InputStream { |
Spring MVC 中的适配器模式
Spring MVC 中的处理器适配器也是适配器模式的典型应用。
DispatcherServlet 做请求分发处理时,要找到对应的处理控制器(Controller)及方法。Spring MVC 中有多种类型的 Controller,不同类型的 Controller 都有自己的内部方法来处理请求(不同的方法来对请求进行处理)。
1 |
|
如果不使用适配器的话,就需要在 DispatcherServlet 中判断每个类型的 Controller 是否支持当前请求的 映射处理器。类似如下:
1 | if(mappedHandler.getHandler() instanceof MultiActionController){ |
使用适配器后,上面的工作就交由适配器完成了,DispatcherServlet 只需要拿到适配器调用统一的接口方法(ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
)就行了。
适配器结构分析:
- 源:不同类型的 Controller 的请求处理方法
- 目标:HandlerAdapter,处理器适配器抽象
- 适配器:HandlerAdapter 的实现类。将 DispatcherServlet 调用 Controller 处理请求的方法通过 HandlerAdapter 适配。
真正调用控制器方法(Controller 中的方法)的就交由 具体的处理器适配器 完成,处理器适配器需要根据传入的 映射处理器 找到与 Controller 匹配的处理器适配器。源码如下:
1 | public class DispatcherServlet extends FrameworkServlet { |
HandlerAdapter 是一个处理器适配器的抽象接口,其中主要有两个方法:
1 | public interface HandlerAdapter { |
HandlerAdapter 有如下几个默认实现,其中后面四个在 DispatcherServlet 启动时会被初始化到 List<HandlerAdapter> handlerAdapters
集合中
- AbstractHandlerMethodAdapter
- SimpleServletHandlerAdapter
- HandlerFunctionAdapter
- HttpRequestHandlerAdapter
- RequestMappingHandlerAdapter 继承自 AbstractHandlerMethodAdapter
- SimpleControllerHandlerAdapter
Spring AOP 中的适配器模式
Spring AOP 的实现是基于动态代里的,但 Spring AOP 的增强和通知(Advice)使用了适配器模式,与之相关的接口是 AdvisorAdapter。
通知 Advice 的类型有:MethodBeforeAdvice
,AfterReturningAdvice
,ThrowsAdvice
。
与 Advice 类型对应的适配器有:MethodBeforeAdviceAdapter
,AfterReturningAdviceAdapter
,ThrowsAdviceAdapter
。
适配器结构分析:
- 源:不同通知类型的处理方法(
before(...), proceed(), afterReturning(...)
) - 目标:AdvisorAdapter 的抽象接口,提供判断通知类型和获取通知对应的拦截器
- 适配器:AdvisorAdapter 的实现类,获取具体的通知拦截器交由具体的适配器完成
适配器有两个方法,一个是判断通知类型是否匹配,另一个是将通知(Advice)构建为对应的拦截器,原码如下:
1 | public interface AdvisorAdapter { |
默认的适配器注册实现:
1 | public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable { |
示例代码
类的适配器模式结构
目标接口
1
2
3
4
5
6
7/**
* 目标接口
*/
public interface Target {
void sampleOperation1();
void sampleOperation2();
}源(需要被适配的类)
1
2
3
4
5
6/**
* 源(需要适配的类)
*/
public class Adaptee {
public void sampleOperation1(){}
}适配器类
一个适配器类把源(Adaptee)适配到目标(Target)中,适配器类是源的子类,可以 Override 源的一些方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14/**
* 适配器类
*/
public class Adapter extends Adaptee implements Target {
/**
* 源类没有方法sampleOperation2,
* 因此适配器类补充上这个方法
*/
public void sampleOperation2() {
//重写方法
}
}
对象的适配器模式结构
目标接口
1
2
3
4
5
6
7
8
9/**
* 目标接口
*/
public interface Target {
//源类有此方法
void sampleOperation1();
//源类无此方法
void sampleOperation2();
}源(需要被适配的类)
1
2
3
4public class Adaptee {
//源类有此方法
public void sampleOperation1(){}
}适配器类
适配器类,设置源类型属性,委托结构。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class Adapter implements Target {
//源
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
super();
this.adaptee = adaptee;
}
public void sampleOperation1() {
adaptee.sampleOperation1();
}
/**
* 源类没有此方法
* 适配器类补充此方法
*/
public void sampleOperation2() {
//重写方法
}
}上面的2个示例中的适配器角色补充了一个源角色没有的方法,但目标接口需要的方法。
相关参考
设计模式(十三):适配器模式(Adapter)
http://blog.gxitsky.com/2019/12/30/DesignPatterns-13-Adapter/