9.2 动态绑定和id类型

第4章简略谈到了id数据类型,并指出这是一种通用的对象类型。也就是,它可以用来存储属于任何类的对象。当以这种方式在一个变量中存储不同类型的对象时,在程序的执行期间,这种数据类型的真正优势就出现了。研究代码清单9-2和它对应的输出。

代码清单9-2


//Illustrate Dynamic Typing and Binding

import“Fraction.h”

import“Complex.h”

int main(int argc, char*argv[])

{

NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];

id dataValue;

Fraction*f1=[[Fraction alloc]init];

Complex*c1=[[Complex alloc]init];

[f1 setTo:2 over:5];

[c1 setReal:10.0 andImaginary:2.5];

//first dataValue gets a fraction

dataValue=f1;

[dataValue print];

//now dataValue gets a complex number

dataValue=c1;

[dataValue print];

[c1 release];

[f1 release];

[pool drain];

return 0;

}


代码清单9-2输出


2/5

10+2.5i


变量dataValue被声明为id对象类型。因此,dataValue可用来保存程序中任何类型的对象。务必注意,声明中并没有使用星号:


id dataValue;


Fraction f1被设为2/5,并且Complex数C1被设为(10+2.5i)。赋值语句


dataValue=f1;


将Fraction f1存储到dataValue中。现在,使用datavalue可以做什么呢?其实,可以对dataValue调用可以用于Fraction对象的任何方法,即使dataValue是一个id类型,不是Fraction。然而,如果dataValue可以存储任何类型的对象,那么系统如何知道该调用哪一个方法呢?就是说,当系统遇到消息表达式


[dataValue print];


时,如何知道该调用哪个print方法呢?你知道,Fraction和Complex类中都定义有print方法。

前面提到,答案就在于以下事实:Objective-C系统总是跟踪对象所属的类。答案同样存在于动态类型和动态绑定的概念之中,就是说,先判定对象所属的类,并因此确定运行时而不是编译时需要动态调用的方法。

因此,在程序执行期间,当系统准备将print消息发送给dataValue时,它首先检查dataValue中存储的对象所属的类。在代码清单9-2的第一种情况中,这个变量保存一个Fraction,因此使用在Fraction类中定义的print方法。这一点使用程序的输出即可验证。

在第二种情况下,发生了同样的事情。首先,Complex数c1被指派给dataValue。接下来,执行以下消息表达式


[dataValue print];


这次,因为dataValue包含属于complex的对象,所以选择该类相应的print方法来执行。

这是一个简单的例子,但是我认为你可以将这个概念推广到更复杂的应用。结合多态、动态绑定和动态类型时,你就能轻易地编写出可向来自不同类的对象发送相同消息的代码。

例如,考虑可用来在屏幕上绘制图形对象的draw方法。你可能为每个图形对象(比如文本、圆、矩形、窗口,等等)定义了不同的draw方法。如果要绘制的特定对象存储在一个名为currentObject的id变量中,仅仅通过发送draw消息就可以在屏幕上进行绘制,如下所示:


[currentObject draw];


甚至可以首先测试它,确保存储在currentObject中的对象的确响应draw方法。在本章后面将学到如何这样做。