前言

适配器模式是最为普遍的设计模式之一,它不仅广泛应用于代码开发,在日常生活里也很常见。比如笔记本上的电源适配器,可以使用在110~ 220V之间变化的电源,而笔记本还能正常工作,这就是适配器模式最直接的例子,同时也是其思想的体现,简单的说,适配器模式就是把一个类(接口)转换成其他的类(接口)。

适配器模式

1、定义

适配器模式,也叫包装模式,指的是将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。我们可以通过增加一个适配器类来解决接口不兼容的问题,而这个适配器类就相当于笔记本的适配器。

根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。

2、UML类图



适配器模式包含了几个角色,它们是:

Target(目标角色):该角色定义其他类转化成何种接口,可以是一个抽象类或接口,也可以是具体类。

Adaptee(源角色):你想把谁转换成目标角色,这个“谁”就是源角色,它是已经存在的、运行良好的类或对象。

Adapter(适配器角色):适配器模式的核心角色,职责就是通过继承或是类关联的方式把源角色转换为目标角色。

3、实战例子

知道有哪些角色和UML类图后,我们就可以写一下代码了,为了方便理解,我们用生活中充电器的例子来讲解适配器,现在有一个手机要充电,所需要的额定电压是5V,而家用交流电的电压是标准的220V,这种情况下要充电就需要有个适配器来做电压转换。

把三者代入我们上面画的类图不难得出,充电器本身相当于Adapter,220V交流电相当于Adaptee,我们的目标Target是5V直流电。

Target目标接口

public interface Voltage5 {

    int output5V();
}

目标接口的实现类

public class ConcreteVoltage5 implements Voltage5{
@Override
public int output5V() {
int src = 5;
System.out.println("目标电压:" + src + "V");
return src;
}
}

Adaptee类

public class Voltage220 {

	// 输出220V的电压
public int output220V() {
int src = 220;
System.out.println("源电压是:" + src + "V");
return src;
}
}

Adapter类:完成220V-5V的转变

public class VoltageAdapter extends Voltage220 implements Voltage5 {
@Override
public int output5V() {
// 获取到源电压
int adaptee = super.output220V();
// 开始适配操作
System.out.println("对象适配器工作,开始适配电压");
int dst = adaptee / 44;
System.out.println("适配完成后输出电压:" + dst + "V");
return dst;
}
}

通过适配器类的转换,我们就可以把220V的电压转成我们需要的5V电压了,写个场景类测试下:

/**
* 适配器模式
*/
public class Client { public static void main(String[] args) {
Voltage5 voltage5 = new ConcreteVoltage5();
voltage5.output5V();
// 创建一个适配器对象
VoltageAdapter2 voltageAdapter = new VoltageAdapter2();
// 转换电压
voltageAdapter.output5V();
}
}

结果输出

目标电压:5V
源电压是:220V
对象适配器工作,开始适配电压
适配完成后输出电压:5V

前面说了,适配器模式分为两种,我们上面介绍的通过继承的方式实现的是类适配器,还有一种对象适配器,它是通过关联适配器与适配者的方式实现的,它的通用类图如下所示:

代码方面也比较简单,参考上面的例子把继承的写法改为关联即可,代码就不贴了,读者可以自己写一下。

4、总结

下面对适配器模式做一下总结吧

1)优点

  • 能实现目标类和愿角色类的解耦。适配器模式可以让两个没有任何关系的类在一起运行,只要适配器这个角色能够搞定他们就成。
  • 增加了类的透明性。将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
  • 灵活性和扩展性都非常好。某一天,突然不想要适配器,没问题,删除掉这个适配器就可以了,其他的代码都不用 修改,基本上就类似一个灵活的构件,想用就用,不想就卸载。

2)缺点

  • 类适配器:采用继承方式,对Java这种不支持多继承的语言来说,一次只能适配一个适配器类,不太方便

  • 对象适配器:与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

3)适用场景

  • 需要修改到已上线接口时,适配器模式可能是最适合的模式。
  • 系统扩展了,需要使用一个已有或新建的类,但类不符合系统的接口支持,这时就可以引入适配器类来做转换。

