Ruby子类化Struct时块内方法归属及作用域规则疑问
Ruby 块内方法定义的归属与可见性规则解析
涉及的两段示例代码
Struct 场景示例
Struct::new 相关示例代码如下:
Customer = Struct.new('Customer', :name, :address) do |new_class| p "The new subclass is #{new_class}" def greeting "Hello #{name} at #{address}" end end # => Struct::Customer dave = Customer.new('Dave', '123 Main') dave # => #<struct Struct::Customer name="Dave", address="123 Main"> dave.greeting # => "Hello Dave at 123 Main"
迭代器场景示例
[1,2,5].each { |x| public def m1 a a + self end }[0].m1 3
这段代码运行返回结果为4,但和Struct场景不同:each迭代器块内直接定义的方法默认为私有方法,必须显式声明public才能被外部调用。
底层规则说明
1. Struct.new 块的特殊上下文处理
Struct.new在完成新Struct子类的创建后,会调用class_eval执行传入的配置块,这是所有类/模块构造方法的标准行为:
class_eval会直接把块的执行上下文(即块内的self)切换为刚创建的新类,所以块内直接写def定义方法,和常规类定义语法class SomeClass; def xxx; end; end的效果完全一致,方法会直接成为新类的实例方法,默认可见性为public。- 同时
Struct.new也会把新创建的类作为块参数传入,因此块形参new_class和块内的self指向同一个新类对象,两种方式都可以操作新类。
2. 普通迭代器块的上下文规则
Array#each是普通迭代方法,不会对传入块的上下文做任何修改,块内的self完全继承自定义块时的外层上下文:
- 如果代码在Ruby顶层编写,块内
self就是顶层main对象,对应Object类的上下文。Ruby有明确规则:在顶层(所有类/模块定义之外)直接用def定义的方法,会默认成为Object类的私有实例方法。这类方法所有Ruby对象都能继承,但因为是私有方法,调用时不能指定显式接收者。 - 块内写的
public本质是调用当前上下文的可见性切换方法,把后续定义的方法可见性改成public,因此后面定义的m1就成了Object的公有实例方法。数组中的Integer是Object的子类,自然可以调用m1,执行时self是整数0,加传入的参数3就得到返回值4。
两个场景的差异本质
核心区别只有一点:接收块的方法是否通过class_eval/module_eval修改了块的执行上下文:
- 所有创建类/模块的内置方法,包括
Struct.new、Class.new、Module.new,都会在创建完对应类/模块后,用class_eval把块放到新类/模块的上下文中执行,块内定义方法的行为和普通类/模块定义体内完全一致,方法默认public。 - 普通迭代方法(比如
each、map、times等)不会改动块上下文,块内self和外层保持一致:如果在顶层调用,块内self就是main,直接def定义的就是Object的私有实例方法,必须手动声明public才能通过显式接收者调用。
内容的提问来源于stack exchange,提问作者ByteEater
相关产品推荐
相关产品推荐

