1. 首先对适配器模式做下简单介绍:
适配器模式,从名字上大概可以了解是做适配处理的。是做什么样的适配呢?就是将一个类的接口转换成想要的接口,以便实现不同的接口。这听不起来感觉不太容易实现,但有了设计模式,实现起来还是挺容易的。首先回忆一下装饰者模式(它与适配器模式有点类似,但意图是不相同的):我们将对象包装起来,赋予它们新的职责。而现在则是以不同目的,包装某些对象:让他们的接口看起来不像自己而是别的东西。
以上的描述听起来还是有点抽象了,举个现实中的例子,比如说:你买了一个港版的苹果手机,回来充电的时候可能还需要购买一个英标转换头(这算是一个交流电适配器)。那么它的作用就很容易理解了:它位于英式插头和中式插座的中间,它的工作就是将中式插座转换成英式插座,好让英式插头可以插进这个插座能够正常给手机充电。
好了,这是真实世界的适配器,那面向对象适配器又是什么?其实,OO适配器和真实世界的适配器扮演者同样的角色:将一个接口转换成另一个接口,以符合某种业务场景。
2. 接着,我们使用代码实现一下以上例子(有两种实现方式:对象适配器和类适配器模式),我们先通过对象适配器方式(常用)来实现:
- 定义英标插座接口
/**
* @className: BritishSocket
* @summary: 英标插座
* @Description: TODO
* @author: helon
* date: 2018/11/24 11:36 PM
* version: v1.0
*/
public interface BritishSocket {
/**
* @Summary 英标插座
* @Description 港版iPhone充电插头是英标的,需要英标插座才能满足充电
* @Author helon
* @Date 2018/11/24 11:37 PM
* @Param []
* @return void
**/
void britishSocketSupport();
}
- 定义一个港版的iPhoneX类,只能使用英标插座充电
/**
* @className: IPhoneX
* @summary: 港版iPhoneX
* @Description: TODO
* @author: helon
* date: 2018/11/24 11:40 PM
* version: v1.0
*/
public class IPhoneX {
/**
* @Summary iPhone手机充电
* @Description 必须使用英标插座充电
* @Author helon
* @Date 2018/11/24 11:43 PM
* @Param [britishSocket]
* @return void
**/
public void charging(BritishSocket britishSocket) {
britishSocket.britishSocketSupport();
System.out.println("正在使用英标插座充电...");
}
}
- 定义我们日常使用的插座类(中国标准插座):
/**
* @Summary 中国标准插座
* @Description 提供充电方法
* @Author helon
* @Date 2018/11/24 11:27 PM
* @Param
* @return
**/
public class ChineseSocket {
/**
* @Summary 中国标准插座
* @Description
* @Author helon
* @Date 2018/11/24 11:30 PM
* @Param []
* @return void
**/
public void chineseSocketSupport() {
System.out.println("提供中国标准插座");
}
}
- 接下来就是关键的步骤,需要我们实现一个适配器,能够将我们日常的插座通过这个适配器转换成英标插座,来满足给手机充电
/**
* @className: ChineseSocketAdapter
* @summary: 插座适配器(对象适配器模式)
* @Description: 通过适配器来使中国标准插座转换为英标插座,来满足港版iPhone充电
* @author: helon
* date: 2018/11/24 11:44 PM
* version: v1.0
*/
public class ChineseSocketAdapter implements BritishSocket {
private ChineseSocket chineseSocket;
public ChineseSocketAdapter(ChineseSocket chineseSocket) {
this.chineseSocket = chineseSocket;
}
@Override
public void britishSocketSupport() {
chineseSocket.chineseSocketSupport();
System.out.println("通过中国标准插座来转换适配成英标插座");
}
}
- 最后,我们测试运行看一下结果:
/**
* @className: TestMain
* @summary: 测试适配器模式
* @Description: TODO
* @author: helon
* date: 2018/11/24 11:56 PM
* version: v1.0
*/
public class TestMain {
public static void main(String[] args) {
System.out.println("====对象适配器====");
IPhoneX iPhoneX = new IPhoneX();
//声明一个适配器,该适配器提供了英标插座,中国标准的插头(能够接入中国标准插座)
ChineseSocketAdapter chineseSocketAdapter = new ChineseSocketAdapter(new ChineseSocket());
//开始充电
iPhoneX.charging(chineseSocketAdapter);
}
}
//执行结果
====对象适配器====
提供中国标准插座
通过中国标准插座来转换适配成英标插座
正在使用英标插座充电...
-
以上就是具体实现的代码,我们再看一下关系类图:
-
使用类适配器模式实现案例,修改Adapter类:
/**
* @className: ChineseSocketClassAdapter
* @summary: 类适配器模式
* @Description: 通过实现英标接口,并继承ChineseSocket类的方式实现
* 优点:实现简单
* 缺点:由于Java单继承的缘故,目标类必须是接口(BritishSocket),以便于适配器去继承源类(ChineseSocket)
* 这样就导致了适配器里暴露了源类的方法,使用起来的成本就增加了
* @author: helon
* date: 2018/11/25 5:08 PM
* version: v1.0
*/
public class ChineseSocketClassAdapter extends ChineseSocket implements BritishSocket{
@Override
public void britishSocketSupport() {
chineseSocketSupport();
System.out.println("类适配器方式实现英标转换");
}
}
由于Java单继承的缘故,目标类必须是接口(BritishSocket),以便于适配器去继承源类(ChineseSocket),这样就导致了适配器里暴露了源类的方法,使用起来的成本就增加了,所以推荐使用对象适配器方式。
类对象适配器的类图如下:
3. JDK中的适配器模式使用场景:
其实在jdk的InputStreamReader和OutputStreamWriter都使用到了适配器模式,就拿InputStreamReader来说吧,首先它集成了Reader类,构造方法中需要传入InputStream类对象,这样做的目的就是需要将InputStream对象适配到Reader。其中,InputStreamReader就是适配器,Reader是目标对象,而InputStream是源对象。同理,OutputStreamWriter也是一个适配器类,它是将OutputStream适配到Writer。
4. 适配器模式和装饰者模式的差异:
说了这么多,貌似有好多地方都和装饰者模式很相似,容易混淆。那么它们两者具体的差异有哪些呢,或者说怎么才能不把它们弄混淆?其实,如果使用了装饰者模式,那么一定表示有新的行为或功能要加入到设计中,说白了装饰者模式就是对源组件进行功能增强,不会做适配或转换的意图。那么,适配器模式在不改变源代码的情况下,做到对目标功能的适配,一句话概括就是:将一个接口转换成另一个接口,以符合客户端使用的期望,但装饰者模式不会做转换的工作。其实,适配器模式有好多的实现方式,但只要是基于适配器模式核心思想,模式变得再多也会很容易理解。
5. 总结
在工作中,也会经常遇到需要使用适配器模式来解决的场景,比如:当要接入一个新的支付渠道的时候,前提是线上已经有一家正常运行的支付渠道。需求是将部分银行修改为新接入的这个厂商提供的接口,但发现新厂商的接口和原使用的接口有一些字段差异,那么我们需要考虑如何用最小的改动,最小的风险,来做这次升级改造。这个时候就可以考虑使用适配器模式,将新接口适配到我们原来的接口,将对外业务调用的接口保持不变,只需要在适配器中做下转换操作就可以了,这样外部业务接口可以做到零改动,降低bug的风险。
好了,以上这些就是我对适配器模式的理解和总结,如有什么疑义,欢迎指正,一起交流学习。