Ruby singleton_class的ancestors结果与《The Well-Grounded Rubyist》预期不符
为什么给对象单例类include模块后,祖先链没有重复出现该模块?
这个问题其实涉及到Ruby中模块包含的核心特性——自动去重机制,以及单例类的继承链逻辑,咱们一步步理清楚:
1. Ruby include的核心规则:避免重复包含
当你调用类(包括单例类)的include方法添加模块时,Ruby会先检查这个模块是否已经存在于当前类的整个祖先链中(包括父类、祖父类的所有祖先)。如果已经存在,Ruby会直接跳过这次操作,不会重复把模块加入祖先链。
2. 单例类的继承关系
对象c的单例类(#<Class:#<C:0x...>>)的父类是它所属的类C,所以默认情况下,单例类的祖先链是:
单例类 → C → M → Object → Kernel → BasicObject
这就是你第一次运行代码得到的结果。
3. 给单例类include M时发生了什么?
当你执行class << c; include M; end时,Ruby会检查单例类的祖先链:此时通过父类C,已经能在祖先链中找到M了,所以Ruby不会再把M重复添加到单例类的直接祖先位置。最终的祖先链自然和之前一致,不会出现两个M。
4. 为什么书中的预期和实际不符?
《The Well-Grounded Rubyist》的这个预期,大概率是基于更早版本的Ruby(比如Ruby 1.8及以前)——当时的Ruby还没有实现模块include的自动去重逻辑,重复include同一个模块会在祖先链中多次出现。而现代Ruby(2.0及以后)都加入了这个去重机制,目的是避免方法查找时的冗余,同时符合开发者的直觉(重复包含同一个模块不应该改变祖先链)。
你可以做个小实验验证这个逻辑:换一个不在C祖先链里的模块,比如新增模块N,再给单例类include它:
module M end module N end class C include M end c = C.new class << c include N p ancestors end
输出会是:
[#<Class:#<C:0x000055b6b069c5f0>>, N, C, M, Object, Kernel, BasicObject]
这里N被成功添加到单例类的直接祖先位置,因为它之前不在整个祖先链里。
内容的提问来源于stack exchange,提问作者Shekhar Patil
相关产品推荐
相关产品推荐

