定义
适配器设计模式,顾名思义就是将适配器的作用总结抽象成为一种代码的组织方式,将现有的代码通过适配器进行适配,以满足项目对另外一个类或者接口的要求。换句话说就是将一个类的接口适配(包装/转换)成客户(调用者)希望的另一个接口。适配器设计模式有以下两种形式:
- 类适配器模式(使用继承的适配器)
- 对象适配器模式(使用委托的适配器)
问题引入
我们常用的笔记本电脑的配件中就有一个适配器,负责将220V
交流电转换成为12V
的直流电给笔记本电脑供电,它存在的作用就是将220V
交流电转换成为12V
的直流电。所以它就是适配器,220V
交流电就是被适配的对象,而12V
直流电就是转换后的目标对象,笔记本电脑就是这个目标对象的调用者。
适配器设计模式在JDK源码中的应用
学习适配器设计模式,当然也需要从JDK
中去寻找它的踪迹,在JDK
源码中,采用适配器设计模式的地方很多,比如最常见的IO
转换流和集合等。接下来我们一起从源码中来分析适配器设计模式是如何使用起来的。
我们一起阅读一下java.io.InputStreamReader(InputStream)
的部分源码:
InputStreamReader
的作用是将字节流转换为字符流,是它们之间转换的桥梁(适配器),也就是说,InputStreamReader
就是适配器,负责将InputStream
转换为Reader
,这样就可以使用Reader
的方法来执行各项操作。
package java.io;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import sun.nio.cs.StreamDecoder;
public class InputStreamReader extends Reader {
private final StreamDecoder sd;
public InputStreamReader(InputStream in) {
super(in);
try {
sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
} catch (UnsupportedEncodingException e) {
throw new Error(e);
}
}
public String getEncoding() {
return sd.getEncoding();
}
public int read() throws IOException {
return sd.read();
}
public int read(char cbuf[], int offset, int length) throws IOException {
return sd.read(cbuf, offset, length);
}
public boolean ready() throws IOException {
return sd.ready();
}
public void close() throws IOException {
sd.close();
}
}
上面的代码是经过删减后的部分代码,删除了源码中的注释以及几个构造方法,从阅读源码来看,这个适配器设计模式的形式采用的是“对象适配器模式
”,至于何是“对象适配器模式
”,我们将在后面的学习中介绍,读者可以暂时不必理会何是“对象适配器模式
”。
手动实现适配器设计模式
接下来,我们将手动实现两种适配器设计模式,用简单的代码来说明适配器设计模式是如何运转起来的。
以前小时候都玩过QQ
游戏,有时候需要充值Q
币,使用Q
币在游戏中购买道具,这种情景就可以完全适用适配器设计模式,那么在这种情景中,使用Q
币来购买游戏道具是我们需求,也就是我们的目标(Target
),而现在的现状是我们有人民币,那么人民币就是被适配的对象(Adaptee
),由于人民币不能直接在游戏中购买道具,它需要被转换成Q
币才可以进行交易,所以我们还需要一个适配器(Adapter
),负责将人民币转换成Q
币。
根据以上的文字,我们可以将其总结成为一个表格,可以很方便地理清关系:
角色 | 扮演者 | 作用 |
---|---|---|
Target | 购买游戏道具的接口 | 有充值Q币接口,有使用Q币充值游戏道具的接口 |
Adapter | Q币充值器 | 将人民币转换成为Q币,并完成充值 |
Adaptee | 人民币 | 被适配,被转换的对象 |
根据以上的关系,我们分别来创建各个角色对应的类或者接口。这里我们模拟了使用人民币充值Q
币,使用Q
币购买游戏道具的案例,假设一个单位的人民币可以充值10
个Q
币,每个Q
币可以一个游戏道具。
示例代码1:类适配器设计模式(使用继承的适配器)
- Target
我们的目标是有一个接口,这个接口可以购买游戏道具,但是需要使用Q
币来进行购买。在适配器设计模式里,它是我们需要最终的目标。
package cn.itlemon.design.pattern.chapter02.adapter.example3;
/**
* @author jiangpingping
* @date 2018/9/6 下午7:43
*/
public interface TargetInterface {
/**
* 购买qCoinCount个游戏道具
*/
void buyGameProps();
}
- Adaptee
现在的现状是手头上有人民币,所以需要有一个Q
币充值器,将人民币换成相同价值的Q
币。在适配器设计模式里面,人民币就是需要被适配的对象,因为它不能直接用来购买游戏道具,但是得必须通过它才可以充值Q
币,才能满足要求。
package cn.itlemon.design.pattern.chapter02.adapter.example3;
/**
* 人民币
*
* @author jiangpingping
* @date 2018/9/6 下午7:45
*/
public class Rmb {
private int count;
public Rmb(int count) {
this.count = count;
}
public int getCount() {
return this.count;
}
}
- Adapter
所以我们需要一个Q
币充值器,将人民币换成Q
币,然后还具备购买游戏道具的功能。在适配器设计模式中,Q
币充值器就是我们所需的适配器。
package cn.itlemon.design.pattern.chapter02.adapter.example3;
/**
* Q币充值器
*
* @author jiangpingping
* @date 2018/9/6 下午7:31
*/
public class QCoinRechargeableDevice extends Rmb implements TargetInterface {
private int qCoinCount;
public QCoinRechargeableDevice(int rmbCount) {
super(rmbCount);
this.qCoinCount = getCount() * 10;
}
@Override
public void buyGameProps() {
System.out.println("一共购买了" qCoinCount "个道具");
}
}
- Main
这里写一个Main
方法,来验证我们上面设计的适配器设计模式,主要代码如下:
package cn.itlemon.design.pattern.chapter02.adapter.example3;
/**
* @author jiangpingping
* @date 2018/9/6 下午9:26
*/
public class Main {
public static void main(String[] args) {
TargetInterface qCoinRechargeableDevice = new QCoinRechargeableDevice(10);
qCoinRechargeableDevice.buyGameProps();
}
}
这里,我们向Q
币充值器充值10
个单位的人民币,就可以完成购买100
个游戏道具转变。本来人民币不能直接用来购买游戏道具,使用适配器设计模式之后,就可以完成我们购买游戏道具的需求。
使用继承的适配器UML类图
使用继承的适配器有一个特点就是Adapter
继承了Adaptee
,并实现了Target
,这就是三者之间的关系。
示例代码2:对象适配器设计模式(使用委托的适配器)
这里仅仅是贴出代码,对于各个类的说明,在上面都已经进行了阐述。
- Target
package cn.itlemon.design.pattern.chapter02.adapter.example4;
/**
* @author jiangpingping
* @date 2018/9/10 下午9:16
*/
public abstract class TargetInterface {
/**
* 购买qCoinCount个游戏道具
*/
public abstract void buyGameProps();
}
- Adaptee
package cn.itlemon.design.pattern.chapter02.adapter.example4;
/**
* 人民币
*
* @author jiangpingping
* @date 2018/9/6 下午7:45
*/
public class Rmb {
private int count;
public Rmb(int count) {
this.count = count;
}
public int getCount() {
return this.count;
}
}
- Adapter
package cn.itlemon.design.pattern.chapter02.adapter.example4;
/**
* @author jiangpingping
* @date 2018/9/10 下午9:18
*/
public class QCoinRechargeableDevice extends TargetInterface {
private Rmb rmb;
public QCoinRechargeableDevice(Rmb rmb) {
this.rmb = rmb;
}
@Override
public void buyGameProps() {
System.out.println("一共购买了" rmb.getCount() * 10 "个道具");
}
}
- Main
package cn.itlemon.design.pattern.chapter02.adapter.example4;
/**
* @author jiangpingping
* @date 2018/9/10 下午9:16
*/
public class Main {
public static void main(String[] args) {
TargetInterface qCoinRechargeableDevice = new QCoinRechargeableDevice(new Rmb(10));
qCoinRechargeableDevice.buyGameProps();
}
}
使用委托(对象)的适配器UML类图
使用委托的适配器有一个特点就是Adapter
拥有了Adaptee
,并继承了Target
抽象类,这就是三者之间的关系。
浅析适配器模式中的重要角色
适配器设计模式也是一个比较常用的设计模式之一,现对适配器设计模式中的角色进行浅析。
- Target(对象)
该角色负责定义最终的需求,也就是使用适配器模式之后的最终效果。在本次示例中,
TargetInterface
就是扮演了这个Target
角色。 - Adaptee(被适配)
该角色定义的是原始的功能,它也许无法直接被利用,但是又不能随意更改,所以它就需要被适配,使得在不修改原始代码的情况下能激活
Target
的功能。在本次示例中,Rmb
扮演了这个角色。 - Adapter(适配)
该角色是适配器设计模式的核心角色,他负责适配
Adaptee
和Target
,使得Adaptee
来满足Target
的需求。在本次示例中,QCoinRechargeableDevice
扮演了这个角色。 - Client(请求者)
该角色负责调用
Target
的方法来进行一系列的逻辑处理。在本次示例中,Main
类扮演了这个角色。
适配器设计模式UML类图
分析完适配器设计模式的重要角色,当然也得理清适配器设计模式的UML
类图。
- 使用继承的适配器设计模式类图
- 使用委托的适配器设计模式类图
为什么要使用适配器设计模式
我们往往有这种思想,要使用什么类的方法,直接使用不就OK
了,或者稍微修改一下已有的代码不就可以使用了吗?其实这种思想是不正确的,因为在现有类的基础下,很多类的方法都经过了严格的测试,贸然地去修改他容易造成意外情况的发生,我们使用适配器设计模式,往往无需修改现有的代码,直接在现有的代码的基础上创建新的代码,这样即使出了错误,我们也能很快从我们新写的代码中找出端倪。使用适配器设计模式,也是对现有代码的一种重复利用。