适配器模式
- 适配器概念介绍
- 介绍
- 角色
- 工作原理
- 3种适配器模式
- 类适配器模式演示
- 对象适配器模式
- 对象适配器的优点
- 接口适配器模式
- 综合小案例---使用类适配器模式
- power--带转换的电压
- adapter--适配器
- FindAdapter--寻找合适的适配器
- 测试
- 适配器模式总结
- 主要优点
- 主要缺点
- 适用场景
- spring MVC中的适配器模式
- springMVC处理请求流程
- 请求处理方法中适配器模式部分源码探究
- 总结
- 参考文章
适配器概念介绍
1、不同国家的插座是有区别的,如果我们去国外旅游,需要带上国外的插头转换器,来能兼容国外的插座;
2、手机的耳机孔有圆头和扁头,如果扁头的耳机孔想接圆头的耳机就需要一个耳机的转换器;
上述所说的转换器,其实就是适配器;它是用来做兼容的;
介绍
适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
在适配器模式中,我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。
根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联(聚合)关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。
角色
Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。
Adaptee(适配者类—适配接口):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。
缺省适配器模式(Default Adapter Pattern):当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。
工作原理
- 适配器模式:将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容;
- 从用户的角度看不到被适配者;
- 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法;
3种适配器模式
- 类适配器模式
- 对象适配器模式
- 接口适配器模式
类适配器模式演示
以生活中充电器为例,充电器本身相当于适配者 (Adapter),220V 交流电相当于被适配者,我们的目标(target) 想把220V交流电转成5V直流电
要适配的类,即需要将220v电压转化为5v电压
代码语言:javascript复制//中国的电压220V
public class ChinaPower
{
private final Integer outPut=220;
public Integer getOutPut() {
return outPut;
}
}
适配器接口,只负责定义转化需要使用的业务逻辑方法,具体实现交由适配器类完成
代码语言:javascript复制//将电压转化为5v---适配接口
public interface TransTo5V
{
Integer transTo5V();
}
适配器类,继承了ChinaPower,并实现了适配器接口,负责实现讲220v电压转化为5v的具体业务逻辑代码实现
代码语言:javascript复制//适配器类---实现适配器接口
public class ChinaAdapter extends ChinaPower implements TransTo5V
{
//将220v电压转换为5v的
@Override
public Integer transTo5V()
{
//获得被适配类,即我们需要将220v电压转化为5v返回
Integer output=super.getOutPut();
//进行电压转换操作
return output/44;
}
}
Phone类,需要用到适配器进行兼容,这样才可以充电
代码语言:javascript复制//手机需要5v的电压进行充电
public class Phone
{
//通过适配器获得5v的电压
public void charging(ChinaAdapter chinaAdapter)
{
if(5==chinaAdapter.transTo5V())
{
System.out.println("得到5v,充电中...");
}
else
{
System.out.println("电压过高,手机压力过大");
}
}
}
充电测试
代码语言:javascript复制public class test
{
@Test
public void test()
{
Phone p=new Phone();
p.charging(new ChinaAdapter());
}
}
对象适配器模式
还是以上面的例子为例,这一次适配器类不再继承ChinaPower ,而是以聚合的方式来代替继承,符合设计模式中的"合成复用原则";java是单继承机制,这样可以保留对象继承权;
我们只需要修改适配器类即可:
代码语言:javascript复制//适配器类---实现适配器接口
public class ChinaAdapter implements TransTo5V
{
private ChinaPower chinaPower;
//通过构造器,完成赋值
public ChinaAdapter(ChinaPower chinaPower)
{
this.chinaPower=chinaPower;
}
//将220v电压转换为5v的
@Override
public Integer transTo5V()
{
//获得被适配类,即我们需要将220v电压转化为5v返回
Integer output=chinaPower.getOutPut();
//进行电压转换操作
return output/44;
}
}
测试
代码语言:javascript复制public class test
{
@Test
public void test()
{
Phone p=new Phone();
p.charging(new ChinaAdapter(new ChinaPower()));
}
}
对象适配器的优点
对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承被适配者的局限性问题;
接口适配器模式
- 接口适配器模式(Default Adapter Pattern),也叫缺省适配器模式;
- 核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),该抽象类的子类可有选择地覆盖父类的某些方法来实现需求;
- 适用于一个接口不想使用其所有的方法的情况;
定义一个适配器接口:
代码语言:javascript复制public interface InterfaceTest {
public void m1();
public void m2();
public void m3();
public void m4();
}
抽象类 AbsAdapter 将 InterfaceTest 的方法进行默认实现,当子类需要使用适配器接口中的某个方法,而不是全部方法时,就可以通过继承抽象类,来完成对需要使用的特定方法重写操作即可,无需实现适配器接口里面的全部方法
代码语言:javascript复制public abstract class AbsAdapter implements InterfaceTest {
//默认实现
public void m1() {}
public void m2() {}
public void m3() {}
public void m4() {}
}
Client 调用接口,重写适配器抽象类方法
代码语言:javascript复制public class Client {
public static void main(String[] args) {
AbsAdapter absAdapter = new AbsAdapter() {
//只需要去覆盖我们 需要使用 接口方法
@Override
public void m1() {
System.out.println("使用了m1的方法");
}
};
absAdapter.m1();
}
}
综合小案例—使用类适配器模式
power–带转换的电压
一个顶层接口Power
代码语言:javascript复制public interface Power
{
Integer getOutPut();
}
分支一: 中国的220v电压
代码语言:javascript复制//中国的电压220V
public class ChinaPower implements Power
{
private final Integer outPut=220;
@Override
public Integer getOutPut() {
return outPut;
}
}
分支二: 日本的110v电压
代码语言:javascript复制//日本电压110v
public class JapenPower implements Power
{
private final Integer output=110;
@Override
public Integer getOutPut() {
return output;
}
}
adapter–适配器
失配器接口–DC5Adapter
代码语言:javascript复制//适配器接口
public interface DC5Adapter
{
boolean support(Power power);
Integer transTo5V(Power power);
}
适配器类—ChinaAdapter–只负责将220v电压进行转换的工作
代码语言:javascript复制//该适配器负责将中国的220v电压转化为5v的电压
public class ChinaAdapter implements DC5Adapter
{
//当前适配器只负责将220v电压转化为5v的功能实现
private static Integer voltage=220;
//判断当前适配器能否胜任传入power电压的转化职责
@Override
public boolean support(Power power)
{
if(power.getOutPut().equals(voltage))
return true;
return false;
}
//将220v电压转换为5v的
@Override
public Integer transTo5V(Power power)
{
//获得被适配类,即我们需要将220v电压转化为5v返回
Integer output=power.getOutPut();
//进行电压转换操作
return output/44;
}
}
适配器类—JapenAdapter–只负责将110v电压进行转换的工作
代码语言:javascript复制//该适配器负责将日本的110v电压转化为5v的电压
public class JapenAdapter implements DC5Adapter
{
//当前适配器只负责将110v电压转化为5v的功能实现
private static Integer voltage=110;
//判断是否支持将日本110v电压转化为5v电压的操作
@Override
public boolean support(Power power) {
if(power.getOutPut()==voltage)
return true;
return false;
}
//将110v电压转换为5v的
@Override
public Integer transTo5V(Power power)
{
//获得被适配类,即我们需要将110v电压转化为5v返回
Integer output=power.getOutPut();
//进行电压转换操作
return output/22;
}
}
FindAdapter–寻找合适的适配器
代码语言:javascript复制//手机需要5v的电压进行充电
public class FindAdapter
{
//存放所有适配器的set集合
private static final Set<DC5Adapter> DC5Adapters=new HashSet<>();
//通过静态代码块进行初始化操作
static
{
DC5Adapters.add(new ChinaAdapter());
DC5Adapters.add(new JapenAdapter());
}
// 根据电压找合适的变压器
public DC5Adapter getPowerAdapter(Power power)
{
DC5Adapter dc5Adapter=null;
for(DC5Adapter da:DC5Adapters)
{
//如果遍历到当前电压合适的变压器就直接退出遍历
if(da.support(power))
{
dc5Adapter=da;
break;
}
}
//如果遍历完所有的变压器都没有找到合适的,就抛出异常
if(dc5Adapter==null)
{
throw new IllegalArgumentException("未能找到合适的变压器");
}
//返回找到的合适的变压器
return dc5Adapter;
}
}
测试
代码语言:javascript复制public class test
{
@Test
public void test()
{
//找寻合适的变压器是第一步
FindAdapter fa=new FindAdapter();
//找寻可以将220v转化为5v的变压器,即适配器
DC5Adapter adapter = fa.getPowerAdapter(new ChinaPower());
//输出当前变压器转化之后的电压
System.out.println(adapter.transTo5V(new ChinaPower()));
}
}
适配器模式总结
主要优点
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
- 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
- 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
具体来说,类适配器模式还有如下优点:
- 由于适配器类是适配者类(适配器接口或适配器接口实现的抽象类)的子类,因此可以在适配器类中置换一些适配者(适配器接口或适配器接口实现的抽象类)的方法,使得适配器的灵活性更强。
- 一个对象适配器可以把多个不同的适配者(适配器接口或适配器接口实现的抽象类)适配到同一个目标;
- 可以适配一个适配者的子类,由于适配器和适配者(适配器接口或适配器接口实现的抽象类)之间是关联关系,根据“里氏代换原则”,适配者(适配器接口或适配器接口实现的抽象类)的子类也可通过该适配器进行适配。
主要缺点
- 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类(适配器接口或适配器接口实现的抽象类),不能同时适配多个适配者;
- 适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;
- 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。
- 与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。
适用场景
- 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
- 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
spring MVC中的适配器模式
springMVC处理请求流程
- 第1步 用户发送请求至前端控制器DispatcherServlet;
- 第2,3步DispatcherServlet收到请求,根据请求url调用HandlerMapping处理器映射器找到具体的处理器;生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;
- 第4步 DispatcherServlet通过HandlerAdapter处理器适配器调用具体的处理器;(这一步用到适配者模式)
- 第5,6步 执行处理器(Controller,也叫后端控制器),返回ModelAndView;
- 第7步 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
- 第8步DispatcherServlet将ModelAndView传给ViewReslover视图解析器(但是如果加上@responsebody注解,则返回值不通过viewResolver,而是直接返回object);
- 第9步 ViewReslover解析后返回具体View;
- 第10步 DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中);
- 第11步 DispatcherServlet响应用户。
SpringMVM 中的 HandlerAdapter(上图的第4步), 就使用了适配器模式;
请求处理方法中适配器模式部分源码探究
Spring MVC中的适配器模式主要用于执行目标 Controller
中的请求处理方法。
在Spring MVC中,DispatcherServlet
作为用户,HandlerAdapter
作为期望接口(适配器接口),具体的适配器实现类用于对目标类进行适配,Controller
作为需要适配的类。
为什么要在 Spring MVC 中使用适配器模式?Spring MVC 中的 Controller
种类众多,不同类型的 Controller
通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet
直接获取对应类型的 Controller
,需要的自行来判断,像下面这段代码一样:
if(mappedHandler.getHandler() instanceof MultiActionController){
((MultiActionController)mappedHandler.getHandler()).xxx
}else if(mappedHandler.getHandler() instanceof XXX){
...
}else if(...){
...
}
这样假设如果我们增加一个 HardController
,就要在代码中加入一行 if(mappedHandler.getHandler() instanceof HardController)
,这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。
我们来看看源码,首先是适配器接口 HandlerAdapter
//适配器接口
public interface HandlerAdapter
{
//判断当前的controller请求是否能被当前的适配器类处理
boolean supports(Object var1);
//只有当支持处理当前请求后,才会执行下面的处理请求方法,返回一个ModelAndView对象
ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
long getLastModified(HttpServletRequest var1, Object var2);
}
现该接口的适配器每一个 Controller
都有一个适配器与之对应,这样的话,每自定义一个 Controller
需要定义一个实现 HandlerAdapter
的适配器。
springmvc 中提供的 Controller
实现类有如下:
springmvc 中提供的 HandlerAdapter
实现类如下
HttpRequestHandlerAdapter 这个适配器代码如下:
代码语言:javascript复制//不同的适配器类实现不同的功能
//当前的HttpRequestHandlerAdapter 适配器类,只负责处理关于HttpRequest相关的请求
public class HttpRequestHandlerAdapter implements HandlerAdapter {
public HttpRequestHandlerAdapter() {
}
//判断当前的controller请求是否是HttpRequestHandler类型的
//当前适配器只支持处理当前类型的handler
public boolean supports(Object handler) {
return handler instanceof HttpRequestHandler;
}
//如果验证支持,会调用下面这个方法进行具体逻辑处理
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//先进行强制类型转换,转换为指定的handler类型,然后就可以调用该类型处理对应请求的方法了
//调用HttpRequestHandler的handleRequest处理对应的请求
((HttpRequestHandler)handler).handleRequest(request, response);
return null;
}
public long getLastModified(HttpServletRequest request, Object handler) {
return handler instanceof LastModified ? ((LastModified)handler).getLastModified(request) : -1L;
}
}
当Spring容器启动后,会将所有定义好的适配器对象存放在一个List集合中,当一个请求来临时,DispatcherServlet
会通过 handler
的类型找到对应适配器,并将该适配器对象返回给用户,然后就可以统一通过适配器的 hanle()
方法来调用 Controller
中的用于处理请求的方法。
public class DispatcherServlet extends FrameworkServlet {
//用于存放所有HandlerAdapter适配器类的list集合
private List<HandlerAdapter> handlerAdapters;
//初始化handlerAdapters
private void initHandlerAdapters(ApplicationContext context) {
//..省略...
}
// 遍历所有的 HandlerAdapters,通过 supports 判断找到匹配的适配器
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" ha "]");
}
if (ha.supports(handler)) {
return ha;
}
}
}
// 分发请求,请求需要找到匹配的适配器来处理
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
//找到能处理当前processedRequest,即request请求的handler
mappedHandler = getHandler(processedRequest);
// 确定当前handler匹配的适配器类.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
ha.getLastModified(request, mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
// ...省略...
}
通过适配器模式我们将所有的 controller
统一交给 HandlerAdapter
处理,免去了写大量的 if-else
语句对 Controller
进行判断,也更利于扩展新的 Controller
类型。
总结
使用 HandlerAdapter
的原因分析:
如果处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果直接调用 Controller
方法,就得不断使用 if else
来进行判断是哪一种子类然后执行。那么如果后面要扩展 Controller
,就得修改原来的代码,这样违背了 OCP
原则;
说明:
- Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类;
- 适配器代替 controller执行相应的方法;
- 扩展Controller时,只需要增加一个适配器类就完成了SpringMVC的扩展;
参考文章
设计模式 8 - 适配器模式与springmvc源码分析 设计模式 | 适配器模式及典型应用 适配器模式(SpringMVC源码分析) 设计模式 | 适配器模式及典型应用