Dynamic Dispatch.
考虑类 Point:
1 | class Point |
接下来我们来考虑类 Point 的一个有趣的子类 PolarPoint:它内部采用弧度表示法而非坐标表示法来表示某个点,其余方法与父类等价。
1 | class PolarPoint < Point |
注意,在 initialize 中没有使用 super 调用父类中的同名初始化方法,所以实际上类 PolarPoint 是没有定义实例变量 @x 与 @y 的;但它 override 了方法 x, x=, y 与 y=,使得用户无法将其与类 Point 进行区别。而在 Java/C++ 中,子类默认将继承父类中的实例变量,你可以选择是否使用它们。
The key point of this example is that the class does not override distFromOrigin2, but the inherited method works correctly.
这是为什么呢?我们来看 PolarPoint 类从父类中继承的 DistFromOrigin2 方法的定义:
1 | def distFromOrigin2 |
与 distFromOrigin 方法不同,distFromOrigin2 方法使用调用其他方法 (self.x() 与 self.y()) 的结果来计算距离而不是直接使用实例变量 @x 与 @y 中储存的值。
然而,PolarPoint 类 override 了方法 x 与 y;这使得继承自 Point 类的方法 DistFromOrigin2 的表现 (behavior) 发生了改变。虽然是相同的代码,但在子类与父类中却分别调用了不同的方法。
这一 semantic 被称之为 dynamic dispatch。又名 late binding 或 virtual method call。这是一个十分 OOP 的 semantic —— 它涉及到环境中 self 的特殊处理。
我们看到一个熟悉的名字 virtual method:事实上,C++ 中的虚函数采用的也是该 semantics。虚函数的定义是:通过基类指针调用虚函数时,若指针指向基类对象,则被调用的是基类的虚函数;若指向的是派生类对象,则被调用的是派生类的虚函数。
1 |
|
以上这个代码的运行结果为 2\n4;也就是说,派生类 Derived 类由基类中继承的函数 put_ans() 调用的是派生类中被 override 的函数 x() 与 y(),实现了调用同样的方法 put_ans() 在基类与派生类中的不同表现。
注意,在 C++ 中 dynamic dispatch 所依赖的函数需要在基类中添加关键字 virtual 声明其为虚函数;在上例中,若 x() 与 y() 函数未声明为虚函数,代码的运行结果将变为 2\n2。