一、背景
笔记本电脑的插头一般都是三相的,即除了阳极、阴极外,还有一个地极。而有些地方的电源插座却只有两极,没有地极。电源插座与笔记本电脑的电源插头不匹配使得笔记本电脑无法使用。
二、定义
适配器模式就是把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
适配器模式分类的适配器模式和对象的适配器模式。
三、类适配器模式
类的适配器模式把适配的类的API转换成为目标类的API。
目标类中有两个接口,需要实现,但是Adaptee类中只有一个接口,用Adapter就可以让Target类和Adaptee类能够在一起工作。其中Adapter和Adaptee是继承的关系,这就是类的适配器模式。
模式所涉及的角色有:
- 目标(Target)角色:这就是所期待得到的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类。
- 源(Adapee)角色:现在需要适配的接口。
- 适配器(Adapter)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。
public interface Target {
/**
* 这是源类Adaptee也有的方法
*/
public void sampleOperation1();
/**
* 这是源类Adapteee没有的方法
*/
public void sampleOperation2();
}
上面给出的是目标角色的源代码,这个角色是以一个JAVA接口的形式实现的。可以看出,这个接口声明了两个方法:sampleOperation1()和sampleOperation2()。而源角色Adaptee是一个具体类,它有一个sampleOperation1()方法,但是没有sampleOperation2()方法。
public class Adaptee {
public void sampleOperation1(){}
}
public class Adapter extends Adaptee implements Target {
/**
* 由于源类Adaptee没有方法sampleOperation2()
* 因此适配器补充上这个方法
*/
@Override
public void sampleOperation2() {
//写相关的代码
}
}
适配器角色Adapter扩展了Adaptee,同时又实现了目标(Target)接口。由于Adaptee没有提供sampleOperation2()方法,而目标接口又要求这个方法,因此适配器角色Adapter实现了这个方法。
四、对象适配器模式
与类的适配器模式一样,对象的适配器模式把被适配的类的API转换成为目标类的API,与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用委派关系连接到Adaptee类。
从上图可以看出,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法。为使客户端能够使用Adaptee类,需要提供一个包装(Wrapper)类Adapter。这个包装类包装了一个Adaptee的实例,从而此包装类能够把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee是委派关系,这决定了适配器模式是对象的。
所以最后Adapter类改为:
public class Adapter {
private Adaptee adaptee;
public Adapter(Adaptee adaptee){
this.adaptee = adaptee;
}
/**
* 源类Adaptee有方法sampleOperation1
* 因此适配器类直接委派即可
*/
public void sampleOperation1(){
this.adaptee.sampleOperation1();
}
/**
* 源类Adaptee没有方法sampleOperation2
* 因此由适配器类需要补充此方法
*/
public void sampleOperation2(){
//写相关的代码
}
}
五、类适配器和对象适配器比较
1、类适配器使用对象继承的方式,是静态的定义方式;而对象适配器使用对象组合的方式,是动态组合的方式。
2、对于类适配器,由于适配器直接继承了Adaptee,使得适配器不能和Adaptee的子类一起工作,因为继承是静态的关系,当适配器继承了Adaptee后,就不可能再去处理 Adaptee的子类了。
对于对象适配器,一个适配器可以把多种不同的源适配到同一个目标。换言之,同一个适配器可以把源类和它的子类都适配到目标接口。因为对象适配器采用的是对象组合的关系,只要对象类型正确,是不是子类都无所谓。
3、对于类适配器,适配器可以重定义Adaptee的部分行为,相当于子类覆盖父类的部分实现方法。
对于对象适配器,要重定义Adaptee的行为比较困难,这种情况下,需要定义Adaptee的子类来实现重定义,然后让适配器组合子类。虽然重定义Adaptee的行为比较困难,但是想要增加一些新的行为则方便的很,而且新增加的行为可同时适用于所有的源。
4、对于类适配器,仅仅引入了一个对象,并不需要额外的引用来间接得到Adaptee。
对于对象适配器,需要额外的引用来间接得到Adaptee。
建议尽量使用对象适配器的实现方式,多用合成/聚合、少用继承。当然,具体问题具体分析,根据需要来选用实现方式,最适合的才是最好的。
六、适配器模式的优缺点
1、适配器模式优点
(1)更好的复用性
系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。
(2)更好的扩展性
在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。
2、适配器模式缺点:
过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
七、缺省适配器模式
Target类中的接口并不是Adapter需要都实现的 ,在Adapter之前可以用个缺省的类对于这些接口简单实现。
例如,
public interface Target {
void test1();
void test2();
void test3();
}
上面三个接口,Adapter只是需要实现其中一个即可,并不想将其他的两个接口放到Adapter中实现。可以在Adapter之前弄个基类,例如,
public abstract class AbstractAdapter implement Target {
void test1() {}
void test2() {}
void test3() {}
}
public class Adapter extends AbstractAdapter {
void test3() {....}
}
对于test1() 和 test2() 两个接口,Adapater并不想将其表现在类中,造成平庸的感觉。当所有的Adapter类想实现不平庸的接口时,可以在之前用个抽象类,将所有的接口平庸话,这就是缺省适配器模式的意义。
八、策略模式和适配器模式的区别
在策略模式中提到了,各种Adapter其实可以看成是一个策略模式的实现,那策略模式和适配器模式有什么区别呢?
1、意义不同
策略模式是将所有的不同的策略(或算法的实现)封装成不同的策略类,彼此之间是平行的。
适配器模式是将系统中的某个类(Adaptee类)加以适配,形成一个适配器类(Adapter类)以便达到用户的需求。
2、实现不同
对于策略模式,用户不关心算法,只需要一个抽象策略类(抽象Strategy类)的接口,但是用户需要知道所有的策略类(ConcreteStrategy类),并需要知道它们分别代表着策略。(给我个jar库,告诉我接口名字和类名字即可)
对于适配器模式,用户需要关心每个接口的实现。
3、策略模式比较适配器模式更优,代码比较简化,耦合度也降低(每个策略类是平行的)。
九、Androd中的适配器模式
对于android开发者来说起,适配器模式简直太熟悉不过,有很多应用可以说是天天在直接或者间接的用到适配器模式,比如ListView。
ListView用于显示列表数据,但是作为列表数据集合有很多形式,有Array,有Cursor,我们需要对应的适配器作为桥梁,处理相应的数据(并能形成ListView所需要的视图)。
正是因为定义了这些适配器接口和适配器类,才能使我们的数据简单灵活而又正确的显示到了adapterview的实现类上。
适配器模式,Adapter Pattern,勇敢的去适配,大量的资源可以重用。
详细的source code暂不列出,其中的BaseAdapter继承自ListAdapter,BaseAdapter是缺省适配器类,其中将一些接口缺省实现了,例如isEmpty() 和 isEnabled()。
后面的CursorAdapter 和ArrayAdapter都是继承自BaseAdapter。
ListView 中存有ListAdapter对象,通过接口setAdapter传入Adapter。