适配器模式是把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
这很像变压器(adapter)。变压器把一种电压变换成另外一种电压。把美国的电器拿回来国内使用的时候,用户就面临电压不同的问题。美国的生活用电是110V,而中国是220V。如果要在国内使用美国的电器,就必须有一个能把220V电压转换为110V电压的变压器。而这正是本模式所做的事情,因此本模式也常常被成为变压器模式。
同时,这种做法也很像货物的包装过程:被包装的货物的真实样子被包装所掩盖和改变,因此有人把这种模式叫做包装(Wrapper)模式。事实上,大家经常写很多这样的Wrapper类,把已有的一些类包装起来,使之能有满足需要的接口。
适配器模式的形式
适配器模式有类的适配器模式和对象的适配器模式两种不同的形式。
类的适配器模式
模式结构
类的适配器模式把被适配的类的API转换成为目标类的API,其静态结构如下图所示:
在上图可以看出:Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法。为使客户端能够使用Adaptee类,提供一个中间环节,即类Adapter,把Adaptee的API方法与Target接口的API衔接起来。Adapter与Adaptee是继承关系,这决定了这个适配器模式是类的。
模式角色
- 目标(Target)角色:这就是期待得到的接口。注意,由于这里讨论的是类的适配器模式,因此目标不可以是类。
- 源(Adaptee)角色:现有需要适配的类。
- 适配器(Adapter)角色:适配器类是模式的核心。适配器把源类转换为支持目标接口的类。显然,这一角色不可能是接口,而必须是具体类。
模式代码
Target的源代码:
package com.javapatterns.adapter.classAdapter;
public interface Target
{
/**
* 这是源类也有的方法
* /
public void sampleOperation1();
/**
* 这是源类没有的方法
* /
public void sampleOperation2();
}
Adaptee的源代码:
package com.javapatterns.adapter.classAdapter;
public class Adaptee
{
/**
* 源类的方法sampleOperation1
*/
public void sampleOperation1() { }
}
Adapter的源代码:
package com.javapatterns.adapter.classAdapter;
public class Adapter extends Adaptee implements Target
{
/**
* 由于源类没有方法sampleOperation2,
* 因此适配器类补充上这个方法
*/
public void sampleOperation2(){ }
}
模式效果
使用一个具体类把源(Adaptee)适配到目标(Target)中。这样依赖,如果源以及源的子类都适用此类适配,就行不通了。
由于适配器类是源的子类,因此可以在适配器类中置换掉(Override)源的一些方法。
由于只引进了一个适配器类,因此只有一个路线到达目标类,使问题得到简化。
对象的适配器模式
模式结构
与类的适配器模式一样,对象的适配器模式把被适配的类的API转换成为目标类的API。与类的适配器不同的是,对象的适配器模式不是使用继承关系连接到Adatpee类,而是使用委派关系连接到Adaptee类。
从上图可以看出,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法。为使客户端能够使用Adaptee类,需要提供一个包装(Wrapper)类的Adaper。这个包装类包装了一个Adaptee的实例,从而此包装类能够把Adaptee的API与Target接口的API衔接起来。Adapter与Adaptee是委派关系,这决定了这个适配器模式是对象的。
模式角色
- 目标(Target)角色:这就是期待得到的接口。目标可以是具体或者抽象的类。
- 源(Adaptee)角色:现有需要适配的类。
- 适配器(Adapter)角色:适配器类是模式的核心。适配器把源类转换为支持目标接口的类。显然,这一角色必须是具体类。
模式代码
Target源代码:
package com.javapatterns.adapter.classAdapter;
public interface Target
{
/**
* 这是源类也有的方法
* /
public void sampleOperation1();
/**
* 这是源类没有的方法
* /
public void sampleOperation2();
}
Adaptee的源代码:
package com.javapatterns.adapter.classAdapter;
public class Adaptee
{
/**
* 源类的方法sampleOperation1
*/
public void sampleOperation1() { }
}
Adapter的源代码:
package com.javapatterns.adapter.classAdapter;
public class Adapter implements Target
{
private Adaptee adaptee;
public Adapter(Adaptee adaptee)
{
super();
this.adaptee = adaptee;
}
/**
* 由于源类有方法sampleOperation1,
* 因此适配器类直接委派即可
*/
public void sampleOperation1()
{
adaptee.sampleOpreation1();
}
/**
* 由于源类没有方法sampleOperation2,
* 因此适配器类补充上这个方法
*/
public void sampleOperation2(){ }
}
模式效果
- 一个适配器可以把多种不同的源适配到同一个目标。换言之,同一个适配器可以把源类和它的子类都适配到目标接口。
- 与类的适配器模式相比,要想置换源类的方法就不容易。如果一定要置换掉源类的一个或多个方法,就只好先做一个源类的子类,将源类的方法置换掉,再把源类的子类当作真正的源类进行适配。
- 虽然要想置换源类的方法不容易,但是要想增加一些新方法则方便得很,而且新增加的方法可同时适配与所有的源。
什么情况下使用适配器模式
在以下各种情况下使用适配器模式:
- 系统需要使用现有的类,而此类的接口不符合系统的需要。
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口。
- 在设计里,需要改变多个已有的子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器类,而这不太现实。