适配器模式是结构模式,包括类的结构模式和对象结构模式两种。适配器模式把一个类(系统中已有的类)的接口变成客户端所期待的另一种接口(需求,新的行为增强功能),从而使原本因为接口不匹配而无法再一起工作的两个类能够在一起工作。实质上是把已有的一些类应用适配器模式的结构进行包装(Wrapper),来满足需要的接口 (新的需求),达到软件复用和扩展的目的。
一、类的适配器模式的结构
类图与角色
在上图中可以看到,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法。为了使客户端能够使用Adaptee类,提供一个中间环节,即类Adapter,把Adaptee类的API和Target类的API衔接起来。Adapter和Adaptee是继承关系,因此这个适配器模式是类的。
目标(Target)角色:这就是所期待得到的接口(新建的接口,包含新的需求)。注意,对于类适配器模式,目标不可以是类。
源(Adaptee)角色:现在需要适配的接口(这个是系统已有的类,适配器模式要复用的类)。
适配器(Adapter)角色:继承源角色(Adaptee)并实现目标角色(Target)。适配器将源接口转换为目标接口。显然,这一角色不可以是接口,而必须是具体类。
源代码
目标角色Target是以Java接口的形式实现的。接口声明了两个方法:sampleOperation1()和sampleOperation2()。
Target的源代码。
/**
* 目标角色
*/
public interface Target {
/**
* 这个是源类也有的方法
*/
void sampleOperation1();
/**
* 这个是源类没有的方法
*/
void sampleOperation2();
}
源角色Adaptee是一个具体类,它有sampleOperation1()方法,但是没有sampleOperation2()方法。
Adaptee类的源代码。
/**
* 源角色
*/
public class Adaptee {
/**
* 源类含有的方法
*/
public void sampleOperation1(){
//code
}
}
适配器角色Adapter扩展了Adaptee类,同时又实现了目标接口。由于Adaptee没有提供sampleOperation2()方法,而目标接口又要求有这个方法,因此适配器角色实现了这个方法。
Adapter类的源代码。
/**
* 适配器角色(类的适配器模式)
*/
public class Adapter extends Adaptee implements Target{
/*
* 由于源类没有该sampleOperation2()方法,
* 所有适配器类需要补充这个方法。
*/
@Override
public void sampleOperation2() {
//write your code here
}
}
二、对象的适配器模式的结构
结构与角色
从上图中可以看到,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法。为了使客户端能够使用Adaptee类,需要提供一个包装(Wrapper)类Adapter。这个包装类包装了一个Adaptee类的实例,从而这个包装类能够把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee是委派关系,因此这个适配器模式是对象的。
目标(Target)角色:这就是所期待得到的接口。目标可以是具体的类或者抽象类。
源(Adaptee)角色:现在需要适配的接口。
适配器(Adapter)角色:适配器是本模式的核心。适配器将源接口转换为目标接口。显然,这一角色必须是具体类。
源代码
目标Target类的源代码。
/**
* 目标角色
*/
public interface Target {
/**
* 这个是源类也有的方法
*/
void sampleOperation1();
/**
* 这个是源类没有的方法
*/
void sampleOperation2();
}
源Adaptee类源代码。
/**
* 源角色
*/
public class Adaptee {
/**
* 源类含有的方法
*/
public void sampleOperation1(){
//code
}
}
适配器Adapter源代码。
/**
* 适配器角色(对象的适配器模式)
*/
public class Adapter_2 implements Target{
private Adaptee mAdaptee;
public Adapter_2(Adaptee mAdaptee) {
this.mAdaptee=mAdaptee;
}
/*
* 源类有sampleOperation1()方法
* 因此适配类直接委派即可。
*/
@Override
public void sampleOperation1() {
mAdaptee.sampleOperation1();
}
/*
* 由于源类没有该sampleOperation2()方法,
* 所有适配器类需要补充这个方法。
*/
@Override
public void sampleOperation2() {
//write your code here
}
}
需要明确的是,适配器模式的用意是将接口不同而功能相同或相近的两个接口加以转换,这里面包括适配器角色补充了一个源角色没有的方法,但是这个方法是目标接口需要的方法。
三、在什么情况下使用适配器模式
在以下情况下使用适配器模式
(1) 系统需要使用现有的类,而此类的接口不符合系统的需要。
(2) 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括在将来可能要引进的一些类一起工作。
(3) 在设计里,需要改变多个已有的子类的接口,可以使用对象的适配器模式。
四、适配器模式的例子
(1)需求:系统已有一个Car类表示普通汽车。现在需要为普通汽车增加GPS定位功能,也就是需要一个新的带有定位功能的汽车GPSCar类。
系统设计:
Car类是源角色。Car类的源代码。
/**
* 源角色
*/
public class Car {
private String name;
private double speed;
public Car() {
}
public Car(String name,double speed) {
this.name=name;
this.speed=speed;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSpeed() {
return speed;
}
public void setSpeed(double speed) {
this.speed = speed;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("车名:" + name + ",");
sb.append("速度:" + speed + "千米/小时");
return sb.toString();
}
}
需要定义一个接口GPS,该接口声明了getLocation()方法,用来确定汽车的位置。GPS是目标角色。
GPS接口的源代码。
/**
* 目标角色
*/
public interface GPS {
Point getLocation();
}
最后GPSCar类是需求的对象,是适配器角色。GPSCar类继承Car并实现GPS接口。在该类中首先实现getLocation()方法,用于实现确定汽车位置的功能。
GPSCar类的源代码。
/**
* 适配器角色
*/
public class GPSCar extends Car implements GPS {
@Override
public Point getLocation() {
Point point = new Point();
return point.setLocation(super.getSpeed(), super.getSpeed());
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
sb.append(",坐标:(" + getLocation().getX()+ "," + getLocation().getY() + ")");
return sb.toString();
}
}
Point类的源代码。
/**
* 坐标类
*/
public class Point {
private double x;
private double y;
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public Point setLocation(double x,double y){
Point point=new Point();
point.setX(x);
point.setY(y);
return point;
}
}
(2)需求:有一个“源”Person类是一个对象人,他拥有2种技能分别是说日语和说英语,而某个岗位(目标)Job需要你同时会说日语、英语、和法语,现在我们的任务就是要将人这个“源”适配的这个岗位中。
Person类源码。
/**
* 源角色
*/
public class Person {
private String name;
private String sex;
private int age;
public void speakJapanese(){
System.out.println("Japanese");
}
public void speakEnglish(){
System.out.println("English");
}
//省略属性get和set方法
}
目标接口Job源码。
/**
* 目标角色
*/
public interface Job {
public abstract void speakJapanese();
public abstract void speakEnglish();
public abstract void speakFrench();
}
适配器模式实现一:类的适配器模式
适配器AdapterClass源码。
/**
* 类的适配器模式,适配器角色
*/
public class AdapterClass extends Person implements Job{
@Override
public void speakFrench() {
System.out.println("French");
}
}
适配器模式实现二:对象的适配器模式
适配器AdapterObject源码。
/**
* 对象的适配器模式,适配器角色
*/
public class AdapterObject implements Job{
private Person person;
public AdapterObject(Person person) {
this.person=person;
}
@Override
public void speakJapanese() {
person.speakJapanese();
}
@Override
public void speakEnglish() {
person.speakEnglish();
}
@Override
public void speakFrench() {
System.out.println("French");
}
}