SpringMVC底层的适配器模式

这里扩展一下适配器模式的知识点,想必做过Java开发的都知道SpringMVC,这套框架可以帮助我们把前端的请求访问到后台对应的controller的方法上,然后再把处理结果返回给后端,它的底层其实就用到了适配器模式。

SpringMVC中的适配器模式主要用于执行目标Controller中的请求处理方法。在它的底层处理中,DispatcherServlet作为用户,HandlerAdapter作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller作为需要适配的类。

为什么要在 Spring MVC 中使用适配器模式?Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,那样每增加一个类型的Controller就需要使用增加一个if else判断instance of,这违反了设计模式的开闭原则 —— 对扩展开放,对修改关闭。

那么SpringMVC是怎么处理的呢?我们来简单看一下源码,首先是适配器接口HandlerAdapter

public interface HandlerAdapter {
boolean supports(Object var1); ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception; long getLastModified(HttpServletRequest var1, Object var2);
}

该接口的适配器类对应着 Controller,每自定义一个Controller需要定义一个实现HandlerAdapter的适配器。举个例子,有一个适配器类HttpRequestHandlerAdapter ,该类就是实现了HandlerAdapter接口,这是它的源码:

public class HttpRequestHandlerAdapter implements HandlerAdapter {

   @Override
public boolean supports(Object handler) {
return (handler instanceof HttpRequestHandler);
} @Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception { ((HttpRequestHandler) handler).handleRequest(request, response);
return null;
} @Override
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified) {
return ((LastModified) handler).getLastModified(request);
}
return -1L;
} }

当Spring容器启动后,会将所有定义好的适配器对象存放在一个List集合中,当一个请求来临时,DispatcherServlet会通过handler的类型找到对应适配器,并将该适配器对象返回给用户,然后就可以统一通过适配器的handle方法来调用Controller中的用于处理请求的方法。

public class DispatcherServlet extends FrameworkServlet {

	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

		//.........................

		//找到匹配当前请求对应的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
} if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
} try {
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
}
// 遍历集合,找到合适的匹配器
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;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
}

这样一来,所有的controller就都统一交给HandlerAdapter处理,免去了大量的 if-else 语句判断,同时增加controller类型只需增加一个适配器即可,不需要修改到Servlet的逻辑,符合开闭原则。

关于适配器模式的介绍就到这里了,有不足之处还望指出。

参考

《设计模式之禅》

https://blog.csdn.net/wwwdc1012/article/details/82780560

设计模式:与SpringMVC底层息息相关的适配器模式的更多相关文章

  1. 深入源码分析SpringMVC底层原理(二)

    原文链接:深入源码分析SpringMVC底层原理(二) 文章目录 深入分析SpringMVC请求处理过程 1. DispatcherServlet处理请求 1.1 寻找Handler 1.2 没有找到 ...

  2. SpringMVC底层数据传输校验的方案(修改版)

    团队的项目正常运行了很久,但近期偶尔会出现BUG.目前观察到的有两种场景:一是大批量提交业务请求,二是生成批量导出文件.出错后,再执行一次就又正常了. 经过跟踪日志,发现是在Server之间进行jso ...

  3. JAVA设计模式详解(五)----------适配器模式

    各位朋友好,本章节我们继续讲第五个设计模式. 在生活中,我们都知道手机内存卡是无法直接接电脑的,因为内存卡的卡槽比较小,而电脑只有USB插孔,此时我们需要用到读卡器.这个读卡器就相当于是适配器.这是生 ...

  4. 分享知识-快乐自己:SpringMVC 底层执行原理解析

    底层实现原理图: 观看底层代码: 1):打开 web.xml 文件  2):按住 Ctrl + 鼠标左键 进入底层查看源码   3):按住 Ctrl+o 找到对应的方法doDispatch   5): ...

  5. (一)springMvc 底层运作流程

    目录 什么是 springMvc SpringMVC的底层运作流程 什么是 springMvc springMvc 是spring 框架的一个模块,这也就意味着二者不需要通过整合层(整合包)进行整合 ...

  6. SpringMVC底层执行原理

    一个简单的HelloSpringMVC程序 先在web,xml中注册一个前端控制器(DispatcherServlet) <?xml version="1.0" encodi ...

  7. 设计模式(七):Adapter 适配器模式 -- 结构型模式

    1. 概述: 接口的改变,是一个需要程序员们必须(虽然很不情愿)接受和处理的普遍问题.程序提供者们修改他们的代码;系统库被修正;各种程序语言以及相关库的发展和进化.  例子1:iphone4,你即可以 ...

  8. SpringMVC底层数据传输校验的方案

    团队的项目正常运行了很久,但近期偶尔会出现BUG.目前观察到的有两种场景:一是大批量提交业务请求,二是生成批量导出文件.出错后,再执行一次就又正常了. 经过跟踪日志,发现是在Server之间进行jso ...

  9. 设计模式PHP篇(三)————适配器模式

    简单的适配器模式: interface Adaptor { public function read(); public function write(); } class File implemen ...

