Ruby mixins:扩展和包含

Posted

技术标签:

【中文标题】Ruby mixins:扩展和包含【英文标题】:Ruby mixins: extend and include 【发布时间】:2013-07-07 08:04:34 【问题描述】:

我一直在阅读一些关于 Ruby 的 mixin 方法的文章,extendinclude,但我仍然不太确定这种行为。我知道extend 会将给定模块的实例方法作为单例方法添加到进行扩展的模块中,并且include 本质上会将模块的内容(方法、常量、变量)附加到执行扩展的模块中包括,在接收器中有效地定义它们。

但是,经过一些修改,试图了解行为将如何表现,我有几个问题。这是我的测试设置:

module Baz
  def blorg
    puts 'blorg'
  end
end

module Bar
  include Baz
  def blah
    puts 'blah'
  end
end

module Foo
  extend Bar
end

class Bacon
  extend Bar
end

class Egg
  include Bar
end

正如我所料,模块Bar 获得了在Baz (#blorg) 中定义的实例方法,就好像它们是由于包含方法而在其自身中定义的一样,而类Bacon 获得了单例方法 Bacon::blahBacon::blorg 的扩展。

Bacon.blah  # => blah
Bacon.blorg # => blorg

Egg 类获得Bar#blah 和现在的#blorg)中定义的方法作为实例方法。

Egg.new.blah  # => blah
Egg.new.blorg # => blorg

我明白了,这很好。

但是,我不明白使用 #ancestors#is_a? 方法得到的响应。

Bacon.ancestors  # => [Bacon, Object, Kernel, BasicObject]
Bacon.is_a? Bar  # => true

Egg.ancestors    # => [Egg, Bar, Baz, Object, Kernel, BasicObject]
Egg.is_a? Bar    # => false

似乎扩展一个模块会导致#is_a?方法在查询该模块时返回true,但它没有添加到类的祖先中,反之亦然:该类包含被包含的模块,但#is_a? 方法在查询时返回false。为什么会这样?

【问题讨论】:

+1 这个问题的格式很好。 【参考方案1】:

不同之处在于include 会将包含类添加到包含类的祖先,而extend 会将扩展类添加到扩展类的单例类的祖先。唷。让我们先观察会发生什么:

Bacon.ancestors
#=> [Bacon, Object, Kernel, BasicObject]

Bacon.singleton_class.ancestors
#=> [Bar, Baz, Class, Module, Object, Kernel, BasicObject]

Bacon.new.singleton_class.ancestors
#=> [Bacon, Object, Kernel, BasicObject]

Bacon.is_a? Bar
#=> true

Bacon.new.is_a? Bar
#=> false

对于Egg

Egg.ancestors
#=> [Egg, Bar, Baz, Object, Kernel, BasicObject]

Egg.singleton_class.ancestors
#=> [Class, Module, Object, Kernel, BasicObject]

Egg.new.singleton_class.ancestors
#=> [Egg, Bar, Baz, Object, Kernel, BasicObject]

Egg.is_a? Bar
#=> false

Egg.new.is_a? Bar
#=> true

所以foo.is_a? Klass实际上做的是检查foo.singleton_class.ancestors是否包含Klass。发生的另一件事是,当创建实例时,类的所有祖先都成为实例的单例类的祖先。因此,对于任何类的所有新创建的实例,这将评估为 true:

Egg.ancestors == Egg.new.singleton_class.ancestors

那么这一切意味着什么? extendinclude 在不同的层次上做同样的事情,我希望下面的例子能清楚地说明这一点,因为扩展类的两种方式本质上是等价的:

module A
  def foobar
    puts 'foobar'
  end
end

class B
  extend A
end

class C
  class << self
    include A
  end
end

B.singleton_class.ancestors == C.singleton_class.ancestors
#=> true

其中class &lt;&lt; self 只是进入单例类的奇怪语法。所以extend 只是单例类中include 的简写。

【讨论】:

【参考方案2】:
Egg.is_a? Egg # => false

包含(有效地)更改Egg 类的实例。虽然不太一样,但和做类似的事情很相似

class Egg < Bar
end

extend 的时候会添加类方法,所以这和做类似

class Bacon
  class << self
    include Bar
  end
end

你可以把它想象成包含更改类的实例,而扩展实际上更改了类。

【讨论】:

也许你打错了东西,但是当我输入Egg.new.is_a? Egg 时,它返回true。你的意思是Egg.is_a? Egg # =&gt; false

以上是关于Ruby mixins:扩展和包含的主要内容,如果未能解决你的问题,请参考以下文章

Ruby 中的“包含模块”和“扩展模块”有啥区别? [复制]

主干:mixin 事件从扩展视图扩展

vue 扩展现有组件的方法

vue 扩展现有组件的方法

Ruby Squeel 库弃用核心扩展

Ruby mixins 和调用超级方法