2015-12-30 5 views
7

Angenommen, ich habe eine KlasseRuby-Add-Verfahren zu einer Klasse

  1. die Klasse Wieder und implementieren die Methode:

    class Foo 
        def bar 
        end 
    end 
    
  2. class_eval mit der Methode zu implementieren:

    Foo.class_eval { def bar; end} 
    

Was ist der Unterschied? Welches ist besser?

+0

['Foo # define_method'] (http://ruby-doc.org/core-2.0/Module.html#method-i-define_method) auch. – mudasobwa

Antwort

15

Tatsächlich gibt es einige andere Möglichkeiten, einer Klasse neue Methoden hinzuzufügen. Zum Beispiel können Sie auch die Methoden in einem Modul definieren und das Modul in die ursprüngliche Klasse mischen.

module ExtraMethods 
    def bar 
    end 
end 

Foo.class_eval { include ExtraMethods } 
class Foo 
    include ExtraMethods 
end 

Es gibt kein wirklich besseres oder schlechteres. Die zwei (oder drei) Wege, die Sie erwähnten, haben ein unterschiedliches Verhalten und Sie möchten vielleicht das eine oder andere verwenden, je nach Ihrem Bedarf (oder Ihrer Präferenz). In den meisten Fällen ist dies subjektiv. In anderen Fällen hängt es wirklich davon ab, wie Ihr Code strukturiert ist.

Der Hauptzielunterschied zwischen dem erneuten Öffnen der Klasse vs class_eval ist, dass die erste auch eine Klassendefinition ist, während die zweite die ursprüngliche Klasse bereits definiert erfordert.

In der Praxis kann das erneute Öffnen der Klasse in einigen Fällen zu unerwarteten Nebenwirkungen führen. Nehmen wir an, Sie haben Foo in der Datei lib/foo.rb mit einer Reihe von Methoden definiert. Dann öffnen Sie erneut Foo in config/initializers/extra.rb und fügen Sie die bar Methode hinzu.

In verwenden Sie Foo, aber statt lib/foo.rb manuell benötigen Sie eine Autoload-Funktion.

Wenn extra.rb vor lib/foo.rb geladen wird, was passieren könnte, ist, dass die Foo Klasse bereits in Ihrer Umgebung definiert ist, und der Code wird lib/foo.rb nicht geladen werden. Was Sie haben werden, ist eine Foo Klasse, die nur die bar Erweiterung enthält, die Sie definiert haben, und nicht das Original Foo eins. Wenn Sie also aus irgendeinem Grund die Klasse erneut öffnen, um einige Methoden hinzuzufügen, ohne sicherzustellen, dass die vollständige Originalklassendefinition zuerst (oder danach) geladen wird, kann Ihr Code brechen, wenn er auf Autoload angewiesen ist.

Umgekehrt ruft Foo.class_eval eine Methode auf Foo auf, daher erwartet es die ursprüngliche Foo-Definition, die zu dem Zeitpunkt bereits vorhanden ist, an dem Sie versuchen, neue Methoden hinzuzufügen. Dadurch wird sichergestellt, dass beim Hinzufügen neuer Methoden die Klasse Foo bereits definiert ist. Der wichtigste Unterschied besteht darin, dass das Wiedereröffnen der Klasse es Ihnen erlaubt (zu guter oder schlechter) Methoden zu einer Klasse hinzuzufügen, die noch nicht geladen wurde, während class_eval erfordert, dass die Klasse bereits definiert wird.

Im Allgemeinen, wenn ich namespaced Unterklassen oder Klassen wiedereröffnen definiere ich volle Kontrolle über, bevorzuge ich den zweiten Ansatz wie in großen Codebasen hält es den Code wartungsfähiger. In der Tat verwende ich in der Regel Mixins, wenn ich Klassen von Drittanbietern erweitere, so dass ich die vollständige Vorfahrenkette der Methode behalten kann, wenn ich bestehende Methoden überschreiben muss.

+0

Es gibt keine "Ruby Autoload", wahrscheinlich bedeutet "Schienen Autoload." – mudasobwa

+0

@ Mudasobwa technisch gibt es (die 'Autoload' Methode), aber es würde nicht für diesen Fall gelten. Ich habe eine kleine Änderung an der Antwort vorgenommen. Danke, dass du es aufgezeigt hast. –

5

Der zweite Ansatz ist sehr praktisch, wenn Sie etwas dynamisches brauchen. Ruby hat tatsächlich mehrere Bereiche:

# scope one, opened with `class` keyword 
class ... 
    # scope two, opened with `def` keyword 
    def ... 
    end 
end 

Mit class_eval können Sie Bereiche teilen.

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

Dies bedeutet jedoch auch, dass dieser gemeinsam genutzte Bereich nicht als Sammlung von Datenmüll erfasst wird, solange die Klasse existiert – Vasfed

Verwandte Themen