Ruby ruby 的 extend 方法为什么在类和实例上实现的逻辑不一致

lihaidong · 2014年05月19日 · 最后由 lihaidong 回复于 2014年05月20日 · 2984 次阅读

我们先来看下实例对象extend module的情况

module Foo
  def bar
     "bar in the Module Foo"
  end
end

class FooBar
  def bar
     "bar in the class FooBar"
  end
end

f = FooBar.new
f.extend Foo
f.bar 
=> "bar in the Module Foo"

接着看下类对象extend module 的情况

module Foo
   def bar
       "bar in the Module Foo"
   end
end

class FooBar
  class << self
     def bar
        "bar in the class FooBar"
     end
   end
end

FooBar.extend Foo
FooBar.bar
=> "bar in the class FooBar"

再来看下列代码

module Foo
  def bar
     puts super
     "bar in the Module Foo"
  end
end

class FooBar
  def bar
     "bar in the class FooBar"
  end
end

f = FooBar.new
f.extend Foo
f.bar 

bar in the class FooBar
=> "bar in the Module Foo"

类extend

module Foo
   def bar
       "bar in the Module Foo"
   end
end

class FooBar
  class << self
     def bar
        puts super
        "bar in the class FooBar"
     end
   end
end

FooBar.extend Foo
FooBar.bar

bar in the Module Foo
=> "bar in the class FooBar"

原来 extend在实例对象上时,创建了一个虚拟类,本身的那个类作为了虚拟类的超类,对象指针指向了虚拟类,方法的调用优先级自然比超类的高, 然而 extend用在类对象上时,也创建了一个虚拟类,这个虚拟类就变成了本身这个类的超类。虽然知道它的原理,但不知道它为什么会这样实现,求解?

共收到 18 条回复

我认为一个对象的单例方法优先级最高,上面的第二段代码跟我下面的代码结果一致。

module Foo
  def bar
     "bar in the Module Foo"
  end
end

class FooBar
end

f = FooBar.new

class << f
    def bar
        "bar in class FooBar"
    end
end

f.extend Foo
f.bar

@piecehealth 你这么一说,我突然明白了,ruby 中对于单例的处理是创建一个直接虚拟类,本身的类作为虚拟类的超类,谢谢,开发者果然需要多交流

错的抹掉

@palytoxin 如果按此逻辑,foo(FooBar)实例对象通过extend mixin module,那它应该是 (Object(Class) --> Object(eigenclass) --> foo(FooBar) ,但实际上是(Object(Class) --> foo(FooBar) --> Object(eigenclass)

#4楼 @lihaidong 没懂,能举个栗子不, foo(FooBar) 是如何插到Object(Class)和Object(eigenclass)中的?

@palytoxin 帖子中第一段代码

module Foo
  def bar
     "bar in the Module Foo"
  end
end

class FooBar
  def bar
     "bar in the class FooBar"
  end
end

f = FooBar.new
f.extend Foo
p f.bar

f.bar 查找链难道不是 f --> f(eigenclass) ,然后在f(eigenclass) 发现了bar? 怎么会跑到了Object的class和eigenclass的中间

8楼 已删除

@palytoxin 那你觉得它内部的类继承结构是什么样的

@palytoxin @lihaidong @piecehealth,第一段代码里,为什么实例对象去extend模块的时候,继承树里模块会出现在singleton class 和自己对象的中间?

f.singleton_class.ancestors #=> [Foo, FooBar, Object, Kernel, BasicObject]

而调用的时候先去singleton_class里面找?那肯定就是singleton_class位于方法链的最低端,先从这里找了

我是这么想的,singleton_class肯定位于低端。所以f.extend后父类方法被覆盖了。 第2段代码在class << self的时候,就创建了类的singleton_class。 FooBar.extend的时候,不能覆盖已经创建的singleton_class里的同名方法

code show

module A
  def f1
    p 'A f1'
  end
end

a = []
def a.f1
  p 'a f1'
end
a.f1 #=> a f1

a.extend A
a.f1 #=> a f1

看到过一句话:

Object#extend() is simply a shortcut that includes a module in the receiver’s eigenclass.

查找方法顺序是:

  • 先在 eigenclass 中找方法,然后再找类的实例方法

所以先找到了 Foo 中的 bar 方法。

lz 还是先看下 ruby元编程,把eigenclass这个概念弄清楚吧

我又去翻了翻书。 发现之前对include 和module中对方法的继承确实有误解 extend 实际相当于

class << xxx
  include module
end

然后我改写下代码1

module Foo
  def bar
     "bar in the Module Foo"
  end
end

class FooBar
  def bar
     "bar in the class FooBar"
  end
end

f = FooBar.new
class << f
    include Foo
end
f.bar 

代码2

module Foo
   def bar
       "bar in the Module Foo"
   end
end

class FooBar
  class << self
     def bar
        "bar in the class FooBar"
     end
   end
end

class << FooBar
    include Foo
end

FooBar.bar

然后代码2再多手简化下

module Foo
   def bar
       "bar in the Module Foo"
   end
end

class FooBar
  class << self
     def bar
        "bar in the class FooBar"
     end
     include Foo
   end
end

FooBar.bar

不知道这样lz能明白么

然后看一下这些东西的祖先链

module Foo
  def bar
     "bar in the Module Foo"
  end
end

class FooBar
    include Foo
end

class << FooBar
    include Foo
end

FooBar.ancestors #=> [FooBar, Foo, Object, Kernel, BasicObject]

ss = class << FooBar
    self
end
ss.ancestors #=> [Foo, Class, Module, Object, Kernel, BasicObject]

#12楼 @zealinux 这句话简短到位

#14楼 @palytoxin 费心了,我也去翻了翻书,又增加不少理解

#16楼 @lihaidong 没明白就乱说见笑了。这个栗子我还是有点自己的理解 其实上方的代码最重要的区别在class << self这句

class << self
   def bar
      "bar in the class FooBar"
   end
 end

这个打开类,是在eigenclass内,定义了一个instance_method bar

FooBar.singleton_class.instance_methods(false)
#=> [:bar]

FooBar.extend Foo 把Foo混在了eigenclass上方 extend混入,实际上是在eigenclass里调用了include,那么这个模块以虚拟类放在了eigenclass的上方

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

一个定义在eigenclass内,一个被混入放在了eigenclass上 这就是为什么" extend 方法为什么在类和实例上实现的逻辑不一致" 其实是一致的,只不过你用的这个栗子class<<self,正好造成了这种困扰

昨天扣了半天书本,觉得把自己说服了,欢迎讨论,求拍砖: )

#17楼 @palytoxin 你说的挺到位的,我重新梳理下,算是告一段落吧

  1. @zealinux 提及的extend 就是在对象的eigenclass 里include module,所以
FooBar.extend Foo => class << FooBar ; include Foo; end
f.extend Foo =>  class << f; include Foo; end

2.eigenclass 里定义的方法优先级最高,因为ruby内部把它作为当前对象类的直接子类,所以 FooBar.bar 方法查找的第一个类是FooBar对象的eigenclass,在代码一中直接在类定义中通过

class <<  self
   def bar 
     "bar in the class FooBar"
   end
end

定义了eigenclass 的bar 方法,故调用了此方法,mixin 的module作为了proxy类。 f.bar 方法链的一个搜索类也是对象 f 的eigenclass,在此eigenclass内没有找到,在f的eigenclass mixin 的 Foo 模块中找到,故f.bar 调用的是模块的方法

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册