关于 ruby??:用模块和继承调用”super”关键字

Calling "super" keyword with modules and inheritance

我认为在一个类中包含一个作为 mixin 的模块"将功能"添加到了类中。

我不明白为什么这不能按预期工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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: no superclass method `blah'


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

当您调用对象的方法时,Ruby 会遍历对象类的 ancestors 列表,寻找响应该方法的祖先类或模块。当您在该方法中调用 super 时,您实际上是在继续沿着 ancestors 的树向上走,寻找响应相同方法名称的下一个对象。

XY 类的祖先树如下所示:

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

问题在于,在子类中第二次 include 执行该模块时,不会在祖先链中注入该模块的第二个副本。

实际上,当您调用 Y.new.blah 时,Ruby 开始寻找响应 blah 的类。它经过 YX,并降落在引入 blah 方法的 A 上。当 A#blah 调用 super 时,祖先列表中的"指针"已经指向 A,Ruby 从该点继续寻找响应 blah 的另一个对象,从 Object 开始,Kernel,然后是 BaseObject。这些类都没有 blah 方法,因此您的 super 调用失败。

如果一个模块 A 包含一个模块 B,然后一个类同时包含模块 AB,也会发生类似的情况。 B 模块未包含两次:

1
2
3
4
5
6
7
8
9
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

其意图似乎是允许您在任何 A 的方法中安全地调用 super,而不必担心消费类层次结构可能会无意中包含两次 A

有一些实验证明了这种行为的不同方面。第一个是给 Object 添加一个 blah 方法,它允许 super 调用通过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 链中正确插入两次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 不存在而失败:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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)