在李维的《inside VCL》中详细描述了VCL中TObject的地位。是的Borland的工程师们有心将Delphi语言做成pure language。所以你几乎可以看到TObject的所有pure pascal的实现。
更重要的,你应该会发现。Delphi将代码的所有运行机制都暴露在我们面前。这也就是Delphi的TObject和C++中的Object以及C#的Object有很大不同的地方。
Delphi将整个语言的机制都在TObject上实现了。消息机制、接口机制、面向对象机制(多态)等等你都可以从TObject的实现代码中看到运行的全部流程。
不管TObject如何优秀,可是TObject正如Borland一样,在它身上总是看到受制于MS的影子。特别是接口机制。我有两点认为TObject的设计不好。
-
对象指针和接口指针并不是同一个地址
-
对象和接口不可以混用
先说说我第一个不满意的地方。说是第一,并不表示是“最”的意思。只是代表“最先”的意思。
在Delphi的TObject中,类在实现了接口之后,其创建的实例中,对象指针和接口指针并不是同一个地址。这个可能大家没有注意到,但是主要通过简单的例子(取两个指针的地址)就可以发现这个现象。
你可能不会在意这个差异。是的,其实也没什么,不是同一个地址,代码照样可以工作。在对象的内存实例模型中,接口的指针直接存储在属性后面(如果存在继承,可能会出现交错的)。因为TObject设计的时候,两个地址不在一起,那么就存在两个问题:
-
如果从对象转换到接口
-
如何从接口转换到对象
稍微知道对象模型的人,就应该知道对象调用虚拟方法表的过程。只有对象指针可以指向VMT。另外,在接口访问方法的实现代码的时候,也需要传入对象指针。这是为什么?我们知道,一个对象的方法中,有一个隐含的指针,我们一般称它为Self指针。只要不是class function,那么这个Self指针就是指向对象实例的指针。所以在代码的调用过程中,必然有这个转换。
TObject提供的AS操作,可以完成第1个转换,但是可惜的是,TObject并没有提供一个公开的方法来负责第2个转换。这是我认为TObject在这方面设计不好的原因。(JCL代码库中有第2个转换的代码)
第二个我不满意的地方就是TObject在生存期管理上,没有做到和接口一致。我相信这也是许多使用Delphi接口的同志们一致的想法。虽然我们现在看到Java和C#都已经做到这点,可不能不指出的是,在Delphi中,对象和接口不可以混用!
我并不是奢求TObject的生存期可以自管理。毕竟,我还是习惯了“谁创建谁释放”的规则。可是一旦到了接口和对象混合使用的时候,就发生了问题。
这虽然可以解释为接口是Delphi为了迎合COM而后加上的。我们今天却应该来设计一下一种规则,来解决接口混用的问题。我认为可以有一种简单的方式:TObject在Free的时候,发现其接口引用计数不为0的时候,不会Destroy。
目前我们无法做到这点,这是因为Destroy是Delphi默认做的,也就是说,只要调用了Free方法,Destroy必然发生。我们无法完美地改变这个现实。也正因为此,我才认为必须在设计TObject的时候,将这个考虑进去!
OK。尝试重新审视VCL中的各个基础类,其实有点大胆。所以用“苛评”这个词来做标题,表明这完全是我的苛刻,VCL的设计是非常棒的。不过也算是我使用6年Delphi的一点回报吧。以后还会继续苛评其他类。希望大家继续关注。