2017-10-18 6 views
1

Betrachten Sie den folgenden Code ein:Warum findet Ruby keine Konstanten, die in der Klasse des Aufrufers definiert sind?

class MyClass 
    def foo_via_method 
    foo_method 
    end 

    def foo_via_constant 
    FOO_CONSTANT 
    end 
end 

class SubClass < MyClass 
    FOO_CONSTANT = "foo" 

    def foo_method 
    FOO_CONSTANT 
    end 
end 

Die beiden hier Instanzmethoden verhalten sich anders:

sub_class_instance = SubClass.new 

### THIS WORKS ### 
sub_class_instance.foo_via_method 
# => "foo" 

### THIS DOESN'T ### 
sub_class_instance.foo_via_constant 
# NameError: uninitialized constant MyClass::FOO_CONSTANT 

Die Version, die in der Unterklasse auf ein Verfahren bezieht sich der gewünschte Wert zurückgegeben, aber die Version, die sich bezieht Eine Konstante in der Unterklasse löst einen Fehler aus. Das Rätsel ist also: Warum funktioniert die Version, die eine Methode verwendet, aber die Version, die die Konstante verwendet, fehlschlägt?

+0

Dies ist ein unverständliches Design für mich. In OOP würde ich niemals erwarten, dass ich nach etwas Ausschau halten kann, das in einer Kindklasse definiert ist, außer (und mein Verständnis ist eine Debatte über den Wert davon) eine Methode, deren Verhalten außer Kraft gesetzt wird. Sicherlich sollten Sie niemals erwarten, dass etwas, das nur in einer Unterklasse definiert ist, für seine Eltern sichtbar ist. –

+0

Von mir: Version, die Methode verwendet - Aufrufe Konstante im Bereich der Klasse 'SubClass', so ist es Arbeit, in einem anderen Sie' FOO_CONSTANT' aufrufen und versuchen, diese Konstante im Bereich von 'MyClass' zu finden, um es praktikabel zu machen: nur schreibe 'SubClass :: FOO_CONSTANT' –

Antwort

2

Dies war ein Puzzle, das ich in realen Produktionscode begegnete. Ich habe eine detaillierte Erklärung dazu geschrieben, was in this blog post vor sich geht.

Hier ist die TLDR: Ruby verwendet einen viel komplexeren Algorithmus zum Auflösen von Konstanten als für Methoden. Eine Phase der Konstanten-Nachschlage-Routine beinhaltet das Durchsehen der Superklassen-Kette für die Definition. Diese Phase ähnelt der Methoden-Lookup-Routine und vertieft das Rätsel, warum sich Methoden und Konstanten in der in der Frage dargestellten Weise unterscheiden.

Die Erklärung ist, dass sich die beiden Superklassen-Kettenroutinen darin unterscheiden, wo sie starten, d. H. Welche Klasse die Wurzel der Kette ist.

Methodensuche beginnt mit der Klasse self, wobei self der Empfänger des ursprünglichen Methodenaufrufs ist. Im Beispiel ist sub_class_instance der Empfänger und SubClass ist wo die Suche beginnt. SubClass implementiert foo_method, so ist alles in Ordnung.

Bei Konstanten bezieht sich Ruby nicht auf einen Empfänger, da konstante Aufrufe nicht auf einen Empfänger bezogen sind. Stattdessen beginnt die Suche nach Konstantenklassen mit der Klasse, die im lexikalischen Bereich geöffnet ist, in dem der Konstantenaufruf stattfindet. In dem Beispiel ist die Klasse, die geöffnet ist MyClass, also das ist, wo Ruby beginnt, nach der Konstante zu suchen - und findet es nie.

+1

Ich denke, es kann als" lexikalisch nach außen, dann dynamisch nach oben "zusammengefasst werden, aber IIRC Ich fand Beispiele, wo das nicht wirklich gilt. Ich weiß nicht mehr, was sie waren. Yugui schrieb einmal eine Reihe von Artikeln über die verschiedenen impliziten Kontexte und Suchvorgänge in Ruby, aber während sie erwähnte, an einem Artikel über das ständige Nachschlagen zu arbeiten, tauchte dieser Artikel nie auf. –

+0

Ich war erstaunt, wie komplex das ständige Nachschlagen ist, als ich anfing, in YARV zu graben. Rubys glatte Oberfläche ist das Ergebnis einer knappen Implementierung! "Natürlich, nicht einfach." – hoffm

Verwandte Themen