2010-03-15 68 views
14

我被卡住了。我試圖動態地定義一個類方法,我無法將我的頭圍繞在ruby元類模型上。考慮以下類:Ruby元類瘋狂

class Example 

    def self.meta; (class << self; self; end); end 

    def self.class_instance; self; end 

end 

Example.class_instance.class # => Class 
Example.meta.class   # => Class 

Example.class_instance == Example  # => true 
Example.class_instance == Example.meta # => false 

很顯然,這兩種方法都返回一個Class實例。但是這兩個例子 是不一樣的。他們也有不同的祖先:

Example.meta.ancestors   # => [Class, Module, Object, Kernel] 
Example.class_instance.ancestors # => [Example, Object, Kernel] 

在元類和類實例之間做出區別有什麼意義?

我想通了,我可以send :define_method元組動態定義一個方法,但如果我嘗試將它發送到類實例將無法工作。至少我可以解決我的問題,但我仍然想明白爲什麼它以這種方式工作。

更新2010年3月15日13:40

有以下的假設是否正確。

  • 如果我有一個調用self.instance_eval並定義方法的實例方法,它只會影響該類的特定實例。
  • 如果我有一個調用self.class.instance_eval(與調用class_eval相同)的實例方法並定義了一個方法,它將影響該特定類的所有實例,從而產生新的實例方法。
  • 如果我有一個調用instance_eval並定義一個方法的類方法,它將爲所有實例創建一個新的實例方法。
  • 如果我有一個調用meta/eigen類的instance_eval並定義方法的類方法,它將產生一個類方法。

我認爲它開始對我有意義。如果類內部的自我指向特徵類,它肯定會限制你的可能性。如果是這樣,就不可能在類方法中定義一個實例方法。那是對的嗎?

回答

11

動態定義一個單身方法很簡單,當你使用instance_eval

Example.instance_eval{ def square(n); n*n; end } 
Example.square(2) #=> 4 
# you can pass instance_eval a string as well. 
Example.instance_eval "def multiply(x,y); x*y; end" 
Example.multiply(3,9) #=> 27 

至於差以上,你是在混淆兩件事情:

由您定義的元類,就是所謂的Ruby社區singelton類特徵類。該單例類是可以添加類(單例)方法的類。

對於您試圖使用class_instance方法定義的類實例,除了類本身,只是嘗試將實例方法添加到類Example並檢查您定義的class_instance方法是否返回類Example本身通過檢查方法的存在:

class Example 
    def self.meta; (class << self; self; end); end 
    def self.class_instance; self; end 
    def hey; puts hey; end 
end 

Example.class_instance.instance_methods(false) #=> ['hey'] 

反正來概括你,當你想添加類的方法,只需將它們添加到元類。至於class_instance方法是無用的,只是將其刪除。

無論如何,我建議你閱讀this post來掌握Ruby反射系統的一些概念。

UPDATE

我建議你閱讀這好貼:Fun with Ruby's instance_eval and class_eval, 不幸的是class_evalinstance_eval是困惑,因爲他們以某種方式對他們的命名工作!

Use ClassName.instance_eval to define class methods. 

Use ClassName.class_eval to define instance methods. 

現在回答你的假設:

如果我有一個實例方法, 調用self.instance_eval和定義 方法,它只會影響類的 特定實例。

是:

class Foo 
    def assumption1() 
    self.instance_eval("def test_assumption_1; puts 'works'; end") 
    end 
end 

f1 = Foo.new 
f1.assumption1 
f1.methods(false) #=> ["test_assumption_1"] 
f2 = Foo.new.methods(false) #=> [] 

如果我有 電話self.class.instance_eval(其中 將與調用 class_eval),並定義了一個方法,它 將一個實例方法影響該特定類的所有實例,從而產生新的實例方法 。

在類本身這方面將定義特殊方法(不是實例的)沒有instance_eval

class Foo 
    def assumption2() 
    self.class.instance_eval("def test_assumption_2; puts 'works'; end") 
    end 
end 

f3 = Foo.new 
f3.assumption2 
f3.methods(false) #=> [] 
Foo.singleton_methods(false) #=> ["test_assumption_2"] 

對於工作代替instance_eval以上class_eval

如果我有一個要求 instance_eval的和定義的方法它 將產生一個新的實例方法 所有實例的類方法。

都能跟得上:

class Foo 
    instance_eval do 
    def assumption3() 
     puts 'works' 
    end 
    end 
end 

Foo.instance_methods(false) #=> [] 

Foo.singleton_methods(false) #=> ["assumption_3"] 

這將使單方法,而不是實例方法。爲了這個工作,用上面的class_eval代替instance_eval

如果我有其中要求所述元/本徵類 instance_eval的,並限定了方法將導致 一個類方法的類的方法。

嗯,不會,這會做出如此複雜的東西,因爲它會將singleton方法添加到singleton類中,我認爲這不會有任何實際用途。

+0

欲瞭解更多有關_why_實例_eval'內'def'定義的類方法,請參閱這篇文章http://yugui.jp/articles/846 – horseyguy 2010-03-15 12:15:05

+0

非常感謝迄今。我更新我的問題。你介意看看它嗎? – t6d 2010-03-15 12:44:15

+0

更新後回答更新中的問題。 – khelll 2010-03-15 13:30:07

5

如果定義在類中的方法,可以在其對象調用。這是一個實例方法

class Example 
end 

Example.send :define_method, :foo do 
    puts "foo" 
end 

Example.new.foo 
#=> "foo" 

如果定義在元類的方法,它可以在的類調​​用。這類似於類方法或其他語言的靜態方法的概念。

class Example 
    def self.metaclass 
    class << self 
     self 
    end 
    end 
end 

Example.metaclass.send :define_method, :bar do 
    puts "bar" 
end 

Example.bar 
#=> "bar" 

原因即元類存在,是因爲你可以做到這一點的紅寶石:

str = "hello" 
class << str 
    def output 
    puts self 
    end 
end 

str.output 
#=> "hello" 

"hi".output 
# NoMethodError 

正如你可以看到,我們定義只適用於的一個實例的方法串。我們定義這個方法的東西叫做元類。在方法查找鏈中,在搜索對象的類之前首先訪問元類。

如果我們Class類型的對象代替String類型的對象,你可以想象爲什麼這意味着我們只定義在特定類中的方法,而不是所有類。

當前上下文和self之間的差異很微妙,如果您有興趣,您可以read more