设计模式02——Adapter模式
- 2020 年 4 月 3 日
- 筆記
定义
适配器设计模式,顾名思义就是将适配器的作用总结抽象成为一种代码的组织方式,将现有的代码通过适配器进行适配,以满足项目对另外一个类或者接口的要求。换句话说就是将一个类的接口适配(包装/转换)成客户(调用者)希望的另一个接口。适配器设计模式有以下两种形式:
- 类适配器模式(使用继承的适配器)
- 对象适配器模式(使用委托的适配器)
问题引入
我们常用的笔记本电脑的配件中就有一个适配器,负责将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
了,或者稍微修改一下已有的代码不就可以使用了吗?其实这种思想是不正确的,因为在现有类的基础下,很多类的方法都经过了严格的测试,贸然地去修改他容易造成意外情况的发生,我们使用适配器设计模式,往往无需修改现有的代码,直接在现有的代码的基础上创建新的代码,这样即使出了错误,我们也能很快从我们新写的代码中找出端倪。使用适配器设计模式,也是对现有代码的一种重复利用。