2017-01-03 14 views
3

Я думал, что включение модуля в качестве микса в класс «добавило функции» в класс.Вызов ключевого слова «супер» с модулями и наследованием

Я не понимаю, почему это не работает, как ожидалось:

module A 
    def blah 
     super if defined?(super) 
     puts "hello, world!" 
    end 
end 

class X 
    include A 
end 

class Y < X 
    include A 
end 

y = Y.new 
y.blah 

Я ожидал, что «у», чтобы назвать его супер-бла() (так как его включали в классе X?), Но вместо этого я получил:

test.rb: 3: в мля: супер: нет суперкласса метода `бла»

ответ

7

Вы бежите в нюансы иерархии объектов в 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) 
+0

Очень интересный и поучительный. Что вы подразумеваете под «Я не могу полностью объяснить» в начале вашего вступительного предложения. –

+0

@CarySwoveland Я имел в виду, что этот ответ опирается на гипотезу и эксперименты, которые, кажется, поддерживают эту гипотезу, а не авторитетное объяснение и ссылку на вспомогательную документацию. – meagar

+0

@CarySwoveland В любом случае я немного изменил формулировку. – meagar