假设我有一个班级:
class Foo
end
要向此类添加方法,我知道2个选项:
重新打开课程并实施方法:
class Foo
def bar
end
end
使用class_eval
实现方法:
Foo.class_eval { def bar; end}
有什么区别?哪一个更好?
答案 0 :(得分:20)
实际上,还有一些其他方法可以为类添加新方法。例如,您还可以在模块中定义方法,并将模块混合到原始类中。
module ExtraMethods
def bar
end
end
Foo.class_eval { include ExtraMethods }
class Foo
include ExtraMethods
end
没有真正的好或坏。您提到的两种(或三种)方式具有不同的行为,您可能希望根据您的需要(或偏好)使用其中一种或另一种。在大多数情况下,这是主观的。在其他情况下,它实际上取决于您的代码的结构。
重新打开类与使用class_eval
之间的主要客观差异是第一个也是类定义,而第二个需要已经定义原始类。
在实践中,在某些情况下重新打开课程可能会导致一些意想不到的副作用。我们假设您使用一堆方法在文件Foo
中定义了lib/foo.rb
。然后,您在Foo
中重新开启config/initializers/extra.rb
,然后添加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,这样如果我需要覆盖现有方法,我就可以保留完整方法的祖先链。
答案 1 :(得分:5)
当你需要一些动态的东西时,第二种方法非常方便。 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