2009-05-22 13 views
46

Nur basierend auf dem Namen der Methoden, Ich würde erwarten, dass class_eval Sie hinzufügen eine Klassenmethode zu Foo und instance_eval, damit Sie eine Instanzmethode zu Foo hinzufügen können. Aber sie scheinen das Gegenteil zu tun.Wie kann man den Unterschied zwischen class_eval() und instance_eval() verstehen?

Im obigen Beispiel erhalten Sie einen undefinierten Methodenfehler, wenn Sie class_bar auf der Foo-Klasse aufrufen, und wenn Sie instance_bar auf der Instanz von Foo.new aufrufen, erhalten Sie auch einen undefinierten Methodenfehler. Beide Fehler scheinen einem intuitiven Verständnis von class_eval und instance_eval zu widersprechen.

Was ist der Unterschied zwischen diesen Methoden?

Dokumentation für class_eval:

mod.class_eval (string [, Dateiname [, lineno]]) => obj

Wertet die Schnur oder Block im Kontext mod. Dies kann verwendet werden, um Methoden zu einer Klasse hinzuzufügen.

Dokumentation für instance_eval:

obj.instance_eval {| | block} => obj

Wertet eine String Rubin Quellcode enthält, oder den gegebenen Block, im Rahmen des Empfängers (OBJ). Um den Kontext zu setzen, wird die Variable self auf obj gesetzt, während der Code ausgeführt wird, was dem Code Zugriff auf objs Instanzvariablen gibt.

Antwort

77

Da die Dokumentation sagt, wertet class_eval den String oder Block im Rahmen des Moduls oder eine Klasse. So sind die folgenden Teile des Codes sind äquivalent:

class String 
    def lowercase 
    self.downcase 
    end 
end 

String.class_eval do 
    def lowercase 
    self.downcase 
    end 
end 

In jedem Fall hat die String-Klasse wurde wieder geöffnet und eine neue Methode definiert. Das Verfahren ist in allen Instanzen der Klasse zur Verfügung, so:

"This Is Confusing".lowercase 
=> "this is confusing" 
"The Smiths on Charlie's Bus".lowercase 
=> "the smiths on charlie's bus" 

class_eval hat eine Reihe von Vorteilen gegenüber einfach die Klasse wieder zu öffnen.Erstens, Sie können es leicht auf eine Variable nennen, und es ist klar, was Ihre Absicht ist. Ein weiterer Vorteil ist, dass es fehlschlägt, wenn die Klasse nicht existiert. Das folgende Beispiel wird fehlschlagen, da Array falsch geschrieben wird. Wenn die Klasse einfach wieder geöffnet wurde, würde es gelingen (und eine neue falsche Aray Klasse würde definiert werden):

Aray.class_eval do 
    include MyAmazingArrayExtensions 
end 

Schließlich class_eval eine Zeichenfolge nehmen kann, was nützlich sein kann, wenn man etwas mehr ruchlose tun ...

instance_eval auf der anderen Seite Code gegen eine einzelne Objektinstanz wertet:

confusing = "This Is Confusing" 
confusing.instance_eval do 
    def lowercase 
    self.downcase 
    end 
end 

confusing.lowercase 
=> "this is confusing" 
"The Smiths on Charlie's Bus".lowercase 
NoMethodError: undefined method ‘lowercase’ for "The Smiths on Charlie's Bus":String 

So mit instance_eval wird das Verfahren nur für die einzelne Instanz einer str definiert Ing.

Warum also instance_eval auf eine Class Klassenmethoden definieren?

Wie "This Is Confusing" und "The Smiths on Charlie's Bus" sind beide String Fällen Array, String, Hash und alle anderen Klassen sind selbst Instanzen Class. Sie können dies überprüfen, indem #class ihnen auffordern:

"This Is Confusing".class 
=> String 

String.class 
=> Class 

Wenn wir also instance_eval nennen es macht das gleiche für eine Klasse, wie es wäre auf einem anderen Objekt. Wenn wir instance_eval verwenden, um eine Methode für eine Klasse zu definieren, wird eine Methode nur für diese Klasseninstanz und nicht für alle Klassen definiert. Wir könnten diese Methode eine Klassenmethode nennen, aber es ist nur eine Instanzmethode für diese bestimmte Klasse.

+4

