Ruby 2.0 中模块前置的实现

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

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

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

  Ruby 2.0 为模块添加了一个 Module#prepend 方法,根据 API 文档的描述,它以相反的顺序对参数逐一调用 prepend_features 方法。和模块包含类似,会把一个模块的祖先链插入到另一个模块的祖先链中,但跟模块包含把祖先链插到该模块之后不一样,它会把祖先链插到该模块之前。我喜欢把这个特性叫做模块前置。
  先举个例子来说明模块前置的作用,并和模块包含作了对比。在这个例子中,类 C 前置了模块 A 和 B;类 D 包含了模块 A 和 B。
module A
def foo; 'A' end
end
module B
def foo; 'B' end
end
class C
prepend A, B   # Prepending is done by this line
def foo; 'C' end
end
class D
include A, B
def foo; 'D' end
end
C.ancestors # => [A, B, C, Object, Kernel, BasicObject]
D.ancestors # => [D, A, B, Object, Kernel, BasicObject]
C.new.foo # => 'A'
D.new.foo # => 'D'
  第 10 行,我们在 C 中前置了模块 A 和 B,这样 A 和 B 就插入到 C 的祖先链中了。从 21 和 22 行中的注释中,我们可以看到,在 C 的祖先链中,A 和 B 位于 C 之前。而在 D 的祖先链中,A 和 B 位于 D 之后。
  这就是为什么第 24 行,C.new.foo 的返回值是 'A',因为模块 A 前置于 C,位于祖先链的最前面,方法查找会优先找到 A 中的 foo 方法。
  我们看到了模块前置的强大的特性,但疑问也随之而来。最显而易见的问题是,C.ancestors 为什么不是从类 C 开始。要解开这个疑问,首先应该弄清楚 prepend 方法都做了哪些工作。我们跟到源代码中去一探究竟,下面是 prepend 的默认实现 Module#prepend 对应的源代码:
static VALUE
rb_mod_prepend(int argc, VALUE *argv, VALUE module)
{
int i;
ID id_prepend_features, id_prepended;
CONST_ID(id_prepend_features, "prepend_features");
CONST_ID(id_prepended, "prepended");
for (i = 0; i < argc; i++)
Check_Type(argv[i], T_MODULE);
while (argc--) {
rb_funcall(argv[argc], id_prepend_features, 1, module);
rb_funcall(argv[argc], id_prepended, 1, module);
}
return module;
}
31/3123>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

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

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

沪ICP备05003035号

沪公网安备 31010102002173号