客户端(Client)就是需要调用我们代码的对象。通常,在代码已经存在的情况下编写客户端代码,开发人员可以采取模拟客户端的方式调用我们提供的接口对象。然而,客户端代码也可能与你的代码单独进行开发。例如,设计的火箭仿真程序会使用你所提供的火箭信息,但是对于火箭应该拥有怎样的行为,仿真器也会拥有自己的定义。在这样的情况下,会发现现有的类虽然提供了客户端需要的服务,却被定义为不同的方法名。这时,我们就需要运用适配器(Adapter)模式。
适配器模式的意图在于,使用不同接口的类提供的服务为客户端提供它所期望的接口。
接口适配
当我们需要适配现有代码时,可能会发现客户端开发人员已经事先考虑到这种情形。开发人员为客户端使用的服务提供了接口,如图3.1所示。RequiredInterface接口声明了Client类所要调用的requiredMethod()方法。在ExistingClass类中,则定义了usefulMethod()方法,它是Client类需要的实现。若要对ExistingClass类进行适配,满足客户端对象的需要,就可以编写一个继承自ExistingClass,并同时实现RequiredInterface接口的类,通过重写requiredMethod()方法将客户端的请求委派给usefulMethod()方法。

图3.1 当客户端代码的开发人员在考虑如何满足客户端需求时,可以考虑通过适配现有代码来
实现接口
图3.1中的NewClass类就是适配器模式的一个例子。该类的实例同时也是RequiredInterface的实例。换言之,NewClass类满足了客户端的需求。
为了更具体地说明,假定我们为Oozinoz公司开发仿真火箭的飞行与实时控制程序。在仿真功能包中,包含了一个事件仿真器,它能够探测到多个火箭启动时产生的影响。这是一个指定了火箭行为的接口,图3.2展示了该功能包。

图3.2 仿真功能包清晰地定义了火箭仿真飞行的需求
假设在Oozinoz公司,需要将PhysicalRocket类放到仿真功能中。该类提供的方法类似仿真器需要的功能行为。此时,就可以运用适配器模式,创建PhysicalRocket的子类,并同时实现RocketSim接口。图3.3展示了这一设计的部分内容。

图3.3 本图展示了设计完成后,类的设计对Rocket类进行适配,以满足RocketSim接口的需要
PhysicalRocket类拥有仿真器需要的信息,但它的方法并不完全匹配RocketSim接口中声明的仿真功能。主要的差异在于仿真器保留了一个内部时钟,它不时地会调用setSimTime()方法更新仿真对象。若要适配PhysicalRocket类以满足仿真器的需求,OozinozRocket对象可以维持一个实例变量,用来传递PhysicalRocket类需要的方法。
挑战3.1
完成图3.3中的类图,展现OozinozRocket类的设计,它将让PhysicalRocket对象作为RocketSim对象参与到仿真行为中。假定你无法修改RocketSim与PhysicalRocket类。
答案参见第298页
PhysicalRocket类的代码稍显复杂,因为它包含了Oozinoz用来模拟火箭行为的物理逻辑。然而,这正是我们希望重用的部分。OozinozRocket适配器类只是简单地将调用转为使用超类拥有的方法。这个新的子类的代码如下所示:
packagecom.oozinoz.firework;
importcom.oozinoz.simulation.*;
public class OozinozRocket
extendsPhysicalRocket implements RocketSim {
private double time;
publicOozinozRocket(
doubleburnArea, double burnRate,
doublefuelMass, double totalMass) {
super(burnArea, burnRate, fuelMass, totalMass);
}
public double getMass() {
// 挑战!
}
public double getThrust() {
// 挑战!
}
public void setSimTime(double time) {
this.time = time;
}
}
挑战3.2
完成包括getMass()与getThrust()方法的OozinozRocket类的代码。
答案参见第299页
当客户端在接口中定义了它所期待的行为时,就可以运用适配器模式,提供一个实现该接口的类,并同时令其成为现有类的接口。倘若没有定义客户端期待的接口,也可以运用适配器模式,但必须使用“对象适配器”。