我认为在类中包含一个模块作为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
我期待“y”调用它的超级blah()(因为它包含在X类中?)但我得到了:
test.rb:3:in blah:super:没有超类方法`blah'
答案 0 :(得分:9)
您正在遇到Ruby的对象层次结构的细微差别,以及方法查找如何与包含的模块进行交互。
当您调用对象的方法Ruby walks over the ancestors
list作为对象的类时,查找响应该方法的祖先类或模块。当您在该方法中调用super
时,您实际上继续走在ancestors
的树上,寻找响应的 next 对象使用相同的方法名称。
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
,并且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 ]
请注意,它是C, B, A
,而不是C, A, B, A
。
意图似乎是允许您在任何super
方法中安全地调用A
,而不必担心类层次结构可能会无意中包含A
两次。
有一些实验证明了这种行为的不同方面。第一种是向Object添加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
第三次体验使用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)