我先介绍几个概念。
首先,使用def self.a定义类方法与在类的单例类上定义方法相同:
class C
def self.a; end
class << self
def b; end
end
end
C.method(:a) # => #<Method: C.a>
C.method(:b) # => #<Method: C.b>
此外,对象上的方法是该对象单例类上的实例方法:
C.singleton_class.instance_method(:a) # => #<UnboundMethod: #<Class:C>#a>
C.singleton_class.instance_method(:b) # => #<UnboundMethod: #<Class:C>#b>
如果你看看我们上面是如何定义#b的,你会发现我们没有在它前面加上self,因此这只是一个实例方法。
接下来,#extend 与单例类上的#include 相同:
module M; end
class C1
extend M
end
class C2
class << self
include M
end
end
C1.ancestors # => [#<Class:C2>, M, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
C2.ancestors # => [#<Class:C1>, M, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
注意M 现在是C1 和C2 祖先的一部分。
包含(或扩展)M 也可以通过以下方式实现:
C1.extend M
C2.singleton_class.include M
最后,注意当我们 #include 一个模块时祖先会发生什么:
module M1; end
module M2; end
class C; end
C.include M1
C.ancestors # => [C, M1, Object, Kernel, BasicObject]
C.include M2
C.ancestors # => [C, M2, M1, Object, Kernel, BasicObject]
每个#include 导致模块被插入到祖先链中的接收者(在本例中为C)之后。
现在让我们看看你的定义(省略正文):
module B; end
class A
extend B
end
请记住,#extend 与 #singleton_class 上的 #include 相同。因此,我们可以改写如下:
module B; end
class A; end
A.singleton_class.include B
单例类的祖先现在在第一项之后有B,这是A的单例类,其中定义了类方法(请记住,类方法因此只是该类的单例类上的实例方法有问题):
A.singleton_class.ancestors # => [#<Class:A>, B, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
继续代码的第二部分:
a = A.new
a.extend B
使用#include重写它:
a = A.new
a.singleton_class.include B
让我们检查祖先:
a.singleton_class.ancestors # => [#<Class:#<A:0x00007f83e714be88>>, B, A, Object, Kernel, BasicObject]
同样,#include 将模块放在祖先链中的第一个元素之后,导致B 在A 之前。
这意味着当发送#a 到a(即a.a)时,它将寻找第一个响应#a 的祖先,在这种情况下是B。然后B 将调用super,它将继续沿着祖先链找到响应#a 的A。
现在A.a 会有所不同。记住A的单例类的祖先:
A.singleton_class.ancestors # => [#<Class:A>, B, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
注意B 是如何出现在#<Class:A> 之后的。 #<Class:A> 已经响应#a,这是A 上的类方法。因为该方法不调用super,所以永远不会调用B#a。因此,您不会得到相同的输出。
如果你想在#<Class:A> 之前有B,你必须在A 的单例类前面加上B。 #prepend 在祖先链的最开始插入一个对象,这与 #include 不同,后者将其插入到第一项之后(您必须删除代码中的 extend B 才能使其正常工作,否则如果 B 是已经是祖先了):
A.singleton_class.prepend B
A.singleton_class.ancestors # => [B, #<Class:A>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
现在调用A.a 将产生与a.a 相同的结果,即打印BA。