使用模块和继承调用"超级"关键字

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'

mea*_*gar 9

您正在遇到Ruby的对象层次结构的细微差别以及方法查找如何与包含的模块交互.

当您在对象上调用方法时,Ruby会遍历ancestors对象类列表,查找响应该方法的祖先类或模块.当您super在该方法中调用时,您将有效地继续向上走树ancestors,寻找响应相同方法名称的下一个对象.

XY类的祖先树看起来像这样:

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,则类似的事情发生,然后类包括模块AB.该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)