图3.1与图3.3的设计属于类的适配器,通过子类进行适配。在类的适配器中,新的适配类实现了需要的接口,并继承自现有的类。当你需要适配的一组方法并非被定义在接口中时,这种方式就不奏效了。此时就可以创建一个对象适配器,它使用了委派而非继承。图3.4展现了这样的设计(可以对比之前的类图)。

图3.4 通过继承你所需要的类,可以创建一个对象适配器,利用现有类的实例对象,满足所需方法
图3.4中的NewClass类是适配器的一个例子。该类的实例同时也是RequiredClass类的实例。换言之,NewClass类满足了客户端的需要。NewClass类通过使用ExistingClass实例对象,可以将ExistingClass类适配为符合客户端的需要。
一个更为具体的例子是仿真程序包,它直接与Skyrocket类协作,而没有指定接口去定义仿真系统需要的行为。图3.5展示了该类的设计。
Skyrocket类使用了火箭的基本模型。例如,类假设火箭要在燃料烧尽之后才会坠毁。假设你希望添加一些更为复杂的物理模型,而该模型由Oozinoz系统中的PhysicalRocket类使用。为了适配PhysicalRocket类以满足仿真系统,需要创建OozinozSkyrocket类作为对象适配器,它继承自Skyrocket,同时使用了PhysicalRocket对象,如图3.6所示。

图3.5 在这个替代设计中,com.oozinoz.simulation包并没有指定对火箭进行建模所需的接口

图3.6 完成该类图使其能够体现对象适配器的设计,将现有的类转换,以满足拥有Skyrocket
对象的客户端需求
作为一个对象适配器,OozinozSkyrocket类继承自Skyrocket,而非PhysicalRocket。当仿真程序客户端需要Skyrocket对象时,可以令OozinozSkyrocket对象代替它。通过让simTime变量成为受保护的,Skyrocket类就能够支持它的子类化。
挑战3.3
完成图3.6所示的类图,使得OozinozRocket对象支持Skyrocket对象。
答案参见第300页
OozinozSkyrocket类的代码如下所示:
Packagecom.oozinoz.firework;
Importcom.oozinoz.simulation.*;
public class OozinozSkyrocket extends Skyrocket {
privatePhysicalRocket rocket;
publicOozinozSkyrocket(PhysicalRocket r) {
super(
r.getMass(0),
r.getThrust(0),
r.getBurnTime());
rocket = r;
}
public double getMass() {
returnrocket.getMass(simTime);
}
public double getThrust() {
returnrocket.getThrust(simTime);
}
}
OozinozSkyrocket类可以为需要Skyrocket对象的仿真程序包提供OozinozSkyrocket类型的对象。总体而言,对象适配器在一定程度上解决了这一问题,即将对象适配为没有明确定义的接口。
与实现RocketSim接口相比,运用了对象适配器的Skyrocket类存在更大的风险。但我们却不应该吹毛求疵,因为它仅仅是没有将方法标记为final,使得我们不能防止子类去重写它们。
挑战3.4
分析为何OozinozSkyrocket类使用的对象适配器设计要比类的适配器方式更加脆弱。
答案参见第301页