前言
由于所在行业的需求,需要跟许多第三方系统进行接口对接,并且虽然每个系统大同小异,但每次对接开发都采用重写一套,独立维护,对接时间久了就开始考虑有什么方式能将这么多的三方系统接口接入现有系统时统一规范化,按照固定模式接入,带着这个疑惑我开始四处寻求答案。在近期参加的《Head First Design Patterns》技术读书营中,本着学习设计模式,提高编码设计能力为目的,我看到适配器模式之后,明白它就是解除我疑惑的那个答案,紧接深入学习研究,于是就有了这篇文章。那么这是个怎样的模式,以及如何使用的呢,接下来就让我们学习下吧。
正文
什么是适配器模式
首先我们来看下维基百科的定义:
In software engineering, the adapter pattern is a software design pattern that allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code.
大概意思就是将一个已存在类的接口转换为另一个接口去使用,使得在不需要修改原有代码的情况下使得原本接口不兼容的类,能与其他类正常工作。这里强调了不改动原有系统的源代码的情况下,对不兼容的接口进行适配,其实就是一层转换,转换成已有系统所采用的接口方式。
除此之外,再来看下 Gang of Four 的《设计模式》中的定义:
“Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.”
这里主要还是描述适配器的行为:将类的接口转为客户端所期待的接口,也就是可直接使用的,最终消除的就是接口之间的不兼容性。
大概了解适配器模式的定义,我们再来看个生活的例子,来更好理解下适配器模式:
自 iPhone7 开始,iPhone 已经取消了耳机插孔, 而这样一来,就无法直接使用苹果有线耳机了。那该怎么办呢,随即苹果推出耳机转接线,来协调无耳机插孔的 iPhone 和耳机之间连接,一端支持苹果正常的充电插口,另端支持苹果耳机插口, 用过转换保证即使无耳机插孔的 iPhone 能正常使用有线耳机,这就是通过特定的转换来解决两个事物的兼容问题。
耳机转接线
适配器模式结构
清楚什么是适配器模式之后,我们再来了解下适配器模式的结构以及如何识别它。
首先我们认识下适配器模式中存在的主要角色,看下他们是如何在适配器模式扮演怎样的角色:
- Target 目标类:要把其他类转换为我们期望接口的类。
- Adaptee 被适配者:想要转换成目标角色的类.
- Adapter 适配者:需要要新建,并且要把 Adaptee 被适配者类转换为 Target 目标类,转换方式一般采用类继承或者对象组合的方式.
- Client 客户端类:通常为我们原有系统中与 Target 目标类交互的类。
适配器模式通常有两种存在的形式:对象适配器和类适配器。差异主要就体现在模式结构上,接下来就看下这两种形式的 UML 图:
- 对象适配器 UML 图
对象适配器
- 类适配器 UML 图
类适配器
从上面两张图可以看出对象适配器与类适配器的区别在于适配器类与被适配者的关系,是采用组合的方式还是继承。所谓组合,就是拥有该类型的实例成员变量。
对象适配器可以适配某个类以及其任何子类,而类适配器只能适配特定的类,但它不需要重新实现被适配者类的所有方法,并且必要时可以覆写某些方法。
有了 UML 类这里我们看下适配器模式的通用代码实现:
carbon
所有角色类已经有了,就来看下客户端类如何使用适配器类:
carbon
有了实现的代码,我们再来试着画出对应时序图,来更清楚地认识类之前调用关系:
image-20190630155053150
在前文提到的 iPhone 与耳机之间的适配问题例子中, 耳机转接线无疑就是 Adapter 角色, iPhone 的充电接口为 Target 角色, 耳机接口为 Adaptee 角色。
适配器模式实现案例
现在我们再用代码来实现另一个适配器案例:假设我们有一台笔记本和一个显示器,想要笔记本屏幕投影到显示器上,但是笔记本只支持 HDMI 接口输出,显示器只支持 VGA 接口输入, 想要它们之间正常工作,就需要用接口适配器来进行转换.
首先我们看下目标类 HDMI 代码以及要转换为目标类的 Adaptee 类:
carbon
那么想要将 HDMI 与 VGA 连接起来的 Adapter 类 VGA2HDMIAdapter 又是什么样的呢,请看下方代码:
carbon
最后看下客户端类中的使用:
carbon
这样一个简单的适配器模式案例就完成了,虽然代码简单,但类之间的组合使用很好地说明了适配器模式的结构。HDMI 接口作为目标对象,而 VGA 接口为被适配对象,为了能在 Laptop 对象上工作,通过了适配器对象的转换 VGA2HDMIAdapter 。
常见的适配器用法
适配器模式也算是十分常用的设计模式,我们可以在很多框架的源码里见到,在 JDK 中适配器也有很多的应用,这里我们就进行简单介绍:
JDK-InputStreamReader
carbon
上面代码:System.in
实际是类型为 InputStream,而由于 BufferedReader 与 InputStream 不能一起工作,于是引入 BufferedReader 类,作为适配器类,将 InputStreamReader 类的接口转成 BufferedReader 类可用的接口。
JDK-java.util.Collections#enumeration
carbon
在这里,Enumeration 作为被适配者类,Iterator 为目标类,为了让 Iterator 能提供遍历元素的功能,提供了 EnumerationIterator 作为适配器类。
SpringMVC-RequestMappingHandlerAdapter
仅从命名方式上就可以看到 RequestMappingHandlerAdapter 属于适配器模式中适配器角色,用于处理带有 @RequestMapping 注解方法,当 Spring MVC 接受请求时会根据 URL 匹配对应的 RequestMappingHandlerAdapter,调用它的 invokeHandlerMethod()
方法。
适配器模式应用场景
了解那么多关于适配器的使用,我们再来总结下使用适配器模式的常见场景:
- 当已存在类的接口无法满足的所需要接口的功能,即接口之间不兼容。
- 当需要创建一个可重用的类,而该类能与多个不同类一起工作。
- 大多数使用第三方库的应用程序可以使用适配器作为应用程序和第三方库之间的一个中间层,使应用程序与三方库解耦。
结语
本文学习总结了适配器模式的定义和用法,以及具体使用场景。
参考
- 设计模式[1]-什么是四人帮(Gang of Four):https://blog.csdn.net/gladyoucame/article/details/84535764
- Patterns-Adapter:https://java-design-patterns.com/patterns/adapter/
- 《设计模式之禅》:https://book.douban.com/subject/4260618/
- Adapter Design Pattern in Java:https://howtodoinjava.com/design-patterns/structural/adapter-design-pattern-in-java/
- 《Head First Design Patterns》:https://book.douban.com/subject/1400656/