下面这段 Ruby 代码创建了 A, B, C 三个模块,C 包含 A 和 B。
A = Module.new
B = Module.new
module C
include A, B # Inclusion is done by this line
end
在模块 C 中,语句 include A, B 完成了模块包含的工作,该语句以 A, B 两个模块为作为参数,调用了模块 C 的 include 方法。一切都那么自然。
现在,把注意力放在 include 方法上,它的默认实现是 Module#include。根据 API 文档,它以相反的顺序对参数逐一调用 append_features 方法。对应的 C 函数是
rb_mod_include。 static VALUE rb_mod_include(int argc, VALUE *argv, VALUE module) { int i; ID id_append_features, id_included; CONST_ID(id_append_features, "append_features"); CONST_ID(id_included, "included"); for (i = 0; i < argc; i++) Check_Type(argv[i], T_MODULE); while (argc--) { rb_funcall(argv[argc], id_append_features, 1, module); rb_funcall(argv[argc], id_included, 1, module); } return module; } |
从 Ruby 源码来看,除了对参数模块调用 append_features 方法外,还紧接着调用了 included 方法。所以,文档描述得并不完整,可能还没有更新。
append_features 和 included 都是回调方法。而 append_features 方法才是真正干活的地方,Ruby 通过这个方法将包含的逻辑转移到了被包含的模块上。这种精巧的设计能让你灵活地自定义包含模块的行为,既可以在发出包含动作的模块中重定义 include 方法,也可以从源头着手,重定义 append_features 方法。
再来看看 append_features 的默认实现 Module#append_features 都做了什么。下面是 API 文档上给出的源码:
rb_mod_append_features(VALUE module, VALUE include) { switch (TYPE(include)) { case T_CLASS: case T_MODULE: break; default: Check_Type(include, T_CLASS); break; } rb_include_module(include, module); return module; } |