在办公室,我们有这个小小的脑筋急转弯:
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'}
答案 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#class
和Class#superclass
之类的方法将简单地跟随超类链,直到它找到第一个非隐藏类并返回它,而不是直接返回类/超类指针。
这使得Object#class
,Class#superclass
和Module#ancestors
等方法稍微复杂一些,因为它们必须跳过隐藏的类,但它简化了方法查找算法,这是最常见的算法之一任何面向对象系统中的重要性能瓶颈。