@elibinary
2017-06-11T05:15:02.000000Z
字数 1293
阅读 874
Ruby
上篇中有简单说到 module 的 include,下面来看一些稍复杂的使用情况以及 ruby 是如何处理的
那么,首先问题:在 module A 中 include 了 module B,然后 class M 再 include 模块 A,现在 class M 的祖先链是什么样子的?
module Bdef sayputs 'I am B'endendmodule Ainclude Bdef sayputs "I'm A"endendclass Minclude AendM.new.say# => I'm A
可以看出方法查找先找到的 A 中的 #say 方法,那么 M 的祖先链应该是这样的:
M.ancestors# => [M, A, B, Object, Kernel, BasicObject]
事实上,当把 B include 到 A 中时, Ruby 会创建 B 的副本,然后把它设置为 A 的超类,也就是 A 的 super 指针指向 B 的副本。
虽然 module 本身不允许被指派超类,然后在 Ruby 内部可以。
最后把 A include 到 M 中,Ruby 会迭代这两个模块,然后依次作为超类插入。
修改已经被 include 的 module 会怎样?
module Bdef sayputs 'I am B'endendclass Minclude BendM.new.say# I am BM.new.methods# => => [:say, :instance_of?, :public_send ...module Bdef re_sayputs 'Double B'endendM.new.say# I am BM.new.re_say# Double BM.new.methods# => [:say, :re_say, :instance_of?, :public_send ...
那么,这是为什么呢,Ruby 做了什么处理
事实上,Ruby 在 include module 时,拷贝副本时拷贝的是 RClass 结构体,但是并不连 m_tbl 指向的方法表一同拷贝,也就是说副本与原 module 是共享同一个方法表的。
### prepend
有时候我们会有需求期望当前类的方法不会重载掉 module 中的方法,这个时候就会使用 #prepend 方法
module Bdef sayputs 'I am B'endendclass Mprepend Bdef sayputs 'I am M'endendM.new.say# I am BM.ancestors# => [B, M, Object, Kernel, BasicObject]
使用 prepend 时,在超类链中,Ruby 会把 B 放在 M 前面
那么, Ruby 在其中做了什么呢?Ruby 是沿祖先链向后查找方法的,它是如何在 super 指针向下指的情况下做到这样的?
实际上,Ruby 在内部使用了个巧妙的小手段,当使用 prepend 包含一个 module 时,Ruby 会创建 M 的 origin class,并把它设置为前置模块 B 的超类。Ruby 使用 rb_classext_struct 机构体中的 origin 指针指向原 class。 Ruby 还会从 origin class 中把方法全部移动到原 class 中,这意味着这些方法将是可以被前置模块 B 重载的。