动态分派

Dynamic Dispatch.

考虑类 Point:

ruby
class Point
    attr_accessor :x, :y    # define getter/setter for fields x, y
    def initialize(x, y)
        @x = x
        @y = y
    end
    def disFromOrigin
        Math.sqrt(@x * @x + @y * @y)
    end
    def disFromOrigin2
        Math.sqrt(x * x + y * y)    # call getters of x, y instead of directly accessing them
    end
end

接下来我们来考虑类 Point 的一个有趣的子类 PolarPoint:它内部采用弧度表示法而非坐标表示法来表示某个点,其余方法与父类等价。

ruby
class PolarPoint < Point
    def initialize(r, theta)
        @r = r
        @theta = theta
    end
    def x
        @r * Math.cos(@theta)
    end
    def y
        @r * Math.sin(@theta)
    end
    def x= a
        b = y    # avoid mutiple calls to method y
        @theta = Math.atan(b / a)
        @r = Math.sqrt(a*a + b*b)
        self
    end
    def y= b
        a = y
        @theta = Math.atan(b / a)
        @r = Math.sqrt(a*a + b*b)
        self
    end
    def distFromOrigin
        @r
    end
    # No need to override distFromOrigin2, it already works!
end

注意,在 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 方法的定义:

ruby
def distFromOrigin2
    Math.sqrt(x * x + y * y)
end

与 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。虚函数的定义是:通过基类指针调用虚函数时,若指针指向基类对象,则被调用的是基类的虚函数;若指向的是派生类对象,则被调用的是派生类的虚函数。

cpp
#include <iostream>
#include <string>

using namespace std;

class Base {
    public:
        virtual int x() { return 1; }
        virtual int y() { return 1; }
        void put_ans() {
            cout << x() + y() << endl;
        }
};

class Derived : public Base {
    public:
        int x() { return 2; }
        int y() { return 2; }
}

int main() {
    Base a;
    a.put_ans();
    Derived b;
    b.put_ans();

    return 0;
}

以上这个代码的运行结果为 2\n4;也就是说,派生类 Derived 类由基类中继承的函数 put_ans() 调用的是派生类中被 override 的函数 x() 与 y(),实现了调用同样的方法 put_ans() 在基类与派生类中的不同表现。

注意,在 C++ 中 dynamic dispatch 所依赖的函数需要在基类中添加关键字 virtual 声明其为虚函数;在上例中,若 x() 与 y() 函数未声明为虚函数,代码的运行结果将变为 2\n2。