2015-12-30 93 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