假设我有一个类:红宝石添加方法一类
class Foo
end
要将方法添加到这个类,我知道2种选择:
重新开放类并实现方法:
class Foo def bar end end
使用
class_eval
实现方法:Foo.class_eval { def bar; end}
有什么区别?哪一个更好?
假设我有一个类:红宝石添加方法一类
class Foo
end
要将方法添加到这个类,我知道2种选择:
重新开放类并实现方法:
class Foo
def bar
end
end
使用class_eval
实现方法:
Foo.class_eval { def bar; end}
有什么区别?哪一个更好?
实际上,还有其他一些方法可以将新方法添加到类中。例如,您也可以在模块中定义方法,并将模块混合到原始类中。
module ExtraMethods
def bar
end
end
Foo.class_eval { include ExtraMethods }
class Foo
include ExtraMethods
end
没有真正的更好或更坏。您提到的两种(或三种)方式有不同的行为,您可能需要根据您的需要(或偏好)使用其中一种或另一种。在大多数情况下,这是主观的。在其他情况下,它确实取决于代码的结构。
重新打开类与使用class_eval
的主要目的区别在于第一个类也是类定义,而第二个类需要原始类已经定义。
实际上,在某些情况下重新开课可能会导致一些意想不到的副作用。假设您使用一堆方法在文件lib/foo.rb
中定义了Foo
。然后在config/initializers/extra.rb
中重新打开Foo
,并添加bar
方法。
在myclass.rb
中,您使用Foo
,但不需要手动输入lib/foo.rb
,而是依靠自动加载功能。
如果extra.rb
被lib/foo.rb
之前加载,有什么事情发生的是,Foo
类是在您的环境中已经定义,你的代码将不会加载lib/foo.rb
。你将得到的是一个Foo
类,只包含你定义的bar
扩展名,而不包含原来的Foo
。
换句话说,如果无论出于何种原因重新开放类以添加某些方法而未确保首先(或之后)加载完整的原始类定义,则如果代码依赖于自动加载,则代码可能会中断。
相反,Foo.class_eval
调用Foo
上的方法,因此它预计原始Foo
定义在尝试添加新方法时已经存在。这可确保在添加新方法时,Foo
类将已定义。
总而言之,主要区别在于重新打开类允许您(无论好坏)将方法添加到尚未加载的类中,而class_eval
要求已经定义类。一般来说,除非我定义名称空间子类或重新打开类,否则我完全控制它,我更喜欢第二种方法,因为它在大型代码库中保持代码更易维护。事实上,如果我扩展第三方类,我通常会使用mixins,以便我可以保留完整的方法祖先链,如果我需要覆盖现有的方法。
没有“Ruby自动加载”,你可能意思是“Rails autoload”。 – mudasobwa
@mudasobwa在技术上有('autoload'方法),但它不适用于这种情况。我对答案做了一些小改动。感谢您指出。 –
第二种方法非常方便,当你需要一些动态的东西。 Ruby实际上有几个示波器:
# scope one, opened with `class` keyword
class ...
# scope two, opened with `def` keyword
def ...
end
end
使用class_eval
,您可以共享示波器。
>> foo = 1
=> 1
>> class Foo
>> puts foo
>> def bar
>> puts foo
>> end
>> end
NameError: undefined local variable or method 'foo' for Foo:Class
from (irb):3:in <class:Foo>
from (irb):2
>> Foo
=> Foo
>> Foo.class_eval {
?> puts foo
>> define_method :bar do
>> puts foo
>> end
>> }
1
=> :bar
>> Foo.new.bar
1
但是这也意味着这个共享范围不会在类存在时被垃圾回收 – Vasfed
['Foo#define_method'](http://ruby-doc.org/core-2.2.0/Module.html#method-i-define_method)也是。 – mudasobwa