Ruby和一些脑筋急转弯

时间:2015-02-05 12:00:11

标签: ruby

在办公室,我们有这个小小的脑筋急转弯:

class Bicycle
    def spares
        {tire_size: 21}.merge(local_spares)
    end

    def local_spares
        {}
    end
end

class RoadBike < Bicycle
    def local_spares
       {tape_color: 'red'}
    end
end

roadbike = RoadBike.new

roadbike.spares

除非我们在控制台中运行整个代码,否则我们大多数人都没有得到roadbike.spares输出。我们对这种行为有不同的预感但是有人可以把它分解给我真正发生在这里的事情吗?

如果有人想知道,输出为{tire_size: 21, tape_color: 'red'}

2 个答案:

答案 0 :(得分:4)

很明显,RoadBike#spares(与Bicycle#spares相同,因为RoadBike并未覆盖此方法)内部调用RoadBike#local_spares ,将其返回值合并到{tire_size: 21}哈希并返回结果。 毫不奇怪。

答案 1 :(得分:3)

这称为方法覆盖RoadBike#local_spares方法会覆盖Bicycle#local_spares方法,因为RoadBike Bicycle继承了

当您向对象发送消息时,Ruby将尝试查找与要执行的消息同名的方法。它首先查看对象的类,然后是该类的超类,然后是那个类的超类,依此类推。

当您向RoadBike对象发送spares消息时,它将首先尝试(并失败)在spares中找到名为RoadBike的方法。然后它会查看它的超类(Bicycle)并成功。

方法的主体方法包含local_spares到接收者对象的消息发送。同样,Ruby尝试在对象的类中找到一个名为local_spares的方法(仍为RoadBike)并成功,因此它执行该方法。

这只是标准继承和方法覆盖。关于这一点,没有什么特别或令人惊讶的或“脑筋急转弯”。事实上,这几乎是继承和方法覆盖的整点:更专业的对象可以提供比更一般的父对象更专业的实现。


注意:方法查找算法实际上比这更复杂。

首先,如果没有更多超类会发生什么,并且仍未找到该方法?在这种情况下,Ruby会将消息method_missing发送到接收方并传递它尝试查找的方法的名称。只有在找不到method_missing方法时,Ruby raise才会NoMethodError

其次,有单例类,包括模块,前置模块和要求考虑的优化。所以,真的Ruby会先查看对象的单例类,然后才能查看类,然后查看超类等等。而“查看类”实际上意味着首先查看前置模块(按相反顺序),然​​后查看类本身,然后查看包含的模块(再次按相反顺序)。哦,这也必须以递归方式完成,因此对于每个前置模块,首先查看该模块的前置模块,然后是模块本身,然后是包含的模块,等等。

(哦,显然,Refinements会在此引起另一次皱纹。)

大多数Ruby实现通过引入内部存在的“隐藏类”(YARV称之为“虚拟类”)的概念,将“类”的内部概念与程序员的概念分开,从而大大简化了这种算法。实现但不向程序员公开。因此,例如,对象的单例类将是一个隐藏类,对象的类指针将简单地指向单例类,单例类的超类指针将指向实际对象的类。当您将一个模块包含到一个类中时,该实现将合成一个隐藏类(YARV称之为“包含类”),并将其作为类的超类插入,并使前一个超类成为隐藏类的超类。像Object#classClass#superclass之类的方法将简单地跟随超类链,直到它找到第一个非隐藏类并返回它,而不是直接返回类/超类指针。

这使得Object#classClass#superclassModule#ancestors等方法稍微复杂一些,因为它们必须跳过隐藏的类,但它简化了方法查找算法,这是最常见的算法之一任何面向对象系统中的重要性能瓶颈。