模式介绍
适配器模式(Adapter Pattern)属于结构型模式,将某个类的接口转换成客户端期望的另一个接口表示,主要目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作,别名为包装器(Wrapper);
工作原理
- 适配器模式:将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容;
- 从用户的角度看不到被适配者,是解耦的;
- 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法;
- 用户收到反馈结果,感觉只是和目标接口交互。
类适配器模式
Adapter类,通过继承 src 类,实现 dst 类接口,完成 src -> dst 的适配。
模式图解
Adapter 类并没有 sampleOperation2() 方法,而客户端则期待这个方法。为了使客户端能够使用 Adaptee 类,提供一个中间环节,即类 Adapter,把 Adaptee 的 API 与 Target 类的 API 衔接起来。Adapter 与 Adaptee 是继承关系,这决定了这个适配器模式是类的。
模式所涉及的角色有:
- 目标(Target)角色:这就是所期待得到的接口。注意,由于这里讨论的是类适配器模式,因此目标不可以是类。
- 源(Adaptee)角色:现有需要适配的接口。
- 适配器(Adapter)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。
示例代码
类适配器模式--目标接口
public interface IVoltage5V {
int output5V();
}
类适配器模式--源类
public class Voltage220V {
public int output220V() {
int src = 220;
System.out.println("电压:" + src + "V");
return src;
}
}
类适配器模式--适配器
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
int srcv = super.output220V();
int dstv = srcv / 44;
System.out.println("电压:" + dstv + "V");
return dstv;
}
}
适配器模式--使用对象
public class Phone {
public void charging(IVoltage5V voltage5V) {
if (voltage5V.output5V() == 5) {
System.out.println("电压为5V,可以正常充电");
} else {
System.out.println("电压不为5V,没法充电");
}
}
}
适配器模式-测试类
public class Test {
public static void main(String[] args) {
System.out.println("类适配器--------");
new Phone().charging(new VoltageAdapter());
}
}
注意事项
- Java 是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点,因为这要求 dst 必须是接口,有一定局限性;
- src 类的方法在 Adapter 中都会暴露出来,增加了使用成本;
- 由于其继承了 src 类,所以它可以根据需求重写 src 类的方法,使得 Adapter 的灵活性增强。
对象适配器模式
基本思路和类的适配器模式相同,只是将 Adapter 类作修改,不是继承 src 类,而是持有 src 类的实例,以解决兼容性的问题。即:持有 src 类,实现 dst 类接口,完成 src -> dst 的适配;
根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。
模式图解
从上图中可以看出,Adaptee 类并没有 sampleOperation2() 方法,而客户端则期待这个方法。为使客户端能够使用 Adaptee 类,需要提供一个包装(Wrapper)类 Adapter。这个包装类包装了一个 Adaptee 的实例,从而此包装类能够把 Adaptee 的 API 与 Target 类的 API 衔接起来。Adapter 与 Adaptee 是委派关系,这决定了这个适配器模式是对象。
从上图中可以看出,模式所涉及的角色有:
- 目标(Target)角色:这就是所期待的接口,目标可以是具体的或抽象的类。
- 源(Adaptee)角色:现有需要适配的接口。
- 适配器(Adapter)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口,显然,这一角色必须是具体类。
示例代码
类适配器模式--目标接口
public interface IVoltage5V {
int output5V();
}
类适配器模式--源
public class Voltage220V {
public int output220V() {
int src = 220;
System.out.println("电压:" + src + "V");
return src;
}
}
类适配器模式--适配器角色
public class VoltageAdapter implements IVoltage5V {
private Voltage220V voltage220V;
public void setVoltage220V(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}
@Override
public int output5V() {
int dstv = 0;
if (Objects.nonNull(voltage220V)) {
int srcv = voltage220V.output220V();
dstv = srcv / 44;
System.out.println("电压:" + dstv + "V");
}
return dstv;
}
}
适配器模式--使用对象
public class Phone {
public void charging(IVoltage5V voltage5V) {
if (voltage5V.output5V() == 5) {
System.out.println("电压为5V,可以正常充电");
} else {
System.out.println("电压不为5V,没法充电");
}
}
}
适配器模式-测试类
public class Test {
public static void main(String[] args) {
System.out.println("对象适配器--------");
Phone phone = new Phone();
Voltage220V voltage220V = new Voltage220V();
VoltageAdapter voltageAdapter = new VoltageAdapter();
voltageAdapter.setVoltage220V(voltage220V);
phone.charging(voltageAdapter);
}
}
接口适配器模式
当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求;
适用于一个接口不想使用其所有的方法的情况。
示例代码
适配器模式-接口
public interface IAdapter {
void method1();
void method2();
void method3();
}
适配器模式-缺省实现类
public class AbsAdapter implements IAdapter {
@Override
public void method1() {
// 默认实现
}
@Override
public void method2() {
// 默认实现
}
@Override
public void method3() {
// 默认实现
}
}
使用场景
- 系统需要使用现有的类,而此类的接口不符合系统的需要。
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口。
- (对对象的适配器模式而言)在设计里,需要改变多个已有的子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器类,而这个不太实际。
与其它模式的关联
与桥梁模式的关系
对于客户端来说,一个适配器类将一个 Adaptee 类的接口改成 Target 的接口。桥梁模式的简略类图如下图所示。
在桥梁模式里面,Abstraction 对象定义了 Implementation 对象的接口,目的是要隐藏在 Abstraction 对象后面的实现细节。Abstraction 对象看上去也像是把客户端的接口与 Implementation 的接口相连接。但是两者有明显的本质上的不同,并且这种不同反映在用意上和应用上。
桥梁模式的用意是要把实现和它的接口分开,以便它们可以独立地变化。桥梁模式并不是用来把一个已有的对象接到不相匹配的接口上的。当一个客户端只知道一个特定的接口,但是又必须与具有不同接口的类打交道时,就应当使用适配器模式。
与装饰模式的关系
一个装饰类也是位于客户端和另外一个 Component 对象之间的,在它接到客户端的调用后把调用传给一个或几个 Component 对象。一个纯粹的装饰类必须与 Component 对象在接口上的完全相同,并增强后者的功能。与适配器类不同的是,装饰类不能改变它所装饰的 Component 对象的接口。装饰模式的简略类图如下图所示: