有的笔记本电脑的工作电压是20V,而我国的家庭用电是220V,如何让20V的笔记本电脑能够在220V的电压下工作?答案是引入一个电源适配器(AC Adapter),俗称充电器/变压器。有了这个电源适配器,生活用电和笔记本电脑即可兼容,如下图所示:
在软件开发中,有时也存在类似这种不兼容的情况,也可以像引入一个电源适配器一样引入一个被称为适配器的角色来协调这些存在不兼容的结构,这种设计方案即为适配器模式。
与电源适配器相似,在适配器模式中引入了一个被称为适配器(Adapter)的包装类,而它所包装的对象称为适配者(Adaptee),即被适配的类。适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器让那些由于接口不兼容而不能交互的类可以一起工作。
适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
注:在适配器模式定义中所提及的接口是指广义的接口,它可以表示一个方法或者一组方法的集合。
在适配器模式中,通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器模式和类适配器模式两种。在对象适配器模式中,适配器与适配者之间是关联关系;
在类适配器模式中,适配器与适配者之间是继承(或实现)关系。在实际开发中,对象适配器模式的使用频率更高,其结构如下图所示:
可以看出,在对象适配器模式结构图中包含以下3个角色:
代码示例:
/**
* 适配者类
*/
public class Adaptee {
public String specificRequest(String org) {
return org;
}
}
/**
* 适配器类
*/
public class Adapter extends Target{
// 持有一个对象适配者类对象的引用
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public String request(String org) {
// 转发调用
return adaptee.specificRequest(org);
}
}
/**
* 目标抽象类
*/
public class Target {
public String request(String org) {
return org;
}
}
/**
* 客户端
*/
public class Client {
public static void main(String[] args) {
Target target = new Target();
target.request("1024");
}
}
类适配器模式与对象适配器模式最大的区别在于其适配器和适配者之间的关系是继承关系。类适配器模式结构如下图所示。
所示的类适配器模式结构图,适配器类实现了抽象目标类接口Target,并继承了适配者类。在适配器类的request()方法中调用所继承的适配者类的specificRequest()方法,实现了适配。典型的类适配器模式代码如下:
/**
* 适配器类
*/
public class Adapter extends Adaptee implements Target {
public String request(String org) {
// 转发调用
return super.specificRequest(org);
}
}
/**
* 抽象目标类接口
*/
public interface Target {
String request(String org);
}
由于Java、C#等语言不支持多重类继承,因此类适配器模式的使用受到很多限制。例如,如果目标抽象类Target不是接口,而是一个类,就无法使用类适配器模式。此外,如果适配者Adaptee为最终(Final)类,也无法使用类适配器模式。在Java等面向对象编程语言中,大部分情况下使用的是对象适配器模式,类适配器模式较少使用。
在对象适配器模式的使用过程中,如果在适配器中同时包含对目标类和适配者类的引用,适配者可以通过它调用目标类中的方法,目标类也可以通过它调用适配者类中的方法,那么该适配器就是一个双向适配器。其模式结构示意图如下图所示:
双向适配器模式的实现较为复杂,其典型代码如下:
/**
* 适配器类
*/
public class Adapter implements Target, Adaptee {
// 同时持有抽象目标类和适配者的引用
private Target target;
private Adaptee adaptee;
public Adapter(Target target) {
this.target = target;
}
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public String specificRequest(String org) {
return adaptee.specificRequest(org);
}
@Override
public String request(String org) {
return target.request(org);
}
}
缺省适配器模式是适配器模式的一种变体,其应用也较为广泛。
缺省适配器模式的定义如下:
当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求。它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。
适配器模式将现有接口转化为客户类所期望的接口,实现了对现有类的复用。它是一种使用频率非常高的设计模式,在软件开发中得以广泛应用,在Spring等开源框架、驱动程序设计(例如JDBC中的数据库驱动程序)中也使用了适配器模式。
对象适配器模式还有如下优点:
(1)一个对象适配器可以把多个不同的适配者适配到同一个目标。
(2)可以适配一个适配者的子类。由于适配器和适配者之间是关联关系,根据里氏代换原则,适配者的子类也可通过该适配器进行适配。
对象适配器模式的缺点是:与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,在子类中将适配者类的方法置换掉,然后再把适配者类的子类当作真正的适配者进行适配,实现过程较为复杂。