d0b*_*0bz 4 ruby inheritance module super mixins
我认为在类中包含一个模块作为mixin"将函数添加到"类中.
我不明白为什么这不能按预期工作:
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
Run Code Online (Sandbox Code Playgroud)
我期待"y"调用它的超级blah()(因为它包含在X类中?)但我得到了:
test.rb:3:in blah:super:没有超类方法`blah'
您正在遇到Ruby的对象层次结构的细微差别以及方法查找如何与包含的模块交互.
当您在对象上调用方法时,Ruby会遍历ancestors对象类的列表,查找响应该方法的祖先类或模块.当您super在该方法中调用时,您将有效地继续向上走树ancestors,寻找响应相同方法名称的下一个对象.
您X和Y类的祖先树看起来像这样:
p X.ancestors #=> [ X, A, Object, Kernel, BaseObject ]
p Y.ancestors #=> [ Y, X, A, Object, Kernel, BaseObject ]
Run Code Online (Sandbox Code Playgroud)
问题是,include荷兰国际集团该模块的第二次,在子类中,并没有在祖先链注入模块的第二个副本.
实际上,当你调用时Y.new.blah,Ruby开始寻找一个响应的类blah.它走过Y,并X和土地上A,介绍的blah方法.当A#blah所调用super的"指针"到你的祖先列表已经指向A和Ruby重新开始从该点寻找另一个对象响应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 ]
Run Code Online (Sandbox Code Playgroud)
请注意,它C, B, A不是C, A, B, A.
意图似乎是允许您安全地调用super任何A方法,而不必担心类层次结构可能会无意中包含A两次.
有一些实验证明了这种行为的不同方面.第一个是向blahObject 添加一个方法,它允许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
Run Code Online (Sandbox Code Playgroud)
第二个实验是使用两个模块,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
Run Code Online (Sandbox Code Playgroud)
第三个实验使用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)
Run Code Online (Sandbox Code Playgroud)