为什么Ruby包含污染不相关的类?



当我使用include C时,我真的很困惑为什么模块B和C中的方法混合到模块A中的类中。include是否以某种方式递归地与调用它的名称空间下面的所有类混合在一起?

module A
  class AClass
    def a_method_
    end
  end
end
module B
  extend self
  def b_method_
  end
end
module C
  extend self
  include B
  def c_method_
    puts A::AClass.new.methods.grep /_method_/
  end
end
C.c_method_
puts "----"
include C
c_method_

结果是,在包含之前,AClass实例正确地只有a_method_方法,但在包含之后,它也可以访问其他两个方法。

a_method_
----
a_method_
c_method_
b_method_

有人能帮我解释一下吗?

如果您将代码尾部更改为:

C.c_method_
puts self.inspect
puts "----"
include C
c_method_

你应该帮助你看到发生了什么。这会给你这样的输出:

a_method_
main
----
a_method_
c_method_
b_method_

那么main业务是什么?Chuck在这个答案中有一个很好的总结:

Ruby中的一切都发生在某个对象的上下文中。顶层的对象称为"main"。它基本上是Object的一个实例,具有特殊的属性,即任何在那里定义的方法都被添加为Object的实例方法(因此它们在任何地方都可用)。

这意味着在顶层说:

include C

相当于说:

Object.send(:include, C)

添加东西到Object会污染一切

你可能会觉得这很有启发性:

puts Object.instance_methods.grep /_method_/
# c_method_
# b_method_

您的方法已添加到所有内容,而不仅仅是AClass !为了真正展示它的奇怪之处,试着将最后一位改为:

class D
  include C
  puts Object.instance_methods.grep /_method_/
end

没有更多输出!您无意中发现了Ruby的main的一个奇怪的、故意的行为(当没有指定其他内容时隐式接收方)。基本动机是,如果您在main中定义了一个方法,您希望能够在任何地方调用它:

def foo
end
# now all of these work:
class A
  foo
end
module B
  foo
end
A.new.instance_eval { foo }
A.singleton_class.class_eval { foo }

这也与作用域分辨率无关;如果你把它改成foo = :bar,你会得到NameError s。这是针对putsgets这样的东西,当你打电话给他们时,你通常不关心接收者是谁。但是这种行为完全不符合Ruby的对象模型,所以matz将其引入:main中定义的每个方法都作为实例方法添加到Object中。

最新更新