Ruby导入的方法是否总是私有的?

5
这最好通过一个例子来解释:
file1.rb:
def foo
  puts 123
end

file2.rb:

class A
  require 'file1'
end
A.new.foo

如果直接调用A.new.foo()会报错 "': private method 'foo' called"。

我可以通过 A.new.send("foo") 的方式绕过错误,但有没有办法使导入的方法变为公共方法?

编辑:澄清一下,我不是混淆了include和require。此外,我不能使用常规的引用(正如许多人正确指出的那样),因为这是元编程设置的一部分。我需要允许用户在运行时添加功能;例如,他可以说“run-this-app --include file1.rb”,并且应用程序将根据他在file1.rb中编写的代码而表现出不同的行为。抱歉,之前解释得不够清楚。

编辑:在阅读Jorg的答案后,我意识到我的代码与预期的行为不完全相符,他完美地回答了我的(误导性的)问题。我正在尝试做类似于 str=(entire file1.rb as string); A.class_exec(str) 的事情。


你尝试过 A.new.instance_eval{foo} 吗?对我来说不起作用(ruby 1.9.2)。 - knut
你是否混淆了 requireinclude - Andrew Grimm
@AndrewGrimm 不幸的是,我没有。我正在寻找一种廉价的方法来动态扩展程序。整个应用程序相当复杂,但基本上字符串“file1.rb”来自用户输入。 - alexloh
3个回答

10

这是在Ruby中做这件事的一种不好的方式。尝试使用模块通过混合来代替:

file1.rb:

module IncludesFoo
  def foo
    puts 123
  end
end

file2.rb:

require 'file1.rb'

class A
  include IncludesFoo
end

A.new.foo
# => 123

'file1.rb'和它的内容都来自用户输入并且是动态加载的。基本上,用户可以在运行时输入路径,应用程序将加载适当的Ruby文件,该文件将改变现有功能,而无需重新启动等操作。抱歉,我应该让这更清晰些。 - alexloh

7
Ruby中的全局过程并不是真正的全局过程,它们像其他所有方法一样都是方法。特别地,当您定义类似全局过程的内容时,实际上正在定义Object的私有实例方法。由于Ruby中的每个代码片段都在对象的上下文中进行评估,因此可以使用这些方法就像它们是全局过程一样,因为默认接收者是self,而self是其类继承自Object的对象。因此,这段代码:

# file1.rb

def foo
  puts 123
end

实际上等价于

# file1.rb

class Object
  private

  def foo
    puts 123
  end
end

现在你有一个名为foo的“全局过程”,你可以像这样调用它:
foo

你可以这样称呼它的原因是,该调用实际上等同于
self.foo

self 是一个包含在其祖先链中的 Object 对象,因此它继承了私有的 foo 方法。

[注意:准确地说,私有方法不能用显式接收者调用,即使这个显式接收者是 self。所以,更正确的是等价于 self.send(:foo) 而不是 self.foo。]

你在 file2.rb 中的 A.new.foo 是一个红鲱鱼:你同样可以尝试 Object.new.foo[].foo 或者 42.foo 并得到相同的结果。

顺便提一下:putsrequire 本身就是这种「全局过程」的例子,它们实际上是 Object(或者更确切地说,是混入到 Object 中的 Kernel)上的私有方法。

另外注意一点:在类定义中放置 require 调用非常不好的风格,因为这会让人误认为 require 的代码在某种程度上被作用域化或命名空间化了,而这当然是错误的。 require 只是运行文件中的代码,仅此而已。

因此,虽然

# file2.rb

class A
  require 'file1.rb'
end

虽然“<br>”是有效的代码,但也很容易令人困惑。使用以下语义等效的代码会更好:

# file2.rb

require 'file1.rb'

class A
end

这样代码的读者就很清楚,file1.rb在任何情况下都不会被作用域或命名空间在A内。

此外,通常更喜欢省略文件扩展名,即使用require 'file1'而不是require 'file1.rb'。这样可以将Ruby文件替换为例如本地代码(对于MRI、YARV、Rubinius、MacRuby或JRuby)、JVM字节码的.jar.class文件(对于JRuby)、CIL字节码的.dll文件(对于IronRuby)等,无需更改任何require调用。

最后一个评论:规避访问保护的惯用方式是使用send,而不是instance_eval,即使用A.new.send(:foo)代替A.new.instance_eval {foo}


谢谢!这个答案真的很全面,消除了我曾经的误解 - require 就像 C 的 include 一样复制粘贴文本。是否有一种方法可以在运行时将 file1.rb 的内容动态地添加为类 A 的方法?file1 的内容和位置仅在运行时才知道,用户不知道 A。或者我能找到文件中定义的方法或模块列表吗? - alexloh

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接