随机推荐

  1. black box黑盒测试

    软件规格说明书 等价类划分,完备性,无冗余性(不能有交集).   健壮等价类:无效等价类 边界值分析,对于一个含有n个变量的程序,采用边界值分析法测试程序会产生4n+1个测试用例           ...

  2. leadcode的Hot100系列--617. 合并二叉树

    合并,就是两个树的结构交集部分,数据相加,否则,取非空部分. 所以,这里相当于是对两棵树同时遍历: 如果两棵树节点都不为空,则数据相加, 否则,直接指针把不为空的节点复制过来. 注:这里没有申请内存, ...

  3. 并发编程-concurrent指南-原子操作类-AtomicReference

    1.类 AtomicReference<V> public class AtomicReference<V>extends Objectimplements Serializa ...

  4. Ng-Matero:基于 Angular Material 搭建的中后台管理框架

    前言 目前市面上关于 Angular Material 的后台框架比较少,大多都是收费主题,而且都不太好用. 很多人都说 Material 是一个面向 C 端的框架,其实在使用其它框架做管理系统的时候 ...

  5. Linux中修改远程地址

    首先跳转到本地用户root,如果不是的话可能没有权限 第一步:安装ssh服务 执行命令:yum install openssh-server (因为我已经安装过了,所以显示的是已安装) 第二步:修改S ...

  6. DRF 视图

    目录 一.DRF中的Request 二.前戏: 关于面向对象的继承 三.初级版本 1. settings.py文件 -- 注册app 2. models.py文件 -- 创建表 3. admin.py ...

  7. 如何实现LRU算法?

    1.什么是LRU算法? LRU是一种缓存淘汰机制策略. 计算机的缓存容量有限,如果缓存满了就要删除一些内容,给新的内容腾位置.但是要删除哪些内容呢?我们肯定希望删掉那些没有用的缓存,而把有用的数据继续 ...

  8. C语言学习书籍推荐《C语言接口与实现:创建可重用软件的技术》下载

    <C语言接口与实现:创建可重用软件的技术>概念清晰.实例详尽,是一本有关设计.实现和有效使用C语言库函数,掌握创建可重用C语言软件模块技术的参考指南.书中提供了大量实例,重在阐述如何用一种 ...

  9. 你不得不知的几个互联网ID生成器方案

    服务化.分布式已成为当下系统开发的首选,高并发操作在数据存储时,需要一套id生成器服务,来保证分布式情况下全局唯一性,以确保系统的订单创建.交易支付等场景下数据的唯一性,否则将造成不可估量的损失. 基 ...

  10. 基于百度EasyDL定制化图像识别平台的海洋鱼类识别方法

    [目的]鱼类识别对渔业资源的开发利用有着重要的意义.针对海底环境恶劣.拍摄环境亮度低.场景模糊的实际情况导致海底观测视频品质差,视频中的鱼类识别难的问题以及现有鱼类识别方法存在的鱼类标注数据集过少导致 ...