Ruby 小试验,关于 Ruby 类的祖先链

kevin__liu · 2012年06月02日 · 最后由 zlx_star 回复于 2012年06月08日 · 4665 次阅读
module M; end
module M1; end
module M2; end

class MyClass
  include M
  include M1
end

class MySubClass < MyClass
  include M
  include M2
end

obj = MySubClass.new
puts obj.class.ancestors()
puts "\n\n\n"

puts BasicObject.superclass==nil

def my_ancestors(current_class)
  a = []
  a.push current_class
  sc = current_class.superclass
  until sc==nil
    a.push sc
    sc = sc.superclass
  end
  a
end

puts my_ancestors(obj.class)

运行结果如下: MySubClass M2 MyClass M1 M Object Kernel BasicObject

true MySubClass MyClass Object BasicObject

注意:模块相当于在导入模块的类与 superclass 之间加了一个匿名类。祖先链中如果两个类导入了相同的模块(M)的话,这个匿名类只是相当于加在了最祖先的类上层。 可能我解析的有错误,对这方面有研究的同学可以给个正解。

匿名 #2 2012年06月02日

把 module M 改下

module M
  def self.included other
    puts "p:#{other} include M"
  end
end

再运行你的代码结果是

p:MyClass include M p:MySubClass include M MySubClass M2 MyClass M1 M Object Kernel BasicObject

true MySubClass MyClass Object BasicObject 无需解释了吧

匿名 #3 2012年06月02日

好像还是有点问题,included 这个函数可能只是在 include 方法调用时回调。不过不管第二次是不是真正的混入了,但从语言逻辑上来说是认为混入了两次

一个类的单件类的超类是这个类的超类的单件类。也就是说单件类也是有继承关系的,相同的模块当然不能被继承两次了。。。。

#1 楼 @kevin__liu

首先,对于对楼主的探索精神,深表佩服!! 我其实也喜欢没事儿研究这个。

我记得是双飞燕上面写的很清楚,所有被混入的类,会被加入一个数组,再次混入,将被忽略。

以你的示例来说,在 MySubClass 内混入 M 的那个语句,完全被忽略的。

#5 楼 @zw963 我很佩服你,在社区里,也关注到你了,你比我更有探索精神。

这些语言逻辑可能要理论加实际才能记得住,我主要是学习过程中做点例子,加深一下印象,在这里贴出来,目的主要是想新手或者平常编程不太喜欢求其解的同学们一同进步。 也希望大牛们不要忘记自己是牛,向 @zw963 @jjym @messiahxu 学习,有时间就多回帖,让大家学好 Ruby 语言。

呵呵。共勉吧。比起社区很多人,我绝对是新新人类~ 哈哈

刚才回答你问题后,回来后,我又想到为什么不妨看看 Ruby 底层的操作码。

下面是操作码输出的一部分,前一块是父类 A 内混入模块 MyModule 的定义,后一块是子类 B 内混入同一模块的定义。

== disasm: <RubyVM::InstructionSequence:<class:A>@<compiled>>===========
0000 trace            2                                               (   1)
0002 trace            1
0004 putnil           
0005 getinlinecache   12, <ic:0>
0008 getconstant      :MyModule
0010 setinlinecache   <ic:0>
0012 send             :include, 1, nil, 8, <ic:1>
0018 trace            4
0020 leave            
== disasm: <RubyVM::InstructionSequence:<class:B>@<compiled>>===========
0000 trace            2                                               (   1)
0002 trace            1
0004 putnil           
0005 getinlinecache   12, <ic:0>
0008 getconstant      :MyModule
0010 setinlinecache   <ic:0>
0012 send             :include, 1, nil, 8, <ic:1>
0018 trace            4
0020 leave            

虽然我并不完全明白这些乱七八糟的伪码。不过可以很清楚的看出来,两次调用的参数是完全一样的。

send :include,1,nil,8,<ic:1>

这至少说明一点:这两次混入操作,都仅仅是方法调用而已,而且操作的是同样的位置的同样的对象。

我认为是每个类维护了一个祖先链的数组,而在实际执行 class MySubClass < MyClass 时: 首先从祖先哪里继承了祖先链,然后执行 include M include M2 时,根据祖先链中是否含有该模块决定是否加载。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号