理解 Ruby 中的私有方法

52
class Example
 private
 def example_test
  puts 'Hello'
 end
end

e = Example.new
e.example_test

这当然行不通,因为我们指定了显式接收者 - 示例的实例 (e),而这违反了“私有规则”。

但我不明白,为什么不能在 Ruby 中这样做:

class Foo
 def public_m
  self.private_m # <=
 end
 private
 def private_m
  puts 'Hello'
 end
end

Foo.new.public_m
public_m 方法内的当前对象(即 self)是 Foo 的实例。那么为什么它不被允许呢?要解决这个问题,我必须将 self.private_m 改为只有 private_m。但为什么会有这种差异呢?难道不是 selfpublic_m 内是 Foo 的一个实例吗?裸的 private_m 调用的接收者是谁呢?难道不是 self 吗?你省略了它,因为 Ruby 将会为你调用(在 self 上调用 private_m)。

希望我没有把事情搞得太复杂,我对 Ruby 还很生疏。


编辑: 感谢所有回答者。整合了所有答案后,我终于理解了显而易见的事实(对于从未看过 Ruby 类似语言的人来说并不是那么显而易见): self 本身既可以是显式的也可以是隐式的接收者,这就造成了差异。所以有两条规则,如果您想调用私有方法:自己必须是隐式接收者,并且这个自己必须是当前类的一个实例(在实例方法定义内部 self 才会成为当前类的实例 - 在方法执行期间)。如果我理解有误,请纠正我。

class Example 

 # self as an explicit receiver (will throw an error)
 def explicit 
  self.some_private_method
 end

 # self as an implicit receiver (will be ok)
 def implicit
  some_private_method
 end

 private

 def some_private_method; end
end

Example.new.implicit

对于通过谷歌搜索找到这个问题的任何人:这可能会有所帮助 - http://weblog.jamisbuck.org/2007/2/23/method-visibility-in-ruby


这里是一个非常相似的问题的链接。 - ceth
8个回答

55
这里简单说一下。在Ruby中,private表示不能使用显式的接收者来调用方法,例如some_instance.private_method(value)。因此,即使隐式接收者是self,在您的示例中您明确使用了self,因此私有方法是不可访问的。
可以这样想,您是否希望能够使用分配给类实例的变量来调用私有方法呢?不行。Self也是一个变量,因此它必须遵循相同的规则。但是,当您仅在实例内部调用该方法时,它会按预期工作,因为您没有明确声明接收者。
由于Ruby的特性,您实际上可以使用instance_eval调用私有方法:
class Foo
  private
  def bar(value)
    puts "value = #{value}"
  end
end

f = Foo.new
begin
  f.bar("This won't work")
rescue Exception=>e
  puts "That didn't work: #{e}"
end
f.instance_eval{ bar("But this does") }

希望现在更加清晰明了。
-- 编辑 --
我假设您知道这将起作用:
class Foo
 def public_m
  private_m # Removed self.
 end
 private
 def private_m
  puts 'Hello'
 end
end

Foo.new.public_m

好的,我现在觉得我理解了(或者说——我离理解更近了 :))。 - Ernest
4
既然你引用了第512节,我希望你知道,在允许侵权通知的同一小节下,这样的通知必须传递给涉事业务的指定代理。据我所知,在Stack Overflow上编辑答案并不是法律提供的救济措施。Stack Overflow在其法律页面中包括一个有用的指南和完整的联系信息。(CYA注意:此评论只是即兴发言,仅供信息参考,不应被解释为法律建议。) - Chuck

17

Ruby中private的定义是“只能在没有显式接收者的情况下调用”,这就是为什么你只能在没有显式接收者的情况下调用私有方法的原因。没有其他解释。

请注意,实际上有一种例外情况:由于本地变量和方法调用之间的歧义,以下代码将始终被解析为对本地变量的赋值:

foo = :bar

那么,如果你想调用名为foo=的写入器方法,该怎么办呢?嗯,你必须添加一个明确的接收者,因为没有接收者,Ruby就不会知道你要调用方法foo=而不是将值赋给本地变量foo

self.foo = :bar

如果你想调用一个名为foo=private方法,你该怎么办呢?你 不能self.foo=,因为foo=private方法,因此不能使用显式接收器进行调用。但是,对于这种特定情况(仅限于此),你实际上可以使用显式接收器self来调用private方法。


