我试图更好地理解Ruby单例和类继承。我到处读到那个
def self.method_name; end`
相当于
class << self
def method_name; end
end
但如果这是真的,那么我希望print_constant_fails
起作用,但事实并非如此。这是怎么回事?
class SuperExample
A_CONSTANT = "super example constant"
end
class SubExample < SuperExample
def self.print_constant_works_1
puts A_CONSTANT
end
class << self
def print_constant_works_2
puts self::A_CONSTANT
end
def print_constant_fails
puts A_CONSTANT
end
end
end
pry(main)> SubExample.print_constant_works_1
super example constant
pry(main)> SubExample.print_constant_works_2
super example constant
pry(main)> SubExample.print_constant_fails
NameError: uninitialized constant #<Class:SubExample>::A_CONSTANT
from (pry):13:in `print_constant_fails'
你遇到了一个常见的Ruby陷阱 - 常量查找。
常量查找中最重要的概念是Module.nesting
(与方法查找不同,方法查找的主要起点是self
)。此方法为您提供当前模块嵌套,Ruby 解释器在解析常量标记时直接使用该嵌套。修改嵌套的唯一方法是使用关键字class
和module
,它只包括使用该关键字的模块和类:
class A
Module.nesting #=> [A]
class B
Module.nesting #=> [A::B, A]
end
end
class A::B
Module.nesting #=> [A::B] sic! no A
end
在元编程中,可以使用Class.new
或Module.new
动态定义模块或类 - 这不会影响嵌套,并且是导致错误的极其常见的原因(啊,还值得一提 - 常量是在 Module.nesting 的第一个模块上定义的):
module A
B = Class.new do
VALUE = 1
end
C = Class.new do
VALUE = 2
end
end
A::B::VALUE #=> uninitialized constant A::B::VALUE
A::VALUE #=> 2
上面的代码将生成两个警告:一个用于常量 A::VALUE 的双重初始化,另一个用于重新分配常量。
如果它看起来像"我永远不会这样做" - 这也适用于RSpec.describe
中定义的所有常量(内部调用 Class.new),所以如果你在 rspec 测试中定义一个常量,它们肯定是全局的(除非你明确声明要用self::
定义它的模块)
现在让我们回到你的代码:
class SubExample < SuperExample
puts Module.nesting.inspect #=> [SubExample]
class << self
puts Module.nesting.inspect #=> [#<Class:SubExample>, SubExample]
end
end
解析常量时,解释器首先遍历Module.nesting
中的所有模块,并在该模块中搜索此常量。因此,如果嵌套[A::B, A]
并且我们正在寻找带有令牌C
的常量,解释器将首先查找A::B::C
,然后再查找A::C
。
但是,在您的示例中,这在两种情况下都会失败:)。然后解释器开始在 Module.nesting 中搜索第一个(也是唯一的第一个)模块的祖先。SubrExample.singleton_class.ancestors
为您提供:
[
#<Class:SubExample>,
#<Class:SuperExample>,
#<Class:Object>,
#<Class:BasicObject>,
Class,
Module,
Object,
Kernel,
BasicObject
]
如您所见 - 没有SuperExample
模块,只有它的单例类 - 这就是为什么class << self
内的常量查找失败的原因(print_constant_fails
)。
Subclass
的祖先是:
[
SubExample,
SuperExample,
Object,
Kernel,
BasicObject
]
我们在那里SuperExample
,所以解释器将设法在这个嵌套中找到SuperExample::A_CONSTANT
。
我们只剩下print_constant_works_2
.这是单例类上的实例方法,因此此方法中的self
只是SubExample
。因此,我们正在寻找SubExample::A_CONSTANT
- 持续查找首先搜索SubExample
,当失败时,搜索它的所有祖先,包括SuperExample
。
它与范围有关。当您在class << self
内时,范围与您在class Something
内时不同。因此,在class << self
内部实际上没有称为A_CONSTANT
的常数。
在 Ruby 中,每个 Ruby 的常量都有自己的路径,从带有符号::
的主(根)开始(默认,我们不需要声明这个符号)。class
不应该被视为关键字(一种静态),但是一种方法(一种动态)负责创建一个指向该class object
的class object
和class name constant
,并且所有Constants
都是在没有路径(P::Q::...)的类中定义的,将自动被视为属于路径:: ClassName::A_CONSTANT
创建的类。
GLOBAL = 1
class SuperExample
A_CONSTANT = "super constant" # <-- ::SuperExample::A_CONSTANT
end
puts ::GLOBAL # 1
puts ::SuperExample # SuperExample
puts ::SuperExample::A_CONSTANT # "super constant"
看起来子类中的常量路径与父类具有相同的级别
class SubExample < SuperExample
end
puts ::SubExample::A_CONSTANT # "super constant"
正如我注意到的,类块内的所有常量(不带::
)都将在类路径下设置路径,因此当您获得它们时,要么使用显式常量路径,要么在常量所属的类下获得:
class SubExample < SuperExample
def self.print_constant_works_1
puts A_CONSTANT # ::SubExample::A_CONSTANT
end
def another
puts A_CONSTANT # ::SubExample::A_CONSTANT
end
def yet_another
puts SubExample::A_CONSTANT # ::SubExample::A_CONSTANT
end
end
现在检查class << self
class SubExample < SuperExample
class << self
puts self # <Class:SubExample>
def print_constant_works_2
puts self::A_CONSTANT # declare explicitly constant path
end
def print_constant_fails
puts A_CONSTANT # not declare explicitly <-- Class:SubExample::A_CONSTANT
end
end
end
如您所见,class << self
内部的类是不同的,因此方法print_constant_fails
内部常量A_CONSTANT
路径指向未定义任何常量A_CONSTANT
的Class:SubExample
,因此uninitialized constant #<Class:SubExample>::A_CONSTANT
引发错误。
同时print_constant_works_2
将起作用,因为我们声明了显式常量路径,在这种情况下self
实际上是SubExample
(调用SubExample.print_constant_works_2
)。
现在让我们尝试在print_constant_fails
内部使用显式路径::A_CONSTANT
def print_constant_fails
puts ::A_CONSTANT
end
引发的误差是uninitialized constant A_CONSTANT
,::A_CONSTANT
被认为是一个全局常数(main)。