简体   繁体   English

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

[英]Calling “super” keyword with modules and inheritance

I thought that including a module as a mixin in a class "added the functions" to the class. 我认为在类中包含一个模块作为mixin“将函数添加到”类中。

I do not understand why this does not work as expected: 我不明白为什么这不能按预期工作:

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

I was expecting "y" to call its super blah() (since its included in class X?) but instead i got: 我期待“y”调用它的超级blah()(因为它包含在X类中?)但我得到了:

test.rb:3:in blah: super: no superclass method `blah' test.rb:3:in blah:super:没有超类方法`blah'

You're running into nuances of Ruby's object hierarchy and how method lookups interact with included modules. 您正在遇到Ruby的对象层次结构的细微差别以及方法查找如何与包含的模块交互。

When you invoke a method on a object, Ruby walks over the ancestors list for the object's class, looking for an ancestor class or module that responds to that method. 当您在对象上调用方法时, Ruby会遍历对象类ancestors列表 ,查找响应该方法的祖先类或模块。 When you invoke super in that method, you're effectively continuing your walking up the tree of ancestors , looking for the next object that responds to the same method name. 当您在该方法中调用super时,您将有效地继续沿着ancestors树行走,寻找响应相同方法名称的下一个对象。

The ancestor tree for your X and Y classes look like this: XY类的祖先树如下所示:

p X.ancestors #=> [ X, A, Object, Kernel, BaseObject ]
p Y.ancestors #=> [ Y, X, A, Object, Kernel, BaseObject ]

The problem is that include ing the module a second time, in a child class, does not inject a second copy of the module in the ancestors chain. 问题是, include荷兰国际集团该模块的第二次,在一个子类, 注的祖先链模块的第二个副本。

Effectively what is happening is when you invoke Y.new.blah , Ruby begins looking for a class that responds to blah . 实际上发生的事情就是当你调用Y.new.blah ,Ruby开始寻找一个响应blah的类。 It walks past Y , and X , and lands on A which introduces the blah method. 它走过YX ,然后登陆A ,它引入了blah方法。 When A#blah invokes super , the "pointer" into your ancestor list is already pointing at A , and Ruby resumes looking from that point for another object responding to blah , starting with Object , Kernel , and then BaseObject . A#blah调用super ,你的祖先列表中的“指针”已经指向A ,并且Ruby从该点继续寻找另一个响应blah对象,从ObjectKernel ,然后是BaseObject None of these classes have a blah method, so your super invocation fails. 这些类都没有blah方法,所以你的super调用失败了。

A similar thing happens if a module A includes a module B , and then a class includes both module A and B . 如果模块A包括模块B ,则类似的事情发生,然后类包括模块AB The B module is not included twice: 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 ]

Note that it's C, B, A , not C, A, B, A . 注意它是C, B, A ,而不是C, A, B, A

The intent would seem to be to allow you to safely invoke super inside any of A 's methods without worrying about how consuming class hierarchies may inadvertently include A twice. 意图似乎是允许您在任何A方法中安全地调用super ,而不必担心类层次结构可能无意中包含A两次。


There are a few experiments that demonstrate different aspects of this behavior. 有一些实验证明了这种行为的不同方面。 The first is adding a blah method to Object, which allows the super call to pass: 第一个是向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

The second experiment is to use two modules, BaseA and A , which does cause the modules to be inserted twice, correctly, in the ancestors chain: 第二个实验是使用两个模块, BaseAA ,它确实使模块在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

A third experiement uses prepend , instead of include , which places the module in front of the object in the ancestors hierarchy and interestingly does insert a duplicate copy of the module. 第三个实验使用prepend而不是include ,它将模块放在 ancestors层次结构中的对象前面 ,有趣的插入模块的副本。 This allows us to reach the point where effectively Y::blah invokes X::blah , which fails because Object::blah does not exist: 这允许我们达到有效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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM