什么是适配器模式
概念
适配器模式(Adapter Pattern),是一种使用中间层(适配器)来关联新接口与旧接口的设计模式,可以在不改变现有代码的基础上,对现有代码进行拓展,以满足新接口的需要。
例子
其实,在我们的日常生活中有很多关于适配器的例子,最常见的就是手机充电器。家庭用电一般是交流电,且电压比较高(大于100伏),而手机等小型电子设备一般用的是直流电,且电压比较低(几伏或者十几伏),如果直接使用家庭用电给手机充电,极有可能对手机造成损坏,所以手机一般都会配有一个电源插头和一条USB线,这个电源插头就是电源适配器,也就是我们常说的充电器,电源适配器中包含变压器和整流器,其中变压器可以改变输入电流的电压大小,整流器可以把交流电变为直流电,高压交流电经过电源适配器处理之后就变成了低压直流电,电源适配器把处理后的电流通过USB线输送至手机端,手机就能正常充电了。
在手机电源适配器的例子中,我们的需求是给手机充电,但是目前的情况是家庭用电为高压交流电,为了解决电流不匹配的问题,我们找来了电源适配器,电源适配器通过变压、整流操作将高压交流电变为了低压直流电,从而实现了给手机充电的目标。对应到软件设计模式中来,其实是类似的,适配器模式通过创建适配器,对现有代码进行了拓展,使得拓展后的代码能够达到目标需求的需要,解决了日益增长的需求和现有情况不能满足需求之间的矛盾。
根据例子,我们可以发现,适配器模式中包含了3个元素,即:
- 需求(目标接口);
- 现状(现状不能满足需求,需要被适配);
- 适配器(对现状进行拓展,以满足需求)。
接下来,我们将围绕这3个元素具体讲解适配器模式。
适配器模式分类
根据实现方式的不同,适配器模式可分为两种不同的类型:
- 类适配器模式(通过继承的方式实现);
- 对象适配器模式(通过委托的方式实现)。
接下来,我们具体讲解一下这两种不同的适配器模式。
在讲解之前,我们需要明确在适配器模式中有哪些接口和类,在上面手机电源适配器的例子中,我们总结出了适配器模式包含3个元素,即:需求、现状、适配器,那么我们可以将这3个元素提取出来作为适配器模式中的接口或类。
- 需求可以看作还未实现的目标,是抽象的,因此我们用接口来描述需求,接口名定为
ITarget
。 - 现状可以认为是已实现的功能,是具象的,因此我们用类来描述现状,同时,现状要想满足需求,还需要被适配,所以我们类名定为
Adaptee
(适配是Adapt,ee可表示动作的接受者,因此Adaptee可表示被适配者)。 - 适配器会对现状进行适配,也是一个具体的事物,所以我们也用类来表示适配器,类名定为
Adapter
。
类适配器模式
类适配器模式是指适配器(Adapter
)继承现状(Adaptee
),并同时实现需求(ITarget
),从而将需求的具体实现转移到现状(Adaptee
)上来。在类适配器模式中,ITarget
、Adaptee
、Adapter
各自的代码及相互之间的关系如下:
ITarget
:
/**
* 目标接口,表示需要实现的接口
*/
interface ITarget {
/**
* 目标接口方法
*/
fun targetMethod()
}
Adaptee
:
/**
* 被适配者,即需要被适配的类
*/
open class Adaptee {
fun method() {
println("method invoked")
}
}
Adapter
:
/**
* 适配器
*
* 通过继承[Adaptee]并实现[ITarget],使[Adapter]同时拥有[Adaptee]和[ITarget]的能力,
* 从而将[ITarget]的具体实现转移到[Adaptee]类。
*/
class Adapter : Adaptee(), ITarget {
override fun targetMethod() {
// 访问父类[Adaptee]的method()方法
// 将[ITarget]接口方法的具体实现转移到[Adaptee]类
method()
}
}
在类适配器模式中,Adaptee
是抽象类,被Adapter
继承,这样在Adapter
中便能够直接访问Adaptee
暴露出来的方法,在实现ITarget
接口方法时,可以通过调用Adaptee
的方法来实现具体逻辑。
类适配器模式UML类图如下:
对象适配器模式
对象适配器模式是指适配器(Adapter
)持有现状(Adaptee
)的对象,并同时实现需求(ITarget
),从而将需求的具体实现委托给(Adaptee
)对象。在对象适配器模式中,ITarget
、Adaptee
、Adapter
各自的代码及相互之间的关系如下:
ITarget
:
/**
* 目标接口,表示需要实现的接口
*/
interface ITarget {
/**
* 目标接口方法
*/
fun targetMethod()
}
Adaptee
:
/**
* 被适配者,即需要被适配的类
*/
class Adaptee {
fun method() {
println("method invoked")
}
}
Adapter
:
/**
* 适配器
*
* 通过持有[Adaptee]对象并实现[ITarget],使[Adapter]拥有[ITarget]的能力,并能够访问[Adaptee]对象公开出来的方法,
* 从而将[ITarget]的具体实现委托给[Adaptee]对象。
*/
class Adapter : ITarget {
private val adaptee: Adaptee by lazy { Adaptee() }
override fun targetMethod() {
// 调用[Adaptee]对象的method()方法
// 将[ITarget]接口方法的具体实现委托给[Adaptee]对象
adaptee.method()
}
}
在对象适配器模式中,Adaptee
不是抽象类,Adapter
持有Adaptee
对象,这样在Adapter
中便能够通过Adaptee
对象访问Adaptee
暴露出来的方法,在实现ITarget
接口方法时,可以通过调用Adaptee
对象的方法来实现具体逻辑。
对象适配器模式UML类图如下:
示例
接下来,我们通过“水力发电”的例子来演示两种适配器模式,便于我们更好的理解。
我们都知道,水不能直接变为电,如果要想通过水产生电,必须通过能量转换装置将水能变为电能,水力发电设备就是这样一种装置,水力发电设备利用水位落差及水的流动产生的势能来驱动水轮转动,水轮转动产生的机械能再驱动发电机发电。
水力发电的整个过程,也可以看成适配器模式,在这个过程中,需求是“发电机发电”,现状是“只有水和水带来的水能”,水能没法直接变为电能,所以我们必须通过水力发电装置进行能量转换,水力发电装置就是适配器。
我们先来创建示例用到的最基本的角色-“水能”、“电能”、“能量”,水能”、“电能”都蕴含“能量”,所以我们可以定义以下3个类:
Energy
:
/**
* 能量
*
* @param id 能量唯一标识
*/
data class Energy(val id: String)
HydroEnergy
:
/**
* 水能
*
* @param energy 能量
*/
data class HydroEnergy(val energy: Energy)
ElectricEnergy
:
/**
* 电能
*
* @param energy 能量
*/
data class ElectricEnergy(private val energy: Energy)
接下来,我们分别用类适配器模式和对象适配器模式来编写示例代码。
类适配器模式
在类适配器模式中,我们把需求抽象为IElectricEnergyDevice
接口。IElectricEnergyDevice
:
/**
* 电能设备接口
*/
interface IElectricEnergyDevice {
/**
* 输出电能
* @return 电能
*/
fun outputElectricEnergy(): ElectricEnergy
}
把现状抽象为HydroEnergyDevice
类。HydroEnergyDevice
:
/**
* 水能设备
*/
open class HydroEnergyDevice {
/**
* 提供水能
*/
fun inputHydroEnergy(): HydroEnergy {
return HydroEnergy(Energy(hashCode().toString()))
}
}
适配器则用HydropowerAdapter
类来表示。HydropowerAdapter
:
/**
* 水力发电适配器,即水力发电装置,将水能转化为电能
*/
class HydropowerAdapter : HydroEnergyDevice(), IElectricEnergyDevice {
override fun outputElectricEnergy(): ElectricEnergy {
val hydroEnergy = inputHydroEnergy()
println("${LABEL}提供水能($hydroEnergy)")
val electricEnergy = electricEnergyToHydroEnergy(hydroEnergy)
println("${LABEL}把水能($hydroEnergy)转换成为电能($electricEnergy)")
println("${LABEL}输出电能($electricEnergy)")
return electricEnergy
}
/**
* 将水能转化为电能
*/
private fun electricEnergyToHydroEnergy(hydroEnergy: HydroEnergy): ElectricEnergy {
return ElectricEnergy(hydroEnergy.energy)
}
private companion object {
private const val LABEL = "【水力发电适配器】"
}
}
类适配器模式示例逻辑:
- 电能设备
IElectricEnergyDevice
接口需要通过outputElectricEnergy()
方法输出电能; - 水能设备
HydroEnergyDevice
只能通过inputHydroEnergy()
方法提供水能; - 于是水力发电适配器
HydropowerAdapter
继承了水能设备HydroEnergyDevice
,同时实现了电能设备IElectricEnergyDevice
接口,水力发电适配器HydropowerAdapter
具备了提供水能和输出电能两种能力; - 水力发电适配器
HydropowerAdapter
内部实现了将水能转化为电能的方法electricEnergyToHydroEnergy()
- 水力发电适配器
HydropowerAdapter
在实现输出电能的方法outputElectricEnergy()
时,先调用inputHydroEnergy()
方法获取水能,再调用electricEnergyToHydroEnergy()
方法将水能转化为电能,最后输出了电能。
接下来,我们开始构建类适配器模式场景模拟器ClassAdapterPatternSceneSimulator
:
/**
* 类适配器模式场景模拟器
*/
class ClassAdapterPatternSceneSimulator : ISceneSimulator {
override fun run() {
println("============类适配器模式示例============")
val electricEnergyDevice: IElectricEnergyDevice = HydropowerAdapter()
electricEnergyDevice.outputElectricEnergy()
println("============类适配器模式示例============")
}
companion object {
/**
* 运行场景
*/
fun run() {
ClassAdapterPatternSceneSimulator().run()
}
}
}
在ClassAdapterPatternSceneSimulator
中,使用适配器HydropowerAdapter
实例化IElectricEnergyDevice
接口对象electricEnergyDevice
,通过调用electricEnergyDevice
的outputElectricEnergy
方法输出电能。
最后,我们通过测试代码运行一下类适配器模式场景模拟器,测试代码:
/**
* 类适配器模式测试类
*/
class Main {
/**
* 演示类适配器模式
*/
@Test
fun main() {
ClassAdapterPatternSceneSimulator.run()
}
}
运行之后,控制台输出如下日志:
============类适配器模式示例============
【水力发电适配器】提供水能(HydroEnergy(energy=Energy(id=1175185734)))
【水力发电适配器】把水能(HydroEnergy(energy=Energy(id=1175185734)))转换成为电能(ElectricEnergy(energy=Energy(id=1175185734)))
【水力发电适配器】输出电能(ElectricEnergy(energy=Energy(id=1175185734)))
============类适配器模式示例============
可以看到,类适配器模式实现了把水能转化为电能的需求。
对象适配器模式
在对象适配器模式中,我们仍然把需求抽象为IElectricEnergyDevice
接口。IElectricEnergyDevice
:
/**
* 电能设备接口
*/
interface IElectricEnergyDevice {
/**
* 输出电能
* @return 电能
*/
fun outputElectricEnergy(): ElectricEnergy
}
现状仍然用HydroEnergyDevice
类来表示,但不再是抽象类,因为此时现状不需要被适配器继承了。HydroEnergyDevice
:
/**
* 水能设备
*/
class HydroEnergyDevice {
/**
* 提供水能
*/
fun inputHydroEnergy(): HydroEnergy {
return HydroEnergy(Energy(hashCode().toString()))
}
companion object {
const val LABEL = "【水能设备】"
}
}
适配器仍然用HydropowerAdapter
类来表示,HydropowerAdapter
类实现了IElectricEnergyDevice
接口,但不再继承HydroEnergyDevice
,而是在内部持有HydroEnergyDevice
对象。HydropowerAdapter
:
/**
* 水力发电适配器,即水力发电装置,将水能转化为电能
*/
class HydropowerAdapter : IElectricEnergyDevice {
private val hydroEnergyDevice: HydroEnergyDevice by lazy {
println("${LABEL}持有${HydroEnergyDevice.LABEL}")
HydroEnergyDevice()
}
override fun outputElectricEnergy(): ElectricEnergy {
val hydroEnergy = hydroEnergyDevice.inputHydroEnergy()
println("${HydroEnergyDevice.LABEL}提供水能($hydroEnergy)")
val electricEnergy = electricEnergyToHydroEnergy(hydroEnergy)
println("${LABEL}把水能($hydroEnergy)转换成为电能($electricEnergy)")
println("${LABEL}输出电能($electricEnergy)")
return electricEnergy
}
/**
* 将水能转化为电能
*/
private fun electricEnergyToHydroEnergy(hydroEnergy: HydroEnergy): ElectricEnergy {
return ElectricEnergy(hydroEnergy.energy)
}
private companion object {
private const val LABEL = "【水力发电适配器】"
}
}
对象适配器模式示例逻辑:
- 电能设备
IElectricEnergyDevice
接口需要通过outputElectricEnergy()
方法输出电能; - 水能设备
HydroEnergyDevice
只能通过inputHydroEnergy()
方法提供水能; - 于是水力发电适配器
HydropowerAdapter
实现了电能设备IElectricEnergyDevice
接口,同时持有了水能设备HydroEnergyDevice
对象,水力发电适配器HydropowerAdapter
具备了提供水能和输出电能两种能力; - 水力发电适配器
HydropowerAdapter
内部实现了将水能转化为电能的方法electricEnergyToHydroEnergy()
- 水力发电适配器
HydropowerAdapter
在实现输出电能的方法outputElectricEnergy()
时,先调用HydroEnergyDevice
对象的inputHydroEnergy()
方法获取水能,再调用electricEnergyToHydroEnergy()
方法将水能转化为电能,最后输出了电能。
接下来,我们开始构建对象适配器模式场景模拟器ObjectAdapterPatternSceneSimulator
:
/**
* 对象适配器模式场景模拟器
*/
class ObjectAdapterPatternSceneSimulator : ISceneSimulator {
override fun run() {
println("============对象适配器模式示例============")
val electricEnergyDevice: IElectricEnergyDevice = HydropowerAdapter()
electricEnergyDevice.outputElectricEnergy()
println("============对象适配器模式示例============")
}
companion object {
/**
* 运行场景
*/
fun run() {
ObjectAdapterPatternSceneSimulator().run()
}
}
}
在ObjectAdapterPatternSceneSimulator
中,使用适配器HydropowerAdapter
实例化IElectricEnergyDevice
接口对象electricEnergyDevice
,通过调用electricEnergyDevice
的outputElectricEnergy
方法输出电能。
最后,我们通过测试代码运行一下对象适配器模式场景模拟器,测试代码:
/**
* 对象适配器模式测试类
*/
class Main {
/**
* 演示对象适配器模式
*/
@Test
fun main() {
ObjectAdapterPatternSceneSimulator.run()
}
}
运行之后,控制台输出如下日志:
============对象适配器模式示例============
【水力发电适配器】持有【水能设备】
【水能设备】提供水能(HydroEnergy(energy=Energy(id=165245056)))
【水力发电适配器】把水能(HydroEnergy(energy=Energy(id=165245056)))转换成为电能(ElectricEnergy(energy=Energy(id=165245056)))
【水力发电适配器】输出电能(ElectricEnergy(energy=Energy(id=165245056)))
============对象适配器模式示例============
可以看到,通过对象适配器模式也实现了把水能转化为电能的需求。
总结
在软件迭代过程中,我们会接到各种各样层出不穷的新需求,当新需求和现有功能之间存在关联关系,但现有代码又无法直接用于新需求时,我们可以选择对现有代码进行调整,或者想办法对现有代码进行拓展,如果是选择直接对现有代码进行调整,那么很可能需要对改动的地方及相关联的部分进行大规模回归测试,影响面较大,为了降低测试成本,我们可以选择对现有代码进行拓展,此时适配器模式就派上用场了——不改现有代码,需求轻松拿捏!