产生正确的行为

来源:互联网 发布:四灵进阶数据 编辑:程序博客网 时间:2024/04/29 05:17

一旦知道Java 中所有方法都是通过动态绑定实现多态这个事实之后,我们就可以编写只与

基类打交道的程序代码了,并且这些代码对所有的导出类都可以正确运行。或者换种说法,

发送消息给某个对象,让该对象去断定应该做什么事。

 

面向对象程序设计中,有一个最经典的“几何形状(shape)”例子。因为它很容易被可

视化,所以经常用到;但不幸的是,它可能使初学者认为面向对象程序设计仅适用于图形

化程序设计,实际当然不是这种情形了。

 

在“几何形状”这个例子中,包含一个 Shape 基类和多个导出类,如:Circle,Square,

Triangle 等。这个例子之所以好用,是因为我们可以说“圆是一种形状”,这种说法也很

容易被理解。下面的继承图展示了它们之间的关系:

向上转型可以像下面这条语句这么简单:

 

Shapes =   new Circle();

 

这里,创建了一个Circle 对象,并把得到的引用立即赋值给 Shape,这样做看似错误(将

一种类型赋值给另一类型);但实际上是没问题的,因为通过继承, Circle 就是一种

Shape。因此,编译器认可这条语句,也就不会产生错误信息。

 

假设我们调用某个基类方法(已被导出类所重载):

 

s.draw();

 

同样地,我们可能会认为调用的是 shapedraw(),因为这毕竟是一个shape 引用,

那么编译器是怎样知道去做其他的事情呢?由于后期绑定(多态),程序还是正确调用了

Circle.draw( )方法。

 

下面的例子稍微有所不同:

 

//:c07:Shapes.java

// Polymorphismin Java.

import com.bruceeckel.simpletest.*;

import java.util.*;

 

class Shape {

void draw() {}

void erase() {}

}

 

class Circle extends Shape {

void draw() {


 

 

 

    System.out.println("Circle.draw()");

  }

void erase() {

    System.out.println("Circle.erase()");

  }

}

 

class Square extends Shape {

void draw() {

    System.out.println("Square.draw()");

  }

void erase() {

    System.out.println("Square.erase()");

  }

}

 

class Triangle extends Shape {

void draw() {

    System.out.println("Triangle.draw()");

  }

void erase() {

    System.out.println("Triangle.erase()");

  }

}

 

// A"factory" that randomly creates shapes:

class RandomShapeGenerator {

private Random rand = new Random();

public Shape next() {

switch(rand.nextInt(3)) {

default:

case 0:   return   new Circle();

case 1:   return   new Square();

case 2:   return   new Triangle();

    }

  }

}

 

public   class Shapes {

private      static Test monitor =new Test();

private   static RandomShapeGenerator gen =

new RandomShapeGenerator();

public  static    void main(String[] args) {

    Shape[] s = new Shape[9];


 

 

 

// Fill up thearray with shapes:

for(int i = 0; i < s.length; i++)

      s[i] = gen.next();

// Makepolymorphic method calls:

for(int i = 0; i < s.length; i++)

      s[i].draw();

    monitor.expect(new Object[] {

new TestExpression("%%(Circle|Square|Triangle)"

        +  "\\.draw\\(\\)", s.length)

    });

  }

}   ///:~

 

Shape 基类为自它那里继承而来的所有导出类,建立了一个通用接口——也就是说,所有

形状都可以描绘和擦除。导出类重载了这些定义,以便为每种特殊类型的几何形状提供独特

的行为。

 

RandomShapeGenerator是一种“工厂(factory)”,在我们每次调用 next()方法

时,它可以为随机选择的 shape 对象产生一个引用。请注意向上转型是在 return 语句里

发生的。每个return 语句取得一个指向某个 Circle、Square 或者 Triangle 的句柄,并将

其以 Shape 类型从 next()方法中发送出去。所以无论我们在什么时候调用 next()方

法时,是绝对没有可能知道它所获的具体类型到底是什么,因为我们总是只能获得一个通用

的 Shape 引用。

 

main()包含了 Shape 句柄的一个数组,通过调用 RandomShapeGenerator.next( )来

填入数据。此时,我们只知道自己拥有一些 Shape,不会知道除此之外的更具体情况(编

译器一样不知)。然而,当我们遍历这个数组,并为每个数组元素调用 draw()方法时,与

各类型有关的专属行为竟会神奇般地正确发生,我们可以从运行该程序时,产生的输出结果

中发现这一点。

 

随机选择几何形状是为了让大家理解:在编译期间,编译器不需要获得任何特殊的信息,就

能进行正确的调用。对 draw()方法的所有调用都是通过动态绑定进行的。