构建器内部的多形性方法的行为

来源:互联网 发布:维修公司网络推广 编辑:程序博客网 时间:2024/05/21 01:48

构建器调用的分级结构(顺序)为我们带来了一个有趣的问题,或者说让我们进入了一种进退两难的局面。
若当前位于一个构建器的内部,同时调用准备构建的那个对象的一个动态绑定方法,那么会出现什么情况
呢?在原始的方法内部,我们完全可以想象会发生什么——动态绑定的调用会在运行期间进行解析,因为对
象不知道它到底从属于方法所在的那个类,还是从属于从它衍生出来的某些类。为保持一致性,大家也许会
认为这应该在构建器内部发生。
但实际情况并非完全如此。若调用构建器内部一个动态绑定的方法,会使用那个方法被覆盖的定义。然而,
产生的效果可能并不如我们所愿,而且可能造成一些难于发现的程序错误。
从概念上讲,构建器的职责是让对象实际进入存在状态。在任何构建器内部,整个对象可能只是得到部分组
织——我们只知道基础类对象已得到初始化,但却不知道哪些类已经继承。然而,一个动态绑定的方法调用
却会在分级结构里“向前”或者“向外”前进。它调用位于衍生类里的一个方法。如果在构建器内部做这件
事情,那么对于调用的方法,它要操纵的成员可能尚未得到正确的初始化——这显然不是我们所希望的。
通过观察下面这个例子,这个问题便会昭然若揭:

//: PolyConstructors.java// Constructors and polymorphism// don't produce what you might expect.abstract class Glyph {     abstract void draw();     Glyph() {         System.out.println("Glyph() before draw()");         draw();          System.out.println("Glyph() after draw()");     }}class RoundGlyph extends Glyph {     int radius = 1;    RoundGlyph(int r) {     radius = r;     System.out.println(     "RoundGlyph.RoundGlyph(), radius = "     + radius); }     void draw() {      System.out.println("RoundGlyph.draw(), radius = " + radius); }}public class PolyConstructors {     public static void main(String[] args) {     new RoundGlyph(5); }} ///:~

在Glyph 中,draw()方法是“抽象的”(abstract),所以它可以被其他方法覆盖。事实上,我们在
RoundGlyph 中不得不对其进行覆盖。但 Glyph 构建器会调用这个方法,而且调用会在RoundGlyph.draw()中
止,这看起来似乎是有意的。但请看看输出结果:

Glyph() before draw()RoundGlyph.draw(), radius = 0Glyph() after draw()RoundGlyph.RoundGlyph(), radius = 5

当Glyph 的构建器调用 draw()时,radius 的值甚至不是默认的初始值1,而是 0。这可能是由于一个点号或
者屏幕上根本什么都没有画而造成的。这样就不得不开始查找程序中的错误,试着找出程序不能工作的原
因。
前一节讲述的初始化顺序并不十分完整,而那是解决问题的关键所在。初始化的实际过程是这样的:
(1) 在采取其他任何操作之前,为对象分配的存储空间初始化成二进制零。
(2) 就象前面叙述的那样,调用基础类构建器。此时,被覆盖的draw()方法会得到调用(的确是在
RoundGlyph 构建器调用之前),此时会发现 radius 的值为 0,这是由于步骤(1)造成的。
(3) 按照原先声明的顺序调用成员初始化代码。
(4) 调用衍生类构建器的主体。
采取这些操作要求有一个前提,那就是所有东西都至少要初始化成零(或者某些特殊数据类型与“零”等价
的值),而不是仅仅留作垃圾。其中包括通过“合成”技术嵌入一个类内部的对象句柄。如果假若忘记初始
化那个句柄,就会在运行期间出现违例事件。其他所有东西都会变成零,这在观看结果时通常是一个严重的
警告信号。
在另一方面,应对这个程序的结果提高警惕。从逻辑的角度说,我们似乎已进行了无懈可击的设计,所以它
的错误行为令人非常不可思议。而且没有从编译器那里收到任何报错信息(C++在这种情况下会表现出更合理的行为)。象这样的错误会很轻易地被人忽略,而且要花很长的时间才能找出。
因此,设计构建器时一个特别有效的规则是:用尽可能简单的方法使对象进入就绪状态;如果可能,避免调
用任何方法。在构建器内唯一能够安全调用的是在基础类中具有final 属性的那些方法(也适用于private
方法,它们自动具有final 属性)。
这些方法不能被覆盖,所以不会出现上述潜在的问题。

原创粉丝点击