是否有充分的理由选择“私人”?像 Ruby 中那样工作?

发布于 2024-08-08 07:13:41 字数 964 浏览 13 评论 0原文

我花了一段时间才理解 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”无关。

有什么想法吗?

It took me a while to understand how private methods work in Ruby, and it really strikes me as being very awkward. Does anyone know if there are good reasons for private methods to be handled the way they are? Is it just historic reasons? Or implementation reasons? Or are there good solid logical reasons (ie. semantic)?

For example:

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's behaviour on lines (1), (2) and (5) seems reasonable.
  • The fact that (6) is ok is a bit strange, especially coming from Java and C++. Any good reason for this?
  • I really do not understand why (3) fails ! An explanation, anyone?
  • The problem on line (4) looks like an ambiguity in the grammar, which has nothing to do with 'private'.

Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

泪眸﹌ 2024-08-15 07:13:41

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

Ruby 的私有类似于 Java 的受保护。 Ruby 中没有相当于 Java 私有的东西。 编辑: 该解决方案现在提供了一种在 Ruby 对象中伪造 Java 理想的私有方法。

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

我认为您正在寻找更接近受保护的东西。其行为类似于未赋予可见性的 Java 访问器(例如:公共、私有、受保护)
通过更改 Spy 中的 private 来保护所有 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 方法添加到您的类中,以删除对您想要限制访问的方法的引用。

使权重属性无法从子类访问:

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 是有合法用途的。

You might find it helpful to read ruby's definition of public, private and protected. (Skip to Access Control)

Ruby's private is analogous to Java's protected. There is no Ruby equivalent of Java's private. EDIT: This solution now provides a method of faking it Java's ideal of private in Ruby objects.

Private is defined as methods/variables that can only be called implicitly. This is why statements 2 and 3 fail. In other words, private limits methods/variables to the context of a class or subclass in which they are defined. Inheritance passes private methods to the subclasses and can therefore be accessed with an implicit self. (Explaining why statement 6 works.)

I think you're looking for something closer to protected. Which behaves similarly to Java accessors that are not given a visibility (eg: public, private, protected)
By changing the private in Spy to protected all 6 of your statements work.
Protected methods can be called by any instance of the defining class or their subclasses. Either explicitly or implicitly called on self are valid statements for protected methods so long as the caller is either the class of the object responding to the call, or inherits from it.

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.

As for statement 4. You are correct in assuming this is to avoid ambiguity. It's more a safeguard to the potential harm of ruby's dynamic nature. It ensures that you cannot override accessors by opening up the class again later. A situation that can arise, for example by eval'ing tainted code.

I can only speculate on he design decisions that led to these behaviours. For most of it I feel it comes down to the dynamic nature of the language.

P.S. If you really want to give things the java definition of private. Only available to the class in which it's defined, not even subclasses. You could add an self.inherited method to your classes to remove references to the methods you want to limit access to.

Making the weight attribute inaccessible from subclasses:

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

It may make more sense to replace the undef_method call to something like this:

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

Which provides a much more helpful error and the same functionality.

The send is necessary to get around calling private methods for other classes. Only used to prove that things are actually working.

Which in hindsight, makes private and protected useless. If you're really serious about protecting your methods you will have to override send to block them. The following code does that based on the private_methods of the object:

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

You could specify a class_variable of private_methods you want to block access to instead of denying access to all private methods. You could also make send private, but there are legitimate uses of calling send from outside an object.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文