适配器模式Adapter Pattern——谁说两个接口风马牛不相及!
在适配器模式中,我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。
一、 先从一个问题说起
之前负责的项目中遇到过一个问题:
在InterceptorAdapter拦截器中对请求进行验证,把验证信息传给日志服务平台做统计。正常普通接口请求,request.getParameter()可以获取,能多次读取。
但是,如果我们的接口是用@RequestBody来接受数据,就会报异常:java.io.IOException: Stream closed。
这是因为流是一次性的,@RequestBody注解也是根据流的形式获取参数,拦截器中 request 使用流获取参数后,@RequestBody注解就不能再次使用了。
两个都要使用stream,流又是一次性的,那这个问题怎么解决?有个重写HttpServletRequestWrapper的方案,这个HttpServletRequestWrapper就是一个包装类,使用了适配器模式,因此适配器又叫包装模式。大家可以在看完本文后,去研究下HttpServletRequestWrapper,这也是适配器模式的具体应用。
二、什么适配器模式?
适配器模式的主要作用是将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
怎么理解?就好比下面的图:
这个老显示器只兼容VGA数据线,但是当前只有一条HDMI的数据线,怎么才能使用这个显示器呢?
那就是加上一个HDMI转VGA的转换器,把HDMI的数据线转成VGA接口即可,但这里真正工作的还是HDMI的数据线,并不是制造出了一条VGA数据线。
所以适配器起到的是一个中间转换的作用,把原来不兼容的东西组合起来,实现想要的功能。
三、 适配器模式实现
GOF中将适配器模式分为类适配器模式和对象适配器模式。
- 在对象适配器模式中,适配器与适配者之间是关联关系;
- 在类适配器模式中,适配器与适配者之间是继承(或实现)关系。
这么说有点难理解,那就先看代码,适配器的实现是比较简单的,无论是HDMI数据线还是VGA数据线都是一种标准,对应代码中接口就是标准。
3.1、公共代码
HDMI接口:
public interface HDMIInterface {
public void showWithHDMI();
}
VGA接口:
public interface VGAInterface {
public void showWithVGA();
}
现在只有一条HDMI数据线:
public class HDMIDataLine implements HDMIInterface {
@Override
public void showWithHDMI() {
Console.log("我是HDMI高清数据线,是我在传输数据...");
}
}
还有一个只支持VGA接口的老旧显示器:
public class OldMonitor {
private VGAInterface vgaInterface;
public OldMonitor(VGAInterface vgaInterface) {
this.vgaInterface = vgaInterface;
}
public void show() {
Console.log("准备好接收VGA信号...");
vgaInterface.showWithVGA();
Console.log("接收完毕,展示中...");
}
}
3.2、对象适配器(推荐使用)
好了,只有一个HDMI数据线,一个只支持VGA的显示器,那还差一个转换器——适配器:
public class Adapter implements VGAInterface {
// 这里没有使用HDMIDataLine作为成员变量,主要是体现依赖倒置原则
private HDMIInterface hdmiInterface = new HDMIDataLine();
@Override
public void showWithVGA() {
hdmiInterface.showWithHDMI();
}
}
3.3、类适配器
public class Adapter extends HDMIDataLine implements VGAInterface {
@Override
public void showWithVGA() {
super.showWithHDMI();
}
}
两种适配器公共客户端:
public class Client {
public static void main(String[] args) {
Adapter adapter = new Adapter();
OldMonitor oldMonitor = new OldMonitor(adapter);
oldMonitor.show();
}
}
准备好接收VGA信号...
我是HDMI高清数据线,是我在传输数据...
接收完毕,展示中...
以上就是两种适配器模式的代码实现,要说明几点:
- VGA接口不需要实现类,这也符合实际,VGA接口实现类就是VGA数据线,要是有VGA数据线还要啥适配器,直接连接使用多好
- 为啥推荐使用对象适配器,而不推荐类适配器?仔细对比两种方式的Adapter类,差别就是类适配器多了一层继承关系(继承了HDMIDataLine ),我们都知道Java是单继承的,所以不推荐
- 这里涉及到四个角色:目标抽象类(VGAInterface)、适配者类(HDMI数据线)、客户端(显示器)、适配器类(Adapter),后面分析优缺点会用到
四、适用场景
优点:
适配器模式(对象适配器模式),是一种组合优于集成的思想的实现。通过使用适配器模式,我们可以最大程度的复用已有的了类和代码。他主要有以下有点:
-
将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
-
增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
-
灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
缺点:
当然,适配器模式并不是完美的,过度使用还是会带来一些问题的。缺点如下:
-
过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
适用场景:
-
当需要修改一些正在运行着的代码,并且希望可以复用原有代码实现新的功能的时候(这种情况经常遇到)
-
系统需要使用一些现有的类,而这些类的接口不符合系统的需要,甚至没有这些类的源代码
-
创建一个可以重复使用的类,用于和一些彼此之间没有太大关联的类,包括一些可能在将来引进的类一起工作
无论哪种适配器,都是为了保留现有类所提供的服务,向客户提供接口,以满足客户的期望。即在不改变原有系统的基础上,提供新的接口服务。
在选用适配器模式的时候根据需要来选用合适的实现方式,尽量使用对象的适配器模式,多用动态组合合、少用继承。
好了,就到这!