Ruby 2.0 中模块前置的实现

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

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

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

  可以看到,它的行为和 Module#include 方法几乎一样,只不过回调的方法不一样。这里,它回调了参数模块的 prepend_features 方法和 prepended 方法。同样,Module#prepend_features 才是真正干活的地方,所以跟进去看看。
static VALUE
rb_mod_prepend_features(VALUE module, VALUE prepend)
{
switch (TYPE(prepend)) {
case T_CLASS:
case T_MODULE:
break;
default:
Check_Type(prepend, T_CLASS);
break;
}
rb_prepend_module(prepend, module);
return module;
}
  它做了一些类型方面的检查,然后把工作交给了 rb_prepend_module 函数,我们看看 rb_prepend_module 函数做了什么。
void
rb_prepend_module(VALUE klass, VALUE module)
{
void rb_vm_check_redefinition_by_prepend(VALUE klass);
VALUE origin;
int changed = 0;
rb_frozen_class_p(klass);
if (!OBJ_UNTRUSTED(klass)) {
rb_secure(4);
}
Check_Type(module, T_MODULE);
OBJ_INFECT(klass, module);
origin = RCLASS_ORIGIN(klass);
if (origin == klass) {
origin = class_alloc(T_ICLASS, klass);
RCLASS_SUPER(origin) = RCLASS_SUPER(klass);
RCLASS_SUPER(klass) = origin;
RCLASS_ORIGIN(klass) = origin;
RCLASS_M_TBL(origin) = RCLASS_M_TBL(klass);
RCLASS_M_TBL(klass) = st_init_numtable();
st_foreach(RCLASS_M_TBL(origin), move_refined_method,
(st_data_t) RCLASS_M_TBL(klass));
}
changed = include_modules_at(klass, klass, module);
if (changed < 0)
rb_raise(rb_eArgError, "cyclic prepend detected");
if (changed) {
rb_clear_cache();
rb_vm_check_redefinition_by_prepend(klass);
}
}
  这个函数做了一些工作,我们来分析一下。前 16 行都是在做一些类型检查等工作,我们跳过。从第 17 行开始分析。
  首先,宏 RCLASS_ORIGIN 获取 klass 的 origin 成员,并且把它和 klass 比较。我们不知道 origin 字段有什么作用,我们先假设测试条件为真,即 klass 的 origin 成员指向自身。我们来分析一下 if 语句中的逻辑:
  19 行为 klass 创建了一个新的包含类,我们把它称为原始类;
  20 ~ 21 行把新创建的包含类插入到 klass 和 klass 的父类中间;
  22 行将 klass 的 origin 成员指向了新类;
  接下来,23 ~ 24 行把 klass 的方法表转移到新类中,并清空 klass 的方法表;
  最后,25 行又把 klass 原先的方法表中的 Refined 方法移了回来。
  分析完 if 语句,我们继续前进,来到第 28 行。等等,你好像看到了熟悉的东西。没错,那就是 include_modules_at 方法。在前一篇文章中,我们讨论了这个函数,它用来包含某个模块。你简直不敢相信自己的眼睛,明明是在前置模块,怎么突然又变成包含模块了?
  是的,没错,它就是在包含模块。被包含的模块的祖先链插入到了 klass 和 klass 的原始类之间。由于 klass 内部的方法表已经转移到上游的原始类中,所以插入的位置正好合适。Ruby 通过这种变换,巧妙地将前置模块转化为包含模块,太棒了。
  下面这个图描述了文章开头的那个例子中,类 C 中 prepend A, B 语句执行前后的状态:
  +-----+      +--------+
  Before:  |  C  |----->| Object |
  +-----+      +--------+
  +--------------- klass ----------------+
  |                                      |
  v                                      |
  +-----+      +-----+      +-----+      +-----+      +--------+
  After:  |  C  |----->|  A  |----->|  B  |----->|  C' |+---->| Object |
  +-----+      +-----+      +-----+      +-----+      +--------+
  |                                      ^
  |                                      |
  +--------------- origin ---------------+
32/3<123>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

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

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

沪ICP备05003035号

沪公网安备 31010102002173号