Das ist eine tolle Erklärung. Vielen Dank. Ich folgte dir den ganzen Weg bis zum letzten Absatz, wo du versuchst zu erklären, warum instance_eval Klassenmethoden definiert, wenn sie in einer Klasse aufgerufen werden. Die letzte Zeile ist für mich besonders schwer zu verdauen: "Es ist ein Fehler, sie als Klassenmethode zu bezeichnen, sie ist nur eine Instanzmethode für diese bestimmte Klasseninstanz." Meinst du, dass alle Klassenmethoden nichts anderes sind? –

+0

Ja, genau - das ist alles, was sie sind. Ich werde versuchen, es morgen klarer zu machen. – tomafro

+0

Fantastisch - Ich habe gerade eine neue Frage speziell für dieses Problem hinzugefügt: "Warum definiert instance_eval() eine Klassenmethode, wenn sie in einer Klasse aufgerufen wird?" http://stackoverflow.com/questions/900704/why-does-instanceeval-define-a-class-method-when-called-on-a-class. –

5

Ich denke, Sie haben es falsch verstanden. class_eval fügt die Methode in der Klasse hinzu, sodass alle Instanzen über die Methode verfügen. instance_eval fügt die Methode nur einem bestimmten Objekt hinzu.

foo = Foo.new 
foo.instance_eval do 
    def instance_bar 
    "instance_bar" 
    end 
end 

foo.instance_bar  #=> "instance_bar" 
baz = Foo.new 
baz.instance_bar  #=> undefined method 
+1

Sie rufen instance_eval auf dem Objekt foo, während ich es auf der Klasse Foo aufrufen. Ich sehe nicht, wie das zusammenhängt. –

3

instance_eval erstellt effektiv eine Singleton-Methode für die betreffende Objektinstanz. class_eval erstellt eine normale Methode im Kontext der gegebenen Klasse, die für alle Objekte dieser Klasse verfügbar ist.

Hier ist ein Link in Bezug auf singleton methods und der singleton pattern (nicht-Rubin-spezifisch)

+0

Danke für die Links! –

16

Die andere Antwort ist richtig, aber erlauben Sie mir, ein wenig tiefer zu gehen.

Ruby hat eine Reihe von verschiedenen Arten von Anwendungsbereich; sechs nach wikipedia, obwohl detaillierte formale Dokumentation zu fehlen scheint. Die Arten von Umfang in dieser Frage beteiligt sind, ist nicht überraschend, Instanz und Klasse.

Der aktuelle Instanzumfang wird durch den Wert self definiert. Alle nicht qualifizierten Methodenaufrufe werden an die aktuelle Instanz gesendet, ebenso wie alle Referenzen auf Instanzvariablen (die wie @this aussehen).

def ist jedoch kein Methodenaufruf. Das Ziel für die von def erstellten Methoden ist die aktuelle Klasse (oder das Modul), die unter Module.nesting[0] zu finden ist.

Mal sehen, wie die beiden unterschiedlichen eval Aromen diese Bereiche betreffen:

String.class_eval { [self, Module.nesting[0]] } => [String, String] String.instance_eval { [self, Module.nesting[0]] } => [String, #<Class:String>]

In beiden Fällen ist die Instanzbereich das Objekt, auf dem * _eval genannt wird.

Für class_eval wird der Klassenbereich auch zum Zielobjekt, sodass def Instanzmethoden für diese Klasse/dieses Moduls erstellt.

Für instance_eval wird der Klassenbereich die Singleton-Klasse (aka Metaklasse, Eigenklasse) des Zielobjekts.Instanzmethoden, die für ein Objekt in der Singleton-Klasse erstellt wurden, werden Singleton-Methoden für dieses Objekt. Singleton-Methoden für eine Klasse oder ein Modul werden üblicherweise (und etwas ungenau) Klassenmethoden genannt.

Der Klassenbereich wird auch zum Auflösen von Konstanten verwendet. Klassenvariablen (@@these @@things) werden mit dem Klassenbereich aufgelöst, sie überspringen jedoch Singleton-Klassen beim Durchsuchen der Modulschachteln-Kette. Die einzige Möglichkeit, auf Klassenvariablen in Singleton-Klassen zuzugreifen, ist class_variable_get/set.

+0

+1 für auch Einführung von Umfang hier, zusätzlich zu nur selbst durch andere Antworten. – bryantsai

+0

BTW, das funktioniert nur unter 1.9. – bryantsai

+0

Große Antwort. Gibt es aus Neugierde einen Ersatz für 'Module.nesting [0]', da es in den letzten Ruby-Versionen nicht so funktioniert? – antinome

Verwandte Themen