2015-12-30 94 views
7

假设我有一个类:红宝石添加方法一类

class Foo 
end 

要将方法添加到这个类,我知道2种选择:

  1. 重新开放类并实现方法:

    class Foo 
        def bar 
        end 
    end 
    
  2. 使用class_eval实现方法:

    Foo.class_eval { def bar; end} 
    

有什么区别?哪一个更好?

+0

['Foo#define_method'](http://ruby-doc.org/core-2.2.0/Module.html#method-i-define_method)也是。 – mudasobwa

回答

15

实际上,还有其他一些方法可以将新方法添加到类中。例如,您也可以在模块中定义方法,并将模块混合到原始类中。

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.rblib/foo.rb之前加载,有什么事情发生的是,Foo类是在您的环境中已经定义,你的代码将不会加载lib/foo.rb。你将得到的是一个Foo类,只包含你定义的bar扩展名,而不包含原来的Foo

换句话说,如果无论出于何种原因重新开放类以添加某些方法而未确保首先(或之后)加载完整的原始类定义,则如果代码依赖于自动加载,则代码可能会中断。

相反,Foo.class_eval调用Foo上的方法,因此它预计原始Foo定义在尝试添加新方法时已经存在。这可确保在添加新方法时,Foo类将已定义。

总而言之,主要区别在于重新打开类允许您(无论好坏)将方法添加到尚未加载的类中,而class_eval要求已经定义类。一般来说,除非我定义名称空间子类或重新打开类,否则我完全控制它,我更喜欢第二种方法,因为它在大型代码库中保持代码更易维护。事实上,如果我扩展第三方类,我通常会使用mixins,以便我可以保留完整的方法祖先链,如果我需要覆盖现有的方法。

+0

没有“Ruby自动加载”,你可能意思是“Rails autoload”。 – mudasobwa

+0

@mudasobwa在技术上有('autoload'方法),但它不适用于这种情况。我对答案做了一些小改动。感谢您指出。 –

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 
+1

但是这也意味着这个共享范围不会在类存在时被垃圾回收 – Vasfed