关闭

Ruby 2.0 中模块前置的实现

发表于:2014-3-14 10:55

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:言无不尽    来源:51Testing软件测试网采编

  正如之前分析的那样,C' 就是那个新创建的包含类,它是 C 的原始类。但如果是这样的话,还是无法解释之前的疑问:为什么 C.ancestors 不是从类 C 开始?要搞清楚这个问题,我们来看看 C.ancestors 是如何工作的。
  我们找到了 Module#ancestors 的源代码,它看起来比较简单:
VALUE
rb_mod_ancestors(VALUE mod)
{
VALUE p, ary = rb_ary_new();
for (p = mod; p; p = RCLASS_SUPER(p)) {
if (FL_TEST(p, FL_SINGLETON))
continue;
if (BUILTIN_TYPE(p) == T_ICLASS) {
rb_ary_push(ary, RBASIC(p)->klass);
}
else if (p == RCLASS_ORIGIN(p)) {
rb_ary_push(ary, p);
}
}
return ary;
}
  该函数首先创建了一个数组,然后对遍历模块的祖先链,对每个祖先,如果是包含类或者原始类指向自身,就放在返回的数组里面。另外,它还会跳过单例类。
  至此,之前的疑问也得到了解释,C.ancestors 并没有把 C 自身包含进去,因为它既不是包含类,也不是 origin 指向自身的类。而 C.ancestors 返回的数组中的 C 其实是 C 的原始类,同时也是 C 的包含类,所以它才有 C 这个名字。
  模块前置其实也是通过模块包含来完成的,只不过在包含之前做了一些特殊处理:创建了一个原始类,然后在原始类之前包含模块。但由于原始类也是一个包含类,因此被前置模块的某个祖先可能会越过原始类,比如下面这个例子:
A = Module.new
module B
def bar; 'B' end
end
module C
include A, B
end
class D
include A
prepend C
def bar; 'D' end
end
D.new.bar # => 'D'
  类 D 在前置模块 C 之前,包含了模块 A。下图中,前两个是 D 前置模块 C 之前,D 和 C 的祖先链,第三个是 D 前置模块 C 之后,D 的祖先链。
  +---+    +---+    +--------+
  | D |--->| A |--->| Object |
  +---+    +---+    +--------+
  +---+    +---+    +---+    +--------+
  | C |--->| A |--->| B |--->| Object |
  +---+    +---+    +---+    +--------+
  |
  +-----------------+  A 使得插入点移到了 D' 的后面
  |
  v
  +---+    +---+    +---+    +---+    +---+    +--------+
  | D |--->| C |--->| D'|--->| A |--->| B |+-->| Object |
  +---+    +---+    +---+    +---+    +---+    +--------+
  正如图中标注的那样,模块 A 的存在使得插入点移到了 D' 的后面,所以 B 位于 D' 的后面。所以 D.new.bar 的值是 'D' 而不是 'B'。
  现在,我们理解了模块前置在 Ruby 内部是如何实现的。模块前置非常有用,只要明白了它是如何工作的,你一定能想到它的用武之地。
33/3<123
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号