Ruby类层次结构中的'prepend'行为

px9o7tmv  于 2023-02-12  发布在  Ruby
关注(0)|答案(2)|浏览(116)

我有一个类Base,以及两个继承自Base的类DerivedDerived2,它们都定义了一个函数foo
我还有一个模块Gen,它是prepend-艾德到Base的,它也是prepend-ed到Derived2的,但不是Derived的。
当我在Derived2的示例上调用foo时,结果就好像Gen模块只是prepend-艾德到Base,而不是Derived2。这是预期的行为吗?
下面是上述场景的代码:

module Gen
  def foo
    val = super
    '[' + val + ']'
  end
end

class Base
  prepend Gen

  def foo
    "from Base"
  end
end

class Derived < Base
  def foo
    val = super
    val + "from Derived"
  end
end

class Derived2 < Base
  prepend Gen
  def foo
    val = super
    val + "from Derived"
  end
end

Base.new.foo     # => "[from Base]"

Derived.new.foo  # => "[from Base]from Derived"

Derived2.new.foo # => "[from Base]from Derived"

我期望上面语句的最后一个输出:

[[from Base]from Derived]
lnlaulya

lnlaulya1#

为了帮助您理解,有一个方法Class#ancestors,它告诉您搜索方法的顺序。

Base.ancestors     # => [Gen, Base, Object, Kernel, BasicObject]
Derived.ancestors  # => [Derived, Gen, Base, Object, Kernel, BasicObject]
Derived2.ancestors # => [Gen, Derived2, Gen, Base, Object, Kernel, BasicObject]

所以当你在一个对象上调用一个方法时,这个对象是这个类的一个示例,这个方法将按照这个顺序在相应的列表中被搜索。

  • Prepending将模块放在该列表的前面。
  • 继承将父级链放在子级链的末尾(不完全是,但为了简单起见)。
  • super只是说 “进一步遍历链并找到相同的方法”

对于Base,我们有foo的两个实现-Base中的和Gen中的。Gen的实现将首先被发现,因为模块是前置的。因此,在Base的示例上调用它将调用Gen#foo *= [S],其也将向上搜索该链(经由super= [from Base] *。
对于Derived,模块没有前置,并且我们有继承,因此,第一个发现的实现是在Derived= Sfrom Derived * 和super中搜索来自Base的链的其余部分(也就是上面的段落)= [from Base]from Derived *。
对于Derived2,模块是前置的,所以那里的方法会先被找到 *= [S] *,然后那里的super会在Derived2 *= [Sfrom Derived] * 中找到下一个foo,那里的super会再次为Base *= [[from Base]from Derived] * 演示这个情况。

**EDIT:**似乎直到最近,prepend都会先在祖先链中搜索,只有当模块不存在时才添加模块(类似于include)。更令人困惑的是,如果您先创建父级,从父级继承,在子级中预挂,然后在父级中预挂,您将得到更新版本的结果。

plupiseo

plupiseo2#

对于任何感兴趣的人,你可以实现类似这样的继承,基本上对于从Base继承的每个子类,都将Gen添加到它的祖先:

class Base
  def self.inherited(subclass)
    subclass.prepend(Gen)
  end

  def foo
    "from Base"
  end
end

相关问题