1.什么是适配器模式
适配器模式把一个类的接口转换成客户端所期待的另一种接口,从而使得原来因接口不匹配而无法在一起工作的两个类能够在一起工作。
或者说适配器模式主要用于希望复用一些现存的类,但是接口又与复用环境要求不一致。
用电器做例子,笔记本电脑的插头一般都是三相的,即除了阳极、阴极外,还有一个地极。而有些地方的电源插座却只有两极,没有地极。电源插座与笔记本电脑的电源插头不匹配使得笔记本电脑无法使用。这时候一个三相到两相的转换器(适配器)就能解决此问题,而这正像是本模式所做的事情。
2.适配器的结构
适配器分为类适配器和对象适配器两种不同的形式。
适配器有三个角色:拟实现的角色、现已有的角色、适配器角色,我们要做的是让适配器角色去implement拟实现角色的接口,同时extends现已有角色类(类适配器)或关联一个现已有角色的对象(对象适配器)。对于类适配器,在适配器角色中重新定义拟实现角色中有的但现已有角色中没有的方法;对于对象适配器,如果现已有角色和拟实现角色有相同的方法,在调用拟实现角色的方法时实际上调用现已有角色的方法。
在上图中可以看出,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法。为使客户端能够使用Adaptee类,提供一个中间环节,即类Adapter,把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee是继承关系,这决定了这个适配器模式是类的:
该模式涉及三个角色:
目标角色(Target):期待实现的接口,注意这里是类适配器模式,故目标不可以是类(也就是电脑的三相插头);
源角色(Adaptee):现在需要适配的接口(也就是现在有的两相插头);
适配器角色(Adapter):本模式的核心,它把源角色转换为目标角色,显然,这一角色不可能是接口,只能是具体类;
public interface Target(){
//源类也有该方法
public void sampleOperationOne();
//源类没有的方法
public void sampleOperationTwo();
}
public class Adaptee(){
public void sampleOperationOne();
}
public class Adapter extends Adaptee implements Target(){
//适配器角色Adapter扩展了Adaptee,同时又实现了目标(Target)接口。
//由于Adaptee没有提供sampleOperation2()方法,而目标接口又要求这个方法,
//因此适配器角色Adapter实现了这个方法
public void sampleOperationTwo();
}
对象适配器模式:
与类的适配器模式一样,对象的适配器模式把被适配的类的API转换成为目标类的API,与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用委派关系连接到Adaptee类。
从上图可以看出,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法。为使客户端能够使用Adaptee类,需要提供一个包装(Wrapper)类Adapter。这个包装类包装了一个Adaptee的实例,从而此包装类能够把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee是委派关系,这决定了适配器模式是对象的。
public interface Target(){
//源类也有该方法
public void sampleOperationOne();
//源类没有的方法
public void sampleOperationTwo();
}
public class Adaptee(){
public void sampleOperationOne();
}
public class Adapter extends Target(){
Adaptee adaptee = new Adaptee();
public Adapter(Adaptee adaptee){
this.adaptee = adaptee;
}
//源类有sampleOperationOne()方法,因此可以直接委派给Adapter
public void sampleOperationOne(){
this.adaptee.sampleOperationOne();
}
//源类没有sampleOperationTwo()方法,因此需要由Adapter补充
public void sampleOperationTwo(){
//codes
}
}
3.类适配器和对象适配器的对比
①类适配器使用继承的方式,是静态的定义方式;而对象适配器使用对象组合的方式,是动态组合的方式;
②类适配器直接继承了Adaptee,使得适配器不能和Adaptee的子类一起工作,因为继承是静态的关系,当适配器继承了Adaptee后就不能去处理Adaptee的子类了;
对象适配器可以把多种不同的源适配到一个目标,换言之,同一个适配器可以把源类和它的子类都适配到目标接口。因为对象适配器采用的是对象组合的关系,只要对象类型正确,是不是子类都无所谓;
③对于类适配器,适配器可以重定义Adaptee的部分行为,相当于子类覆盖父类的部分实现方法;
对于对象适配器,要重定义Adaptee的行为比较困难,这种情况下,需要定义Adaptee的子类来实现重定义,然后让适配器组合子类。虽然重定义Adaptee的行为比较困难,但是想要增加一些新的行为则方便的很,而且新增加的行为可同时适用于所有的源;
④对于类适配器,仅仅引入了一个对象,并不需要额外的引用来间接得到Adaptee,对于对象适配器,需要额外的引用来间接得到Adaptee;
建议尽量使用对象适配器,多用合成/聚合,少用继承。
4.缺省适配器模式
缺省适配(Default Adapter)模式为一个接口提供缺省实现,这样子类型可以从这个缺省实现进行扩展,而不必从原有接口进行扩展。
在很多情况下,必须要一个具体类实现某个接口,但是这个类又用不到接口的所规定的所有方法,这个时候我们可以用一个抽象的适配器类实现接口,此抽象类要给接口所要求的每一种方法都提供一个空方法,然后让我们要实现的具体类去继承该抽象类。
public interface AbstractService {
public void serviceOperationOne();
public int serviceOperationTwo();
public String serviceOperationThree();
}
public abstract class ServiceAdapter implements AbstractService{
public void serviceOperationOne(){
}
public int serviceOperationTwo(){
return 0;
}
public String serviceOperationThree(){
return null;
}
}
public class Service extends ServiceAdapter(){
public void serviceOperationOne(){
//do something
}
}
适配器模式的用意是要改变源的接口,以便于目标接口相容。缺省适配的用意稍有不同,它是为了方便建立一个不平庸的适配器类而提供的一种平庸实现。在任何时候,如果不准备实现一个接口的所有方法时,就可以使用“缺省适配模式”制造一个抽象类,给出所有方法的平庸的具体实现。这样,从这个抽象类再继承下去的子类就不必实现所有的方法了。