2015-10-15 41 views
0

我讀爲什麼是悽美指南紅寶石,並在第6章,他用這樣的代碼:使用`instance_eval`

class Creature 
    # Get a metaclass for this class 
    def self.metaclass; class << self; self; end; end 

    ... 

    def self.traits(*arr) 
    # 2. Add a new class method to for each trait. 
    arr.each do |a| 
     metaclass.instance_eval do 
     define_method(a) do |val| 
      @traits ||= {} 
      @traits[a] = val 
     end 
     end 
    end 
    end 
end 

爲什麼他呼籲Creature類的元類instance_eval?由於instance_eval添加方法metaclass,他只是這樣做:

def self.metaclass; self; end; 

還是我錯了?有沒有更優雅的解決方案呢?

+0

雖然我喜歡悽美的引導,當它涉及到元編程是完全過時。所有_why試圖做的就是在'metaclass'對象上調用'define_method',這對'define_singleton_method'來說現在是微不足道的。 – Max

回答

1

如果你

def self.metaclass; self; end; 

你將不得不Creature類的引用。因此,在這種情況下,這些方法將不被定義爲對象Creature的單例類,而是類Creature本身(到類Creature的實例方法列表)。該方法

def self.metaclass; class << self; self; end; end 

是檢索在紅寶石< 1.9單例類對象Creature的一個簡單的方法。在紅寶石1.9+實施方法singleton_class這是class << self的快捷方式。這樣的代碼可以簡化爲:

class Creature 

    ... 

    def self.traits(*arr) 
    # 2. Add a new class method to for each trait. 
    arr.each do |a| 
     singleton_class.instance_eval do 
     define_method(a) do |val| 
      @traits ||= {} 
      @traits[a] = val 
     end 
     end 
    end 
    end 
end 
+0

但是instance_eval定義了對象的單例類的方法嗎?爲什麼我需要將singleton類傳遞給instance_eval? –

+0

'instance_eval'或'class_eval'或'module_eval'非常相似。它們只是在它們被調用的對象內執行一個塊(如果你通過一個塊)。所以當你調用Creature.instance_eval {define_method(:m){...}}時,它等於'class Creature; def:m; ...;結束;'。因爲你在eval上下文中有'Creature'類。您可以通過執行'Creature.instance_eval {p self}'來簡單檢查。在你的例子中,定義了「類方法」,但不是「Creature」類的實例方法。 – intale

+3

BTW:我們現在不僅有'Object#singleton_class',還有'Object#define_singleton_method',這使得所有這些歌曲和舞蹈都不必要。 –

0

寫_why的代碼更簡單的方法是隻

def self.traits(*arr) 
    # 2. Add a new class method to for each trait. 
    arr.each do |a| 
    metaclass.define_method(a) do |val| 
     @traits ||= {} 
     @traits[a] = val 
    end 
    end 
end 

這裏唯一的問題是,你得到一個錯誤:

private method `define_method' called for metaclass (NoMethodError) 

私有方法只能用隱式接收方調用,即問題在方法調用之前是明確的metaclass.。但是如果我們刪除它,那麼隱式接收器(self)就是Creature!那麼我們如何將self更改爲不同的對象呢? instance_eval

metaclass.instance_eval do 
    define_method(a) do |val| 
    ... 
    end 
end 

所以它實際上只是一個繞過一個事實,即define_method是私有的方式。破解它的另一種方法是使用send

metaclass.send(:define_method, a) do |val| 
    ... 
end 

但這些天來所有這一切是完全不必要的;你被允許周圍沒有私有方法黑客來定義元類(AKA單例類)方法:

def self.traits(*arr) 
    # 2. Add a new class method to for each trait. 
    arr.each do |a| 
    define_singleton_method(a) do |val| 
     @traits ||= {} 
     @traits[a] = val 
    end 
    end 
end