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

时间:2017-01-03 19:52:40

标签: ruby inheritance module mixins super

我认为在类中包含一个模块作为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'

1 个答案:

答案 0 :(得分:9)

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

当您调用对象的方法Ruby walks over the ancestors list作为对象的类时,查找响应该方法的祖先类或模块。当您在该方法中调用super时,您实际上继续走在ancestors的树上,寻找响应的 next 对象使用相同的方法名称。

XY类的祖先树如下所示:

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

问题是include在子类中第二次使用该模块在祖先链中注入该模块的第二个副本。

有效地发生的事情是当你调用Y.new.blah时,Ruby开始寻找响应blah的类。它会越过YX,然后登陆A,这会引入blah方法。当A#blah调用super时,您的祖先列表中的“指针”已经指向A,并且Ruby从该点继续查找响应blah的另一个对象,开始使用ObjectKernel,然后BaseObject。这些类都没有blah方法,因此您的super调用失败。

如果模块A包含模块B,则类似的事情会发生,然后类包含模块ABB模块包含两次:

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

第二个实验是使用两个模块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

第三次体验使用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)