对象导论:伴随多态的可互换对象

 1211浏览

在处理类型的层次结构时,经常想把一个对象不当作它所属的特定类型来对待,而是将其当做其基类的对象来对待。这使得人们可以编写出不依赖于特定类型的代码。在“几何形”的例子中,方法操作的都是泛化的形状,而不关心它们是圆形、正方形、三角形还是其他什么尚未定义的形状。所有的几何形状都可以被绘制、擦除和移动,所以这些方法都是直接对一个几何对象发送消息;它们不用担心对象将如何处理消息。

这样的代码是不会受添加新类型影响,而且添加新类型是扩展一个面向对象程序以便处理新情况的最常用方式。例如,可以从“几何形”中导出一个新的子类型“五角形”,而并不需要修改处理泛化几何形状的方法。通过导出新的子类型而轻松扩展设计的能力是对改动进行封装的基本方式之一。这种能力可以极大地改善我们的设计,同时也降低软件维护的代价。

但是,在试图将导出类型的对象当做其泛化基类型对象来看待时,仍然存在一个问题。如果某个方法要让泛化几何形状绘制自己、让泛化交通工具行驶,或者让泛化的鸟类移动,那么编译器在编译时是不可能知道应该执行哪一段代码的。这就是关键所在:当发送这样的消息时,程序员并不想知道哪一段代码将被执行;绘图方法可以被等同地应用于圆形、正方形、三角形,而对象会依据自身的具体类型来执行恰当的代码。

如果不需要知道那一段代码会被执行,那么当添加新的子类型时,不需要更改调用它的方法,它就能够执行不同的代码。因此,编译器无法精确地了解哪一段代码将会被执行,那么它该怎么办呢?例如,在下面的图中,BirdController对象仅仅处理泛化的Bird对象,而不了解他们的确切类型。从BirdController的角度看,这么做非常方便,因为不需要编写特别的代码来判定要处理的Bird对象的确切类型或其行为。当move()方法被调用时,即使忽略Bird的具体类型,也会产生正确的行为,那么,这是如何发生的呢?

001.jpg

这个问题的答案,也是面向对象程序设计的最重要的妙诀:编译器不可能产生传统意义上的函数调用。一个非面向对象编程的编译器产生的函数调用会引起所谓的前期绑定,这个术语你可能以前从未听说过,可能从未想过函数调用的其他方式。这么做意味着编译器将产生对一个具体函数名字的调用,而运行时将这个调用解析到将要被执行的代码的绝对地址。然而在OOP中,程序直到运行时才能够确定代码的地址,所以当消息发送到一个泛化对象时,必须采用其他的机制。

为了解决这个问题,面向对象程序设计语言使用了后期绑定的概念。当向对象发送消息时,被调用的代码直到运行时才能确定。编译器确保被调用的方法的存在,并对调用参数和返回值执行类型检查,但是并不知道将被执行的确切代码。

在某些语言中,必须明确的声明希望某个方法具备后期绑定属性所带来的灵活性。在这些语言中,方法在默认情况下不是动态绑定的。而在Java中,动态绑定是默认行为,不需要添加额外的关键字来实现多态。


流行热度:超过1211次围观
生产日期:2016-04-29 21:30:47
上次围观:2016-09-18 00:16:57
转载时必须以链接形式注明原始出处及本声明。