1
删掉前两句话,你就有了一个很好的答案。你应该先说,“Ruby中私有方法的定义是“只能在没有显式接收者的情况下调用”。这就是为什么你只能在没有显式接收者的情况下调用私有方法。没有其他解释。” - Mike Bethany
不错的答案。如果你有时间无聊的话,可以考虑一下这个:self.foo ||= bar - Adam Milligan
感谢您提到编写者异常。即使我不太喜欢以“事情就是这样”的回答开始,我还是会给您点赞。问候。 - Ernest
我尝试谷歌搜索Ruby论坛,看是否有关于“为什么”的解释,最好是来自Matz。但我没有找到。也许有更好的谷歌技巧的人可以找到它。 - Andrew Grimm
正如您所提到的,前两个句子并没有真正帮助回答问题。我刚刚将它们删除了,所以如果有人想知道这些评论是关于什么的:它已经不存在了。 - Florian Pilz
目前为止,Ruby只是变得愚蠢了。给定:(1)实际上对于这个特定的情况(仅限于此情况),您可以使用显式接收器self来调用私有写入器,(2)Self是一个变量,因此它必须遵循相同的规则[?不完全是定义]。定义只需更改即可。(3)如果self.foo ||= bar有效,则也能够访问私有读取器。 - karmakaze

14

很奇怪,关于 Ruby 的可见性修饰符有许多奇怪之处。即使 self 是隐式接收者,在 Ruby 运行时将其明确写出可以使其显式。当它说私有方法不能使用显式接收者调用时,它的意思就是这样,即使 self 也算其中之一。


3
据我所知,私有方法只允许隐式接收器(当然,它始终是self)。

1

为用户门控解决方案添加一些增强功能。调用类方法或实例方法的私有方法是完全可能的。以下是代码片段。但不建议这样做。

类方法

class Example
  def public_m
    Example.new.send(:private_m)
  end

  private
  def private_m
    puts 'Hello'
  end
end

e = Example.new.public_m

实例方法
class Example
  def self.public_m
    Example.new.send(:private_m)
  end

  private
  def private_m
    puts 'Hello'
  end
end

e = Example.public_m

1

对于我之前的回答感到抱歉,我只是不理解你的问题。

我将你的代码更改为以下内容:

class Foo
 def public_m
  private_m # <=
 end

 def Foo.static_m
   puts "static"
 end

 def self.static2_m
   puts "static 2"
 end

 private 
 def private_m
  puts 'Hello'
 end
end

Foo.new.public_m
Foo.static_m
Foo.static2_m

这里是实例方法的调用:

 def public_m
  private_m # <=
 end

这里是一个类方法的调用:

 def Foo.static_m
   puts "static"
 end

 def self.static2_m
   puts "static 2"
 end

Foo.static_m
Foo.static2_m

我认为你没有理解重点。从我对他的问题的理解来看,他不明白为什么使用self.private_m无法正常工作。我猜他习惯了一些语言,可以使用self或this来调用实例方法或使用实例变量。我认为他困惑的是为什么使用Self会出现问题,而不是私有和公共访问器的工作原理。 - Mike Bethany
1
与其创建多个答案,不如编辑或扩展您先前的答案。这有助于我们保持上下文。谢谢。 - the Tin Man

0

以防万一,如果有人现在偶然发现这个问题。从 Ruby 2.7 开始,使用字面上的 self 作为接收者调用私有方法现在是允许的。

我们也可以通过在版本 2.6.9 和 3.1 上运行原始的 Ruby 代码片段来验证这一点。

(ins)tmp->cat sample.rb 
class Foo
 def public_m
  self.private_m # <=
 end
 private
 def private_m
  puts 'Hello'
 end
end

Foo.new.public_m


# See no exception is raise with 3.1 version
(ins)tmp->ruby -v
ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [x86_64-darwin20]
(ins)tmp->ruby sample.rb 
Hello

现在,如果我们尝试使用2.6.9版本运行相同的脚本,就会看到引发了异常。

(ins)tmp->ruby -v
ruby 2.6.9p207 (2021-11-24 revision 67954) [x86_64-darwin20]
(ins)tmp->
(ins)tmp->ruby sample.rb 
sample.rb:3:in `public_m': private method `private_m' called for #<Foo:0x00007ff95289f870> (NoMethodError)
Did you mean?  private_methods
        from sample.rb:11:in `<main>'

0

这并不完全回答问题,但你可以通过这种方式调用私有方法

class Example
 private
 def example_test
  puts 'Hello'
 end
end

e = Example.new
e.send(:example_test)

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