- 类的结构模式:类的结构模式使用继承把类、接口等组合在一起,以形成更大的结构。当一个类从父类继承并实现某接口,这个新类把父类的结构和接口的结构结合起来,类的结构模式是静态的,一个类的结构模式的典型例子就是类形式的适配器模式。
- 对象的结构模式:对象的结构模式描述怎样把各种不同类型的对象组合在一起,以实现新的功能的方法,对象结构模式是动态的,对象机构模式的典型例子就是代理人模式。
适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够一起工作。
适配器模式的两种形式
适配器模式有类的适配器模式和对象的适配器模式两种不同的形式。
类的适配器模式的结构
类的适配器模式把被适配的类的API转换成为目标类的API,其静态结构图如下:
在上图中可以看出,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法,为使客户端能够使用Adaptee类,提供一个中间环节,即类Adapter,把Adaptee的API与Target类的API 衔接起来,Adapter和Adaptee是继承关系,这决定了这个适配器模式是类形式的。
模式所涉及的角色有:
- 目标(Target)角色:这就是所期待得到的接口,注意由于这里讨论的是类适配器模式,因此目标不可以是类。
- 源(Adaptee)角色:现有需要适配的接口。
- 适配器(Adapter)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。这角色不可以是接口,必须是实体类。
示意性源代码如下
目标角色类:Target.java
package com.model.structural.adapter;
public interface Target {
//源类中有的方法
void sampleOperation1();
//源类中没有的方法
void sampleOperation2();
}
源角色类:
Adaptee.javapackage com.model.structural.adapter;
public class Adaptee {
public void sampleOperation1(){
System.out.println(" this is the source class");
}
}
适配器角色类:Adapter.java
package com.model.structural.adapter;
public class Adapter extends Adaptee implements Target {
public void sampleOperation2() {
System.out.println("this is the Adapter");
}
}
类的适配器模式的效果
使用一个具体的类把源(Adaptee)适配到目标(Target)中。这样一来,如果源以及源的子类都使用此适配器,就行不通了。
由于适配器是源的子类,因此可以在适配器类中置换掉(Override)源的一些方法。
只引进了一个适配器,因此只要有一个路线到达目标类,使问题得到简化。
对象的适配器模式的结构
与类的适配器模式一样,对象的适配器模式把适配的类的API转换成为目标类的API,与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用委派关系连接到Adapter类。对象的适配器模式的静态结构如下图:
![](http://static.oschina.net/uploads/space/2013/0402/132929_E8Pr_123079.jpg)
上图可以看出,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法,为使客户端能够使用Adaptee类,需要提供一个包装(Wrapper)类Adapter。这个包装类包装了一个Adaptee的实例,从而此包装类能够把Adaptee的API 和Target 类的API 衔接起来,Adapter与adaptee是委派关系,这决定了这个适配器模式是对象的。
从上图中可以看出,模式所涉及的角色有:
- 目标(Target)角色:这就是所期待的接口,目标可以是具体的或抽象的类。
- 源(Adaptee)角色: 现有需要适配的接口。
- 适配器(Adapter)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口,显然这一角色必须是具体类。
本模式的示意性源代码如下:
Target.java类
package com.model.structural.adapterobj;
public interface Target {
/**
* 这是源类也有的方法sampleOperation1
* */
void sampleOperation1();
/**
* 这是源类也有的方法sampleOperation2
* */
void sampleOperation2();
}
Adaptee.java
package com.model.structural.adapterobj;
public class Adaptee {
/**
* 源类有方法sampleOperation1
* */
public void sampleOperation1(){
System.out.println("源类有方法sampleOperation1");
}
}
Adapter.java
package com.model.structural.adapterobj;
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee){
super();
this.adaptee = adaptee;
}
/**
* 源类有方法sampleOperation1
* 因此适配器类直接委派即可
* */
public void sampleOperation1() {
adaptee.sampleOperation1();
}
/**
* 源类没有方法sampleOperation2
* 因此适配器需要补充此方法
* */
public void sampleOperation2() {
System.out.println("write you code here");
}
}
适配器模式的用意是将接口不同而功能相同的或者相近的两个接口加以转换,这里面包括适配器角色补充了一个源角色没有的方法,适配器角色实际上是源角色没有的的方法。注意:不要误以为适配器模式就是为补充源角色没有的方法而准备的。
对象的适配器模式的效果:
(1)一个适配器可以把多种不同的远适配到同一个目标。换而言之,一个适配器可以把源类和它的子类都适配到目标接口。
(2)与类的适配器相比,要想置换源类的方法就不容易,如果一定要置换掉源类的一个或多个方法,就只好先做一个源类的子类,将源类的方法置换掉,然后再把源类的子类当做真正的源进行适配。
(3)虽然要想置换源类的方法不容易,但是要想增加一些新的方法则比较方便,而且新增加的方法可同时适用于所有的源。
在什么情况下适合适用适配器模式
(1)系统需要使用现有的类,而此类的接口不符合系统的需求。
(2)想要建立一个可重用的类,用于与一些彼此间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口。
(3)(对对象的适配器模式而言)在设计里,需要改变多个已有的子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器类,而这不大实际。
利用适配器模式指方为园
中国古代有指鹿为马的故事,在《楚辞·九辩》中说:“圆凿而方枘,吾固知其龃龉难入。”不论是指鹿为马还是指方为圆都需要适配器模式。
适配器在本例子中的类图如下图:
Cube.java 类源码如下,这是类的源角色。
package com.model.structural.adapterex;
import java.math.BigDecimal;
public class Cube {
private double width;
/**
* 构造子
* */
public Cube(double width){
this.width = width;
}
/**
* 计算体积
* */
public BigDecimal calculateVolume(){
return BigDecimal.valueOf(width).multiply(BigDecimal.valueOf(width)).multiply(BigDecimal.valueOf(width));
}
/**
* 计算面积
* */
public BigDecimal calculateFace(){
return BigDecimal.valueOf(width).multiply(BigDecimal.valueOf(width));
}
/**
* 长度取值
* */
public double getWidth(){
return this.width;
}
/**
* 长度的赋值方法
* */
public void setWidth(double width){
this.width = width;
}
}
CubeInfo.java 源码如下,是目标接口角色
package com.model.structural.adapterex;
import java.math.BigDecimal;
public interface CubeInfo {
/**
* 计算面积
* */
BigDecimal calculateArea();
/**
* 计算体积
* */
BigDecimal calculateVolume();
/**
* 半径取值
* */
double getRadius();
/**
* 半径的赋值
* */
void setRadius(double radius);
}
MagicFinger.java 源码如下,是适配器角色
package com.model.structural.adapterex;
import java.math.BigDecimal;
public class MagicFinger implements CubeInfo{
private double radius=0;
private static final double PI =3.14D;
private Cube adaptee;
public MagicFinger(Cube adaptee){
super();
this.adaptee = adaptee;
radius = adaptee.getWidth();
}
/**
* 面积(球的面积公式:4*PI*r*r)
* */
public BigDecimal calculateArea() {
return BigDecimal.valueOf(4).multiply(BigDecimal.valueOf(PI)).multiply(BigDecimal.valueOf(radius)).multiply(BigDecimal.valueOf(radius));
}
/**
* 体积(球的体积公式:4/3*PI*r*r*r)
* */
public BigDecimal calculateVolume() {
return BigDecimal.valueOf(4/3).multiply(BigDecimal.valueOf(PI)).multiply(BigDecimal.valueOf(radius)).multiply(BigDecimal.valueOf(radius)).multiply(BigDecimal.valueOf(radius)).multiply(BigDecimal.valueOf(radius));
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
}
关于模式实现的讨论
适配器模式在实现的时候,有以下值得注意的地方:
(1)目标接口可以省略。此时,目标接口和源接口是相同的,由于源是一个接口,而适配器是一个类(或抽象类),因此这种做法看似很平庸,实际却不平庸,它可以使客户端不必实现不需要的方法。
(2)适配器的类可以是抽象类。
(3)带参数的适配器模式。使用这种办法,适配器类可以根据参数返回一个合适的实例给客户端。