定义
适配器,其实很好理解,生活中也随处可见,比如电源适配器、usb 适配器等等,那么适配器模式,也被称为Wrapper 模式。
Wrapper 有“包装器”的意思,适配器模式的定义是:将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,解决的痛点便是因接口不兼容导致的类不能正常工作的问题。
如上图所示,A、B 代表已经塑模成型的物体 A 和 B,如果想将这两种物体安装在一起,因为两者的接口是不兼容的,不可能直接安装在一起,这个时候该怎么办?这里,我们可以引入物体 C,物体 C 既要适配 A 的接口,又要适配 B 的接口,经过 C 的无缝“衔接”,便将 A、B 完美结合在了一起。
这里的物体 C 就是我们要说的适配器角色,起到了一定的角色转换的作用。再举个例子,如果想让直流 12v 的笔记本电脑工作在交流 220v 的电源下,就必须要一个电源适配器,该适配器的作用就是将 220v 的 AC 交流转为 12v 的 DC 直流,这就是适配器该干的工作,弥补两者之间的空白——承上启下。
什么时候使用适配器模式,从上面的案例我们也可以看出一点端倪:
- 现有的类或接口不能满足需求,且一般无法直接修改现有类或接口。比方该类为三方提供,就无法修改,亦或者像A、B 这种已经塑模成型的物件,可能已大规模在使用中,所以不允许修改。
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。
组成角色
适配器模式,根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。
适配器模式的通用类图如下:
适配器模式包含的角色如下:
- 目标角色(Target):该角色定义把其它类转换为何种接口,也就是我们的期望接口,可以是一个抽象类或接口,也可以是具体类。以上文中笔记本电脑为例,即指让笔记本正常工作的直流 12v 电源;
- 适配器角色(Adapter):适配器可以调用另一个接口,作为一个转换器,对 Adaptee 和 Target 进行适配,适配器类是适配器模式的核心,通常都是一个具体的类。以上文中笔记本电脑为例,即指电源适配器;
- 源角色(被适配 Adaptee ):你想把谁转换成目标角色,这个“谁”就是源角色,它是已经存在的、运行良好的类或对象,经过适配器角色的包装,它会成为一个崭新、靓丽的角色。以上文中笔记本电脑为例,即指 220v 的 AC 电源;
- 请求者(Client):该角色负责使用 Target 定义的方法进行具体处理,以上文中笔记本电脑为例,即指使用 12v 电源驱动的笔记本电脑。
总的一句话,Adapter 就是一个在 Client 中使用 Target 定义的接口来使用 Adaptee 角色(调用 Adaptee 中的方法)的存在。
类适配器(使用继承)
如上图为使用类适配器实现的适配器模式,具体代码如下:
首先是 Target 接口,也就是我们要适配的目标接口:
public interface Target {
public abstract void targetMethod1();
public abstract void targetMethod2();
}
接下来是要被适配的“接口”,即 Adaptee:
public class Adaptee {
public void methodA() {
System.out.println("Adaptee methodA invoked.");
}
public void methodB() {
System.out.println("Adaptee methodB invoked.");
}
}
然后是我们的适配器,关键代码如下:
public class Adapter extends Adaptee implements Target{
@Override
public void targetMethod1() {
System.out.println("Adapter targetMethod1 inkoked.");
methodA();
}
@Override
public void targetMethod2() {
System.out.println("Adapter targetMethod2 inkoked.");
methodB();
}
}
最后是我们的 Client,在这里就是 Main 类:
public class Main {
public static void main(String[] args) {
// 通过Adapter继承Adaptee实现了Adaptee角色的调用
Target target = new Adapter();
target.targetMethod1();
target.targetMethod2();
}
}
执行结果如下:
Adapter targetMethod1 inkoked.
Adaptee methodA invoked.
Adapter targetMethod2 inkoked.
Adaptee methodB invoked.
这里,Client 使用者并不知道 Adaper 适配器是如何工作的,就好比笔记本电脑只需要在 12v 电压下正常工作即可,具体适配器如何适配实现电压转换,笔记本电脑无需关心。
对象适配器(使用委托)
如上图为使用对象适配器实现的适配器模式,具体代码如下:
首先是我们要适配的目标类,这里不是接口了注意:
public abstract class Target {
public abstract void targetMethod1();
public abstract void targetMethod2();
}
因为 java 的类不支持多继承,但是在单继承模式下我们可以使用委托来实现方法的调用,修改后的 Adapter 适配器代码如下:
public class Adapter extends Target{
private Adaptee adaptee;
public Adapter() {
this.adaptee = new Adaptee();
}
@Override
public void targetMethod1() {
System.out.println("Adapter targetMethod1 inkoked.");
adaptee.methodA();
}
@Override
public void targetMethod2() {
System.out.println("Adapter targetMethod2 inkoked.");
adaptee.methodB();
}
}
Client 调用的时候没有变化,还是如下:
public static void main(String[] args) {
// 通过Adapter使用委托,实现了Adaptee角色的调用
Target target = new Adapter();
target.targetMethod1();
target.targetMethod2();
}
输出结果:
Adapter targetMethod1 inkoked.
Adaptee methodA invoked.
Adapter targetMethod2 inkoked.
Adaptee methodB invoked.
优缺点
主要优点:
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构;
- 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用;
- 可以将两个互不相干的类关联在一起;
- 增强系统灵活性。
主要缺点:
- 类适配器对于 Java、C# 等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者。
应用场景
类适配器与对象适配器的使用场景一致,主要应用于如下场景:
- 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码,这时创建一个适配器就能间接去改造这个类中的方法;
- 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
总结
适配器模式,本身属于一种结构型模式,用于在两个对象或者系统之间建立适配链接,使得前后系统衔接更加平滑,适配器模式的实现主要有继承方式的类适配器,和委托方式的对象适配器。