Sie sind in Nuancen von Rubys Objekthierarchie ausgeführt wird und wie Methode Lookups mit inc interagieren bezahlte Module.
Wenn Sie eine Methode für ein Objekt, Ruby walks over the ancestors
list für die Klasse des Objekts aufrufen, suchen Sie nach einer Vorgängerklasse oder einem Modul, das auf diese Methode reagiert. Wenn Sie super
in dieser Methode aufrufen, sind Sie effektiv fortfahren Ihr zu Fuß den Baum von ancestors
, auf der Suche nach dem nächsten Objekt, das auf den gleichen Methodennamen reagiert.
Der Vorfahr Baum für Ihre X
und Y
Klassen wie folgt aussehen:
p X.ancestors #=> [ X, A, Object, Kernel, BaseObject ]
p Y.ancestors #=> [ Y, X, A, Object, Kernel, BaseObject ]
Das Problem ist, dass include
das Modul ein zweites Mal ing, in einer untergeordneten Klasse, hat nicht eine zweite Kopie von injizieren das Modul in der Ahnenkette.
Effektiv was passiert, wenn Sie Y.new.blah
aufrufen, beginnt Ruby nach einer Klasse zu suchen, die auf blah
reagiert. Es geht an Y
und X
vorbei und landet auf A
, das die blah
-Methode einführt. Wenn A#blah
super
aufruft, zeigt der "Zeiger" in Ihrer Vorfahrenliste bereits auf A
, und Ruby sucht weiter nach einem anderen Objekt, das auf Object
, Kernel
reagiert, und dann BaseObject
. Keine dieser Klassen verfügt über eine blah
-Methode, sodass Ihr Aufruf super
fehlschlägt.
Eine ähnliche Sache passiert, wenn ein Modul A
ein Modul B
enthält und dann eine Klasse beide Module A
und B
enthält. Das B
Modul ist nicht zweimal enthalten:
module A; end
module B; include A; end
class C
include A
include B
end
p C.ancestors # [ C, B, A, Object, Kernel, BaseObject ]
Beachten Sie, dass es C, B, A
, nicht C, A, B, A
.
Die Absicht scheint zu sein, damit Sie sicher super
innerhalb von A
's Methoden aufrufen können, ohne sich Gedanken darüber zu machen, wie der Verbrauch von Klassenhierarchien versehentlich A
zweimal enthalten könnte.
Es gibt ein paar Experimente, die verschiedene Aspekte dieses Verhaltens demonstrieren.Das erste ist das Hinzufügen eines blah
Verfahren, um Objekt, das die super
Aufruf passieren lässt:
class Object; def blah; puts "Object::blah"; end; end
module A
def blah
puts "A::blah"
super
end
end
class X
include A
end
class Y < X
include A
end
Y.new.blah
# Output
# A::blah
# Object::blah
Der zweite Versuch ist, zwei Module zu verwenden, BaseA
und A
, welche tut Ursache die Module zweimal eingeführt werden, richtig, in der ancestors
Kette:
module BaseA
def blah
puts "BaseA::blah"
end
end
module A
def blah
puts "A::blah"
super
end
end
class X
include BaseA
end
class Y < X
include A
end
p Y.ancestors # [ Y, A, X, BaseA, Object, ...]
Y.new.blah
# Output
# A::blah
# BaseA::blah
Ein dritte experiement prepend
statt include
verwendet, die das Modul vor platziert des Objekts in der ancestors
Hierarchie und interessanterweise does eine Kopie des Moduls einfügen. Dies ermöglicht es uns, den Punkt zu erreichen, an dem effektiv Y::blah
X::blah
aufruft, die fehlschlägt, weil Object::blah
existiert nicht:
require 'pry'
module A
def blah
puts "A::blah"
begin
super
rescue
puts "no super"
end
end
end
class X
prepend A
end
class Y < X
prepend A
end
p Y.ancestors # [ A, Y, A, X, Object, ... ]
Y.new.blah
# Output
# A::blah (from the A before Y)
# A::blah (from the A before X)
# no super (from the rescue clause in A::blah)
Sehr interessant und lehrreich. Was meinst du mit "Ich kann nicht vollständig erklären" am Anfang deines Eröffnungssatzes. –
@CarySwoveland Ich meinte, dass diese Antwort auf Hypothesen und Experimenten beruht, die diese Hypothese zu stützen scheinen, und nicht auf einer verbindlichen Erklärung und einem Link zu unterstützender Dokumentation. – meagar
@CarySwoveland Jedenfalls habe ich die Formulierung etwas aktualisiert. – meagar