Adapter Pattern:把一个类的接口变成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
适配器模式很像变压器(Adapter),变压器把一种电压转换成另一种电压。美国的生活电压是110V,中国的生活电是220V,如果把美国的电器放到中国使用,则必须要有一个能把220V的电压转成110V的变压器。同时,适配器模式的做法也很像货物的包装过程:被包装的货物的真实样子被包装所掩盖和修改,因此也有人把这种模式叫做包装(Wrapper)模式。
适配器有两种形式,类的适配器模式和对象的适配器模式。分写来介绍一下。
类的适配器模式
类的适配器模式把被适配的类的API转换成目标类的API,其静态结构图如下所示:
从上图可以看出,Adaptee类并没有needOperation2()方法,而客户端却期待这个方法。这个时候,我们就可以提供一个中间环节,把Adaptee的API和Adapter的API衔接起来,这个中间环节就是类适配器的核心—Adapter类。Adapter与Adaptee是继承关系,这决定了这个模式是类的适配器模式。
模式设计的角色:
a.目标角色(Target):我们希望得到的接口。Java不支持多重继承,所以在类的适配器模式中,目标角色必须是接口。
b.源角色(Adaptee):需要适配的接口。
c.适配器角色(Adapter):本模式的核心,显然,这一角色不可以是接口,必须是具体的类(因为它既要实现Target接口,又要继承Adaptee类,所以只能是具体的类)。
代码实现:
/** * 目标角色,在类的适配器模式中,只能是接口 * 定义了两个操作:needOperation1(),needOperation2() *@author cxy */ interface Target { void needOperation1(); void needOperation2(); } /** * 源角色:需要被适配的角色,只提供了目标角色接口的一部分功能 * @author cxy */ class Adaptee { public void needOperation1() { //do some thing; } } /** * 适配器角色:本模式的核心,把源类转换成目标接口 * 继承自源类,实现目标接口,所以适配器角色必须是具体的类 * @author cxy * */ class Adapter extends Adaptee implements Target { //直接调用源类的方法,当然也可以很方便的置换源类的方法 public void needOperation1() { super.needOperation1(); } //源类没有的方法,因此,适配器类补上这个方法 public void needOperation2() { //do some thing; } }
在类的适配器模式中,使用一个具体的类(Adapter)把源(Adaptee)适配到目标(Target)中,适配器类时源的子类,因此可以在适配器中很容易的置换掉源类的方法,但是这个适配器类只能适配源类,不能适配源类的子类。
对象的适配器模式
与类的适配器模式不同,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用委派关系。对象的适配器模式的静态结构图:
模式所涉及的角色:
a.目标角色(Target):我们希望得到的接口,可以是接口或者是类。
b.源角色(Adaptee):需要适配的接口。
c.适配器角色(Adapter):本模式的核心,这一角色不可以是接口,必须是具体的类,内部保存着对源类的引用。
代码实现:
/** * 目标角色,在类的适配器模式中,只能是接口 * 定义了两个操作:needOperation1(),needOperation2() *@author cxy */ interface Target { void needOperation1(); void needOperation2(); } /** * 源角色:需要被适配的角色,只提供了目标角色接口的一部分功能 * @author cxy */ class Adaptee { public void needOperation1() { //do some thing; } } /** * 适配器角色:本模式的核心,把源类转换成目标接口 * 内部保存源类的引用,提供源类没有方法 * @author cxy * */ class Adapter extends Adaptee implements Target { private Adaptee adaptee; public Adapter(Adaptee ada) { this.adaptee = ada; } //直接调用源类的方法,当然也可以很方便的置换源类的方法 public void needOperation1() { adaptee.needOperation1(); } //源类没有的方法,因此,适配器类补上这个方法 public void needOperation2() { //do some thing; } }
对象的适配器模式中,适配器角色内部保存着源角色的引用,而不再使用继承,因此:
(1)一个适配器可以把多种不同的源适配到同一目标,可以把源类和它的子类都适配到目标接口;
(2)想置换源类的方法就比较困难了。如果一定要置换,那么只能在源类的子类中置换,再把子类适配到目标接口,实际上源类的子类才是真正的源;
(3)可以方便的添加新的方法,新添加的方法适用于所有的源。
在以下情况下使用适配器模式:
(1)系统需要使用现有的类,而此类的接口不符合系统的需要。
(2)想要创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作。
(3)(仅适用于对象的Adapter模式)在设计里,想使用一些已经存在的子类,这是让适配器角色在内部保存着父类的引用,就可以把所有子类都适配到目标接口中。
==============================================================================================
下面讲一下缺省适配器(Default Adapter),我觉得缺省适配器应该应用更广泛,尤其是在Java中。
我们知道,如果一个类要实现某个接口,那么就要实现这个接口中的所有方法。比如人的行为接口PersonAction有20方法,男人的行为类ManAction需要实现这20个方法,女人的行为类WomanAction也需要实现这20个类。可问题是,男人和女人的很多行为是相同的,比如吃饭,行走,睡觉等等,在这20个方法中,只有打扮dress()方法不同,难道我们要把另外19个方法都实现两遍吗?这个时候缺省适配器模式就要闪亮登场了,缺省适配器模式为一个接口提供默认实现,这样子类型就可以从这个缺省实现进行扩展,而不需要从原有接口进行扩展。把上面的例子用图展现出来:
这样ManAction类和WomanAction类只需要实现dress()方法就行了。附上源代码:
/** * 需要实现的接口,方法很多 * @author cxy */ interface PersonAction { void sleep(); void eat(); void walk(); //... void dress(); } /** * 缺省适配器的核心—适配器类 * 提供对目标接口的”平庸“实现 * @author cxy */ class DefaultAdapter implements PersonAction{ public void sleep() { System.out.println("晚上睡觉"); } public void eat() { System.out.println("按时吃饭"); } //... public void walk() { System.out.println("双脚走路"); } public void dress() {} } /** *具体实现,扩展自DefaultAdapter类, *只需要实现自己关心的逻辑 *@author cxy */ class ManAction extends DefaultAdapter { public void dress() { System.out.println("男人只洗脸"); } } /** *具体实现,扩展自DefaultAdapter类, *只需要实现自己关心的逻辑 *@author cxy */ class WomanAction extends DefaultAdapter { public void dress() { System.out.println("洗面奶洗脸"); System.out.println("做面膜"); System.out.println("还要打扮两个小时才能完成"); } }
这个DefaultAction类就是适配器类,此适配器类实现了PersonAction接口所要求的所有方法,但是此适配器类给出的所有实现都是“平庸”的,这种“平庸化“的适配器模式就叫做缺省适配器模式。关于”平庸“实现,我参考的是《Java与模式》这本书,上面给出的实现是空实现,也就是不做任何事情,这种实现当然可以叫做平庸,但是我觉得”平庸“应该是所有实现该接口的类具有的共性行为,如果没有共性行为,那就可以使用空实现(如果我的这种理解有错误,请指出,我改正)。从DefaulAction类再扩展,就可以实现自己关心的方法了,我是大大的欣赏缺省适配器模式。