28

我花了一段时间才理解私有方法在 Ruby 中是如何工作的,这让我觉得非常尴尬。有谁知道私有方法是否有充分的理由按原样处理?仅仅是历史原因吗?还是实施原因?还是有很好的可靠逻辑原因(即语义)?

例如:

class Person
  private
  attr_reader :weight
end

class Spy < Person
 private
  attr_accessor :code
 public
  def test
    code          #(1) OK: you can call a private method in self
    Spy.new.code  #(2) ERROR: cannot call a private method on any other object
    self.code     #(3) ERROR!!! cannot call a private method explicitly on 'self'
    code="xyz"    #(4) Ok, it runs, but it actually creates a local variable!!!
    self.code="z" #(5) OK! This is the only case where explicit 'self' is ok
    weight        #(6) OK! You can call a private method defined in a base class
  end
end
  • Ruby 在第 (1)、(2) 和 (5) 行的行为似乎是合理的。
  • (6) 可以的事实有点奇怪,尤其是来自 Java 和 C++。这有什么好的理由吗?
  • 我真的不明白为什么(3)失败了!解释一下,有人吗?
  • 第 (4) 行的问题看起来像是语法中的歧义,与“private”无关。

有任何想法吗?

4

1 回答 1

34

您可能会发现阅读 ruby​​ 对公共、私有和受保护的定义很有帮助。(跳至访问控制)

Ruby 的 private 类似于 Java 的 protected。没有 Ruby 等价于 Java 的私有。编辑:这个解决方案现在提供了一种在 Ruby 对象中伪造 Java 理想私有的方法。

Private 被定义为只能被隐式调用的方法/变量。这就是语句 2 和 3 失败的原因。换句话说,私有将方法/变量限制在定义它们的类或子类的上下文中。继承将私有方法传递给子类,因此可以使用隐式 self 进行访问。(解释为什么陈述 6 有效。)

我认为您正在寻找更接近受保护的东西。它的行为类似于没有可见性的 Java 访问器(例如:公共、私有、受保护)通过将 Spy 中的私有更改为保护所有 6 个语句工作。受保护的方法可以由定义类或其子类的任何实例调用。只要调用者是响应调用的对象的类,或者从它继承,对 self 显式或隐式调用都是受保护方法的有效语句。

class Person
  private
  attr_reader :weight
end

class Spy < Person
 protected
  attr_accessor :code
 public
  def test
    code          #(1) OK: you can call a private method in self
    Spy.new.code  #(2) OK: Calling protected method on another instance from same class family or a descendant.
    self.code     #(3) OK: Calling protected method on with explicit self is allowed with protected
    code="xyz"    #(4) Ok, it runs, but it actually creates a local variable!!!
    self.code="z" #(5) OK! This is the only case where explicit 'self' is ok
    weight        #(6) OK! You can call a private method defined in a base class
  end
end

s = Spy.new
s.test # succeeds
s.code #(7) Error: Calling protected method outside of the class or its descendants.

至于陈述4。您假设这是为了避免歧义是正确的。它更多地是对 ruby​​ 动态特性的潜在危害的保护。它确保您以后不能通过再次打开该类来覆盖访问器。可能出现的情况,例如通过评估受污染的代码。

我只能推测导致这些行为的设计决策。对于大多数情况,我觉得这归结为语言的动态特性。

PS如果你真的想给事物私有的java定义。仅可用于定义它的类,甚至不能用于子类。您可以向您的类添加一个 self.inherited 方法,以删除对您想要限制访问的方法的引用。

使子类无法访问 weight 属性:

class Person
  private
  attr_reader :weight

  def initialize
    @weight = 5
  end

  def self.inherited(subclass)
    subclass.send :undef_method, :weight
  end
end

class Spy < Person
 private
  attr_accessor :code
 public
  def test
     weight       
  end
end

Person.new.send(:weight)  # => 5
Spy.new.send(:weight)  #=> Unhelpful undefined method error

将 undef_method 调用替换为如下内容可能更有意义:

  def self.inherited(subclass)
    subclass.class_eval %{
      def weight 
        raise "Private method called from subclass. Access Denied"
      end
     }
  end

它提供了更有用的错误和相同的功能。

发送对于绕过调用其他类的私有方法是必要的。仅用于证明事情确实有效。

事后看来,这使得私有和受保护无用。如果你真的很想保护你的方法,你将不得不重写 send 来阻止它们。以下代码基于对象的 private_methods 执行此操作:

def send_that_blocks_private_methods(method, *args)
  if private_methods.include?(method.to_s)
    raise "Private method #{method} cannot called be called with send."
  else
    send_that_allows_private_methods(method, *args)
  end
end

alias_method :send_that_allows_private_methods, :send
alias_method :send, :send_that_blocks_private_methods
private :send_that_allows_private_methods

您可以指定要阻止访问而不是拒绝访问所有私有方法的 private_methods 的 class_variable。您也可以将 send 设为私有,但从对象外部调用 send 有合法用途。

于 2009-10-14T11:10:21.863 回答