前言

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

适配器模式

1、定义

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

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

2、UML类图



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

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

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

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

3、实战例子

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

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

Target目标接口

  1. public interface Voltage5 {
  2. int output5V();
  3. }

目标接口的实现类

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

Adaptee类

  1. public class Voltage220 {
  2. // 输出220V的电压
  3. public int output220V() {
  4. int src = 220;
  5. System.out.println("源电压是:" + src + "V");
  6. return src;
  7. }
  8. }

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

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

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

  1. /**
  2. * 适配器模式
  3. */
  4. public class Client {
  5. public static void main(String[] args) {
  6. Voltage5 voltage5 = new ConcreteVoltage5();
  7. voltage5.output5V();
  8. // 创建一个适配器对象
  9. VoltageAdapter2 voltageAdapter = new VoltageAdapter2();
  10. // 转换电压
  11. voltageAdapter.output5V();
  12. }
  13. }

结果输出

  1. 目标电压:5V
  2. 源电压是:220V
  3. 对象适配器工作,开始适配电压
  4. 适配完成后输出电压: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

  1. public interface HandlerAdapter {
  2. boolean supports(Object var1);
  3. ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
  4. long getLastModified(HttpServletRequest var1, Object var2);
  5. }

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

  1. public class HttpRequestHandlerAdapter implements HandlerAdapter {
  2. @Override
  3. public boolean supports(Object handler) {
  4. return (handler instanceof HttpRequestHandler);
  5. }
  6. @Override
  7. public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
  8. throws Exception {
  9. ((HttpRequestHandler) handler).handleRequest(request, response);
  10. return null;
  11. }
  12. @Override
  13. public long getLastModified(HttpServletRequest request, Object handler) {
  14. if (handler instanceof LastModified) {
  15. return ((LastModified) handler).getLastModified(request);
  16. }
  17. return -1L;
  18. }
  19. }

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

  1. public class DispatcherServlet extends FrameworkServlet {
  2. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  3. //.........................
  4. //找到匹配当前请求对应的适配器
  5. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  6. // Process last-modified header, if supported by the handler.
  7. String method = request.getMethod();
  8. boolean isGet = "GET".equals(method);
  9. if (isGet || "HEAD".equals(method)) {
  10. long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
  11. if (logger.isDebugEnabled()) {
  12. String requestUri = urlPathHelper.getRequestUri(request);
  13. logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
  14. }
  15. if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
  16. return;
  17. }
  18. }
  19. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  20. return;
  21. }
  22. try {
  23. // Actually invoke the handler.
  24. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  25. }finally {
  26. if (asyncManager.isConcurrentHandlingStarted()) {
  27. return;
  28. }
  29. }
  30. }
  31. // 遍历集合,找到合适的匹配器
  32. protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
  33. for (HandlerAdapter ha : this.handlerAdapters) {
  34. if (logger.isTraceEnabled()) {
  35. logger.trace("Testing handler adapter [" + ha + "]");
  36. }
  37. if (ha.supports(handler)) {
  38. return ha;
  39. }
  40. }
  41. throw new ServletException("No adapter for handler [" + handler +
  42. "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
  43. }
  44. }

这样一来,所有的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. Python程序中的协程操作-gevent模块

    目录 一.安装 二.Gevent模块介绍 2.1 用法介绍 2.2 例:遇到io主动切换 2.3 查看threading.current_thread().getName() 三.Gevent之同步与 ...

  2. SQL Server温故系列(2):SQL 数据操作 CRUD 之简单查询

    1.查询语句 SELECT 1.1.查询语句的 SELECT 子句 1.2.查询语句的 FROM 子句 1.2.1.内连接查询 INNER JOIN 1.2.2.外连接查询 OUTER JOIN 1. ...

  3. 风险:隐蔽权限修改导致rgw服务中断

    上午正在开会,突然收到rgw服务异常的告警(503 Service Unavailable),立马停下来处理告警,避免影响到用户~   我们的rgw frontend用的是apache,之前也遇到过5 ...

  4. HDU 2795:Billboard(线段树)

    http://acm.hdu.edu.cn/showproblem.php?pid=2795 Billboard Problem Description   At the entrance to th ...

  5. ajax请求中 两种csrftoken的发送方法

    通过ajax的方式发送两个数据进行加法运算 html页面 <body> <h3>index页面 </h3> <input type="text&qu ...

  6. 配置Windows server 用户和组权限实验详解

    目录 操作步骤如下: 在Windows Server开始菜单下点击管理工具下的计算机管理 新建用户 用户创建完毕 新建文件夹 配置技术部读取"技术资料"和"常用软件&qu ...

  7. kindeditor在线文本编辑器过滤HTML的方法

    在使用kindeditor文本编辑器时遇到的问题,客户直接从Excel里粘贴文本内容到文本编辑器中(能不能再懒一些),然后不调整粘贴内容直接就保存(你敢不敢再懒一些)!对于这种很无语的行径,我只能对他 ...

  8. ServiceFabric极简文档-4.0 开发环境搭建

    1. VS2017安装包启动页,安装Azure.(安装的VS的Tool)2. 下载Service Fabric Web PI,安装Service Fabric(自动安装SDK与Runtime)

  9. 【小家Spring】聊聊Spring中的数据绑定 --- BeanWrapper以及内省Introspector和PropertyDescriptor

    #### 每篇一句 > 千古以来要饭的没有要早饭的,知道为什么吗? #### 相关阅读 [[小家Spring]聊聊Spring中的数据转换:Converter.ConversionService ...

  10. IQueryable.Where中动态生成多个并或筛选Expression<Func<T, bool>>

    直接上图