【发布时间】:2016-04-04 09:01:58
【问题描述】:
假设我有一堂课:
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 扩展。
换句话说,如果您出于某种原因重新打开类以添加一些方法而没有确保首先(或之后)加载完整的 origina 类定义,如果依赖于自动加载,您的代码可能会中断。
相反,Foo.class_eval 调用Foo 上的方法,因此它希望在您尝试添加新方法时原始Foo 定义已经存在。这确保了当您添加新方法时,Foo 类已经被定义。
总之,主要区别在于重新打开类允许您(无论好坏)向可能尚未加载的类添加方法,而 class_eval 要求已定义类。
一般来说,除非我定义命名空间子类或重新打开我可以完全控制的类,否则我更喜欢第二种方法,因为在大型代码库中它使代码更易于维护。事实上,如果我扩展第三方类,我通常会使用 mixins,以便在需要覆盖现有方法时保留完整的方法祖先链。
【讨论】:
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
【讨论】: