module B
def a
print 'B'
super
end
end
class A
extend B
def a
print "A"
end
def self.a
print "A"
end
end
a = A.new
a.extend B
puts a.a # => BA
puts A.a # => A
为什么Kernel#extend方法对类对象和类实例对象的工作方式不同?如果我们扩展一个实例,它看起来像是在继承链中加了模块,但是如果我们扩展了一个类,它将模块放在类之上。
答案 0 :(得分:3)
首先让我介绍一些概念。
首先,使用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
与#include
上的#singleton_class
相同。因此,我们可以将其重写如下:
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
的第一个祖先,即{{1} } 在这种情况下。 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
之前加入B
,则必须在B之前加入#<Class:A>
的单身人士课。 A
在祖先链的最开始插入一个对象,与#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
。
答案 1 :(得分:0)
A.singleton_class.ancestors
#=> [#<Class:A>, B, #<Class:Object>, #<Class:BasicObject>, Class, Module,
# Object, Kernel, BasicObject]
A.method(:a).owner
#=> #<Class:A>
所以我们不要为此感到惊讶
A.a
打印"A"
,而不打印"B"
。
现在让我们考虑在B
的实例上扩展A
:
aa = A.new
aa.extend B
aas = aa.singleton_class
#=> #<Class:#<A:0x00005b19441bf6a8>>
aas.methods.include?(:a)
#=> true
aa.method(:a).owner
#=> B
aas.superclass
#=> A
m = aa.method(:a).super_method
#=> #<Method: A#a>
m.owner
#=> A
因此
aa.a
BA
首先调用在a
的单例类中定义的方法aa
,该方法打印字母"B"
,然后调用其超类A
来执行{{ 1}},该方法为A.a
,导致其打印A::a
。