适配器这个词来源于硬件领域,是一个独立的硬件设备接口,允许硬件或电子接口与其它硬件或电子接口相连,比如常见的电源适配器,将额定220V电压转换为低功率设备所需要的电压电流如12V5A等。适配器模式也是基于这样的思想而出现的一种设计模式,适配器模式同样能够在原本功能不变的情况下,让使用者能够通过“适配器”在不同方法中正常使用原本已有的功能,也就是让新方法通过适配器适配了原本的功能。
在项目开发中,经常会在我们自己的代码里面整合框架中已有的功能,但是框架中的方法又没有提供扩展的接口,于是这个时候适配器模式就派上了用场。适配器模式通过一个适配器,让原本不相关且不兼容的两个功能方法能够一起协同工作,而对于调用方来说不需要做过多的实现就能够在自己的接口中实现原本接口的功能。也就是说我们可以在不改变原有功能的前提下,使用适配器模式在我们自己的代码中对原功能进行实现和扩展。
适配器模式的结构比较简单,主要就是适配器 Adapter 和被适配的对象 Adaptee
适配器模式有类适配器和对象适配器两种方式,首先我们来看看类适配器方式,类适配器主要是使用继承的方式在适配器中调用被适配的对象,代码如下:
首先我们需要定义被适配的对象 Adaptee 和客户端接口及方法,而适配器类通过实现 Client 接口以及继承被适配类 Adaptee ,通过在 adapterMethod 方法中调用 Adaptee 类的 method() 方法来完成 Adaptee 对象的适配
// 被适配对象
public class Adaptee {public void method() {System.out.println("adaptee method.");}
}
// 客户端接口
public interface Client {void adapterMethod();
}
// 适配器
public class Adapter extends Adaptee implements Client {@Overridepublic void adapterMethod() {super.method();}
}
客户端调用代码如下:
Client client = new Adapter();
client.adapterMethod(); // 控制台输出:adaptee method.
而对象适配器不需要适配器继承被适配对象,而是通过将被适配对象作为参数进行调用,代码如下:
//适配器
public class Adapter implements Client {private Adaptee adaptee;public Adapter() {this.adaptee = new Adaptee();}@Overridepublic void adapterMethod() {adaptee.method();}
}
这时客户端调用代码就有点不同:
Client client = new Adapter();
client.adapterMethod(); // 控制台输出:adaptee method.
这种数据线相信大家在生活中都看到过,平时是 Micro 接口的,但是我们只需将苹果接口转换器插入 Micro 接口上,便能让这根数据线能够同样适用于苹果手机,这时便能用适配器模式来完成这一场景。这个时候我们的 Micro 接口数据线正常使用,并将其作为被适配对象来适用于苹果手机。定义接口 IphoneInterface 并定义手机充电方法 charge()。有了 Micro 接口数据线,也有了 iphone 充电的方法,这个时候就需要定义充电适配器 ChargeAdapter 将 Micro 数据线适配用于 iphone 充电。Micro 类和 IphoneInterface 接口定义如下:
public class Micro {public void charge(){System.out.println("Use the micro usb interface for charging.");}
}
public interface IphoneInterface {void charge();
}
首先我们类适配器的方式进行编码,这个时候要让适配器继承被适配类 Micro,代码如下:
public class ChargeAdapter extends Micro implements IphoneInterface {@Overridepublic void charge() {System.out.println("iphone is charging.");super.charge();}
}
客户端调用方法及控制台输出如下:
客户端调用方法IphoneInterface iphone = new ChargeAdapter();iphone.charge();控制台打印:iphone is charging.Use the micro usb interface for charging.
接下来我们使用对象适配器来编码,区别在于适配器 ChargeAdapter 不继承被适配的类,而是将该对象作为参数进行调用:
public class ChargeAdapter extends Micro implements IphoneInterface {private Micro micro;public ChargeAdapter() {this.micro = new Micro();}@Overridepublic void charge() {System.out.println("iphone is charging.");micro.charge();}
}
客户端调用方法及控制台输出如下:
客户端调用方法IphoneInterface iphone = new ChargeAdapter();iphone.charge();控制台打印:iphone is charging.Use the micro usb interface for charging.
可以看到我们的 iphone 最后通过适配器都能够使用 Micro 接口的数据线进行充电了。看到这里肯定很多同学存在疑惑,这适配器模式咋跟装饰模式这么像(不了解装饰模式的话可以参考这篇博文:【深入设计模式】装饰模式—什么是装饰模式?装饰模式在源码中的应用)尤其是对象适配的方式?确实两种模式的写法非常相似,但是我们需要注意的时候装饰模式注重功能的增强,而适配器模式更注重于调用的转换。换句话说装饰模式是在已有功能上进行增强,而增强后的对象和被增强的对象是同一类的,也就是拥有相同父类;而适配器模式从客户端来看是通过适配器将两个毫不相关的对象进行关联,比如上面例子的用 Micro 数据线给 iphone 充电。
在 JDK 中我们经常使用 Set 集合,而我们如果看到源码便知道其实 JDK 中没有 Set 这种数据结构而是通过 Map 来实现的。以 HashSet 为例,在 HashSet 源码中维护了一个 HashMap 变量 map,而在调用 HashSet 构造方法是对该 map 进行初始化,我们再看添加元素、删除元素等常用方法时,均是在调用 map 对象的方法。从这里便能很明显的看出 HashSet 使用了适配器模式,HashSet 实际上就是一个适配器,被适配的对象就是 HashMap。
public class HashSetextends AbstractSetimplements Set, Cloneable, java.io.Serializable
{static final long serialVersionUID = -5024744406713321676L;private transient HashMap map;public HashSet() {map = new HashMap<>();}public boolean add(E e) {return map.put(e, PRESENT)==null;}public boolean contains(Object o) {return map.containsKey(o);}public boolean remove(Object o) {return map.remove(o)==PRESENT;}public int size() {return map.size();}……
}
适配器模式的整体思想和代码都比较简单,当我们需要在一个类中使用另外的类对象方法时,就可以考虑使用适配器模式,这样客户端在使用时便能够统一接口。适配器模式在日常开发中还是非常常见的,然而从某种意义上来讲,随着项目迭代,适配器模式其实是一种弥补措施,让新的接口兼容使用老的接口方式,因此我们应当在新接口以及被适配对象都不会发生太多变动的时候再考虑使用适配器模式,并且过多的使用适配器模式会让整体代码变得凌乱。