例如某游戏系统中有接口Being/生命,定义了接口eat()、move ()等抽象方法,而实现类有狗/Dog等。有一些Being的子类型,特别是实现较复杂的子类型,可以在第三方包或其他开发小组中找到,如(已经存在的)鸟/Bird类、可以作为Being子类型的汽车/Car、机器人/Robot……(其他一些你可以想象出来的东西)。这时存在一个明显的问题,Bird、Car等拥有的对应的方法头/接口,与Being所定义的对应物不完全相同,而Client希望将Bird、Car、Robot……改装或伪装成为Being的子类型以便统一处理。
此时,需要问一句:即使Bird与Being的接口完全一致,又如何?Bird与Being之间没有使用extends或implements关联,所以,解决接口不兼容问题或[GoF]所称的意图即『把一个类的接口变换成客户希望的另一种接口,从而使原本因接口不匹配而不能一起工作的那些类能够一起工作』只是表象,本质是将外界的类变成Being的子类型。
1. 适配器的两种形式
适配器模式包含一下三个角色:
Target(目标):如Being,目标类是一个父类型,它是“被适配类”希望变成的样子。
Adaptee(被适配者):如Bird,Car、Robot。它们拥有已经存在的接口,通常与目标不一致。(即使接口一致也枉然)。
Adapter(适配器类):作为一个转换器或包装器,如MyBird,是目标的子类型。
将“已经存在的类”再次包装一下,例如以适配器MyBird包装被适配者Bird。MyBird一定是Being这个目标类的子类型。而适配器MyBird与被包装的Bird之间,可以是Is-A的继承关系,也可以Has-A的组合关系。
//例程 4-4 接口不兼容
package chap4.adapterP;
public interface Being{
public abstract void eat();
public abstract void move();
}
package chap4.adapterP; //来自第三方的或不可修改的类。方便起见,放在本包中。
import static yqj2065.util.Print.*;
public class Bird{
public void eat(){ pln("Bird.eat()"); }
public void fly(){ pln("Bird.fly()"); }
}
package chap4.adapterP;//来自第三方的或不可修改的类。方便起见,放在本包中。
public interface Robot{
public void battery();//电池充电
public void move();
}
//Client
public static void main(String args[]) {
Being b= new MyBird(); //统一处理.
b.eat();
b.move();
}
复制代码
Is-A型适配器,在[GoF]中叫做类适配器,适配器类Is-A被适配类。由于MyBird将作为Being和Bird的子类型,Java语法方面有一些限制:要求Bird类不得为final类——否则无法继承、Bird与Being不得同时为类——Java不支持类的多继承。Is-A型适配器,用于被适配的类Bird是一个具体类的情形。如果Bird有各种子类如麻雀、鸽子等,MyBird将与麻雀、鸽子同等地位,Is-A型适配器无法适配麻雀、鸽子,这时需要使用Has-A型适配器。
Has-A型适配器,在[GoF]中叫做对象适配器。由于采用委派关系,被适配的类如Robot可以是一个Java接口或抽象类,Robot拥有自己的类层次。
//例程 4-5 适配
package chap4.adapterP;
public class MyBird extends Bird implements Being { // Is-A型适配器
//@Override public void eat() {super.eat();}
@Override public void move() {
super.fly();
}
}
package chap4.adapterP;
public class RobotWrapper implements Being{ // Has-A型适配器
private Robot r;
public RobotWrapper(){
//任一创建对象的模式
}
@Override public void eat() {
r.battery();
}
@Override public void move() {
r.move();
}
}
复制代码
★(适配器)将外界给定的一个类,纳入系统的某一类层次。