Вы бежите в нюансы иерархии объектов в Ruby, и как метод поиска взаимодействуют с инк luded модулей.
Когда вы вызываете метод для объекта, Ruby walks over the ancestors
list для класса объекта, ищем класс-предок или модуль, который отвечает на этот метод. Когда вы вызываете super
в этом методе, вы эффективно Продолжаете свою ходьбу по дереву ancestors
, ищем следующий объект, который отвечает на то же имя метода.
Предок дерево для X
и Y
классов выглядеть следующим образом:
p X.ancestors #=> [ X, A, Object, Kernel, BaseObject ]
p Y.ancestors #=> [ Y, X, A, Object, Kernel, BaseObject ]
Проблема заключается в том, что include
ИНГ модулю второй раз, в классе ребенка, делает не вводят вторую копию модуль в цепи предков.
Фактически, что происходит, когда вы вызываете Y.new.blah
, Ruby начинает поиск класса, отвечающего blah
. Он проходит мимо Y
и X
, и приземляется на A
, который вводит метод blah
. Когда A#blah
вызывает super
, то «указатель» в свой список предков уже указывает на A
, и Руби возобновило смотреть с этой точки для другого объекта отвечая на blah
, начиная с Object
, Kernel
, а затем BaseObject
. Ни один из этих классов не имеет метода blah
, поэтому ваш вызов super
терпит неудачу.
Аналогичная ситуация возникает, если модуль A
включает в себя модуль B
, а затем класс включает в себя как модуль A
, так и B
. Модуль B
является не включены дважды:
module A; end
module B; include A; end
class C
include A
include B
end
p C.ancestors # [ C, B, A, Object, Kernel, BaseObject ]
Обратите внимание, что это C, B, A
, не C, A, B, A
.
Цель состоит в том, чтобы позволить вам безопасно вызывать super
внутри любых методов A
, не беспокоясь о том, как иерархии классов потребления могут непреднамеренно включать A
дважды.
Есть несколько экспериментов, которые демонстрируют различные аспекты этого поведения.Первый добавляют метод blah
к объекту, который позволяет super
вызова пройти:
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
Второй эксперимент состоит в использовании двух модулей, BaseA
и A
, который делает причины модули должны быть вставлены в два раза, правильно, в ancestors
цепи:
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
Третий experiement использует prepend
, вместо того, чтобы include
, который помещает модуль перед объекта в иерархии ancestors
и интересно делает вставить дубликат копии модуля. Это позволяет нам достичь точки, где эффективно Y::blah
вызывающую X::blah
, который терпит неудачу, потому что Object::blah
не существует:
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)
Очень интересный и поучительный. Что вы подразумеваете под «Я не могу полностью объяснить» в начале вашего вступительного предложения. –
@CarySwoveland Я имел в виду, что этот ответ опирается на гипотезу и эксперименты, которые, кажется, поддерживают эту гипотезу, а не авторитетное объяснение и ссылку на вспомогательную документацию. – meagar
@CarySwoveland В любом случае я немного изменил формулировку. – meagar