让 `super` 通过包含的模块调用父 class

Let `super` call the parent class over an included module

我有 class Boy 继承 class Person,并包含模块 BipedalPersonBipedal 都有 #two_legs.

的版本
module Bipedal
  def two_legs(name)
    puts "#{name} has exactly two limbs used for walking."
  end
end

class Person
  def two_legs(name)
    puts "#{name} has two human legs."
  end
end

class Boy < Person
  include Bipedal
  attr_accessor :name
  def initialize(name)
    @name = name
  end

  def two_legs
    super(@name)
  end
end

由于 Bipedal 模块包含在 Boy 中,因此 Bipedal#two_legs 优先于 Person#two_legs。当我在 Boy 实例上调用 super 时,模块 Bipedal 优先于父 class Person.

johnny = Boy.new('Johnny')
johnny.two_legs
# >> "Johnny has exactly two limbs used for walking."

我想在一个地方使用一个版本,在另一个地方使用另一个。 Bipedal还有其他东西,没法注释掉include Bipedal。是否有一些标准方法让 Boy#two_legssuper 使用父 class 版本而不是模块版本,如下所示?

johnny.two_legs
# >> "Johnny has two human legs."

我想到了这个:

Boy.superclass.instance_method(:two_legs).bind(self).call(@name)

代替 super(@name),但比我预期的要复杂。

同样,问题是,在调用 super 时,是否有一种标准方法可以强制父 class 优先于模块?

不,没有标准方法强制调用 super 以特定顺序遍历祖先。他们按照预定义的顺序走。看看 the documentation on calling methods:

When you send a message, Ruby looks up the method that matches the name of the message for the receiver. Methods are stored in classes and modules so method lookup walks these, not the objects themselves.

Here is the order of method lookup for the receiver's class or module R:

  • The prepended modules of R in reverse order
  • For a matching method in R
  • The included modules of R in reverse order

If R is a class with a superclass, this is repeated with R's superclass until a method is found.

Once a match is found method lookup stops.

由于您的 class Boy 直接包含模块 Bipedal,并且由于包含的模块在超级 class 之前被搜索,而且一旦匹配到,搜索就会停止发现,superclass Person 从未被检查过。

使用 refinements 时也是如此,因为查找总是在检查 superclass.

之前命中包含的模块

如果您将 include Bipedal 移动到 Person class 中,那么它将按照您期望的方式工作,因为 Boy 不直接包含模块,所以它最终会在 superclass 中搜索定义的方法。

可能还有其他创造性的方法可以实现此目标,例如您提供的 Boy.superclass... 示例,但您询问是否可以在调用 super 时更改查找行为,答案是根据文档没有。

我不认为你可以改变 super 的工作方式,你不能“super super”跳过一个祖先,但你可以使用不同的祖先。如果您只是将 Bipedal 包含在它的某些方法中并想跳过其中的一些方法,那么您可以这样做:

SemiBipedal = Bipedal.dup
SemiBipedal.remove_method(:two_legs)

class Boy < Person
  include SemiBipedal
  #...
end

当然 johnny.is_a? Bipedal 不再成立,Boy.ancestors 在索引 1 处将有 SemiBipedal 而不是 Bipedal 但这可能无关紧要。

一个人可以使用方法 Method#super_method 两次。这是一种普通的 Ruby 方法,我相信它可以作为 "standard way" 让 Boy#two_legs 调用 Person#two_legs 而不是 Bipedal#two_legs.

class Boy < Person
  include Bipedal
  attr_accessor :name
  def initialize(name)
    @name = name
  end
  def two_legs
    method(:two_legs).super_method.super_method.call(@name)
  end
end

willie = Boy.new('Willie')
willie.two_legs
Willie has two human legs.

注意以下事项。

willie.method(:two_legs).super_method.owner
  #=> Bipedal
willie.method(:two_legs).super_method.super_method.owner
  #=> Person