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方法。在本章后面将学到如何这样做。