理解名词
适配器模式(Adapter Design Pattern)是一种结构型设计模式。
关于适配器,可以用Type-C扩展坞做例子,对于只支持Type-C接口的MAC来说,如果想使用USB接口的键盘,HDMI接口的视频输出线,网线接口,SD卡读卡槽,需要使用Type-C转USB,Type-C转HDMI,Type-C转RJ-45等等适配器。这里体现了适配器模式的一个作用,统一多个类的接口设计,做统一的好处就是使我们开发系统时不用考虑外部系统的差异性,留出接口让外部系统匹配我们即可。
再做个比方,旧计算机的视频输出口是VGA接口,但是现在的新显示器都只支持HDMI接口了,那旧计算机想使用新显示器,就需要VGA转HDMI适配器。这里引出了适配器模式的第二个作用,兼容老版本接口。
作用
- 封装有缺陷的接口设计。为了隔离设计上的缺陷,我们希望对外部系统提供的接口进行二次封装,抽象出更好的接口设计,这个时候就可以使用适配器模式了。
- 统一多个类的接口设计。某个功能的实现依赖多个外部系统(或者说类)。通过适配器模式,将它们的接口适配为统一的接口定义,然后我们就可以使用多态的特性来复用代码逻辑。
- 替换依赖的外部系统。当我们把项目中依赖的一个外部系统替换为另一个外部系统的时候,利用适配器模式,可以减少对代码的改动。
- 兼容老版本接口。在做版本升级的时候,对于一些要废弃的接口,我们不直接将其删除,而是暂时保留,并且标注为 deprecated,并将内部实现逻辑委托为新的接口实现。
- 适配不同格式的数据。比如,把从不同系统拉取的不同格式的数据,统一为相同的格式,以方便存储和使用。
适用场景
接口不兼容
代码示例
ITarget 表示要转化成的接口定义。Adaptee 是一组不兼容 ITarget 接口定义的接口,Adaptor 将 Adaptee 转化成一组符合 ITarget 接口定义的接口。
第一种,基于继承的类适配器
public interface ITarget {
void f1();
void f2();
void fc();
}
public class Adaptee {
public void fa() { //... }
public void fb() { //... }
public void fc() { //... }
}
public class Adaptor extends Adaptee implements ITarget {
public void f1() {
super.fa();
}
public void f2() {
//...重新实现f2()...
}
// 这里fc()不需要实现,直接继承自Adaptee,这是跟对象适配器最大的不同点
}
第二种,基于组合的对象适配器
public interface ITarget {
void f1();
void f2();
void fc();
}
public class Adaptee {
public void fa() { //... }
public void fb() { //... }
public void fc() { //... }
}
public class Adaptor implements ITarget {
private Adaptee adaptee;
public Adaptor(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void f1() {
adaptee.fa(); //委托给Adaptee
}
public void f2() {
//...重新实现f2()...
}
public void fc() {
adaptee.fc();
}
}
使用哪一种适配方式有两个判断标准,一个是 Adaptee 接口的个数,另一个是 Adaptee 和 ITarget 的契合程度。
- 如果 Adaptee 接口并不多,那两种实现方式都可以。
- 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都相同,那我们推荐使用类适配器,因为 Adaptor 复用父类 Adaptee 的接口,比起对象适配器的实现方式,Adaptor 的代码量要少一些。
- 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都不相同,那我们推荐使用对象适配器,因为组合结构相对于继承更加灵活。
业界经典实现
- Sl4j日志框架