gpt4 book ai didi

ruby - 为什么类实例上的扩展方法与继承的工作方式不同?

转载 作者:太空宇宙 更新时间:2023-11-03 18:03:26 25 4
gpt4 key购买 nike

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 方法对类对象和类实例对象的工作方式不同?如果我们扩展一个实例,看起来它会在继承链中添加模块,但如果我们扩展一个类,它会将模块放在类之上。

最佳答案

我先介绍几个概念。

首先,使用 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现在是祖先的一部分C1C2以同样的方式。

包含(或扩展)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 .

这意味着当发送#aa (即 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永远不会被调用。因此,您不会得到相同的输出。

如果你想要B之前#<Class:A> ,您必须将 B 添加到 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 相同的结果,即打印BA .

关于ruby - 为什么类实例上的扩展方法与继承的工作方式不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55991183/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com