这个标题比较怪,实际上很简单,看下列代码
运行结果是打印出 true, 不知道会不会有人和我有一样的疑问,为什么在singleton方法add_method_a中调用module_eval 定义动态方法的结果是为Test类增加了一个instance method而不是一个singleton method。
其实,解释很简单,原因就在于,ruby解释过程中instance method和singleton method处理方式的一点不同:
虽然,两者同样使用rb_add_method添加method,但前者使用局部变量klass,而后者使用的是全局变量ruby_class。
ruby_class 是ruby解释过程中栈信息的一部分,用来记录当前语句(Node)所在的类范围,很明显,例子中module_eval执行时,ruby_class指向的仍然是Test类,因此,运行的结果是我们为Test添加了一个instance method。
对此,我们可以略微加以证明,在上面两处调用rb_add_method的地方加上一条打印信息:
编译ruby,再次运行样例代码, 结果很清楚的告诉我们,此时ruby_class指向的都是Test类。
写倒这里,我们也顺带证明了一下一个我们常用的小技巧:
可以方便为我们的类添加一些简单实用的小方法 :)
ruby 代码
- class Test
- class <<self
- def add_method_a
- module_eval %{
- def method_a
- end
- }
- end
- end
- add_method_a
- end
- puts Test.new.respond_to?(:method_a)
其实,解释很简单,原因就在于,ruby解释过程中instance method和singleton method处理方式的一点不同:
cpp 代码
- // singleton method的解释过程
- case NODE_DEFS:
- VALUE klass;
- // ... ...
- klass = rb_singleton_class(recv);
- // ... ...
- rb_add_method(klass, node->nd_mid, defn,
- NOEX_PUBLIC|(body?body->nd_noex&NOEX_UNDEF:0));
cpp 代码
- // eval.c
- VALUE ruby_class;
- // ... ...
- // 普通method的处理方式
- case NODE_DEFN:
- // ... ....
- rb_add_method(ruby_class, node->nd_mid, defn, noex);
ruby_class 是ruby解释过程中栈信息的一部分,用来记录当前语句(Node)所在的类范围,很明显,例子中module_eval执行时,ruby_class指向的仍然是Test类,因此,运行的结果是我们为Test添加了一个instance method。
对此,我们可以略微加以证明,在上面两处调用rb_add_method的地方加上一条打印信息:
cpp 代码
- printf("curren class: %s method: %s\n", rb_class2name(ruby_class), rb_id2name(node->nd_mid));
编译ruby,再次运行样例代码, 结果很清楚的告诉我们,此时ruby_class指向的都是Test类。
写倒这里,我们也顺带证明了一下一个我们常用的小技巧:
ruby 代码
- // 例如我们自己的"attr_accessor"
- module MyAttribute
- def add_attribute name
- module_eval %{
- def #{name}
- @#{name}
- end
- def #{name}= value
- @#{name} = value
- end
- }
- end
- end
- class TestModule
- extend MyAttribute //增加singleton method add_attribute
- add_attribute :my_attr
- end
- require 'date'
- t = TestModule.new
- t.my_attr = Date.today
- puts t.my_attr
可以方便为我们的类添加一些简单实用的小方法 :)