为什么Ruby既有私有方法又有受保护的方法?

149
在阅读了这篇文章之前,我认为Ruby中的访问控制是这样工作的:
  • public - 可以被任何对象访问(例如Obj.new.public_method
  • protected - 只能从对象本身以及任何子类中访问
  • private - 与保护方法相同,但在子类中不存在该方法
然而,似乎protectedprivate的行为相同,唯一的区别在于您不能使用显式接收器调用private方法(即self.protected_method可以,但self.private_method不行)。
这有什么意义?什么情况下您不想通过显式接收器调用方法?

4
如果允许所有 Object 实例调用其他所有 Object 实例的私有方法,那么就可以像这样说 5.puts("hello world")。请注意,此处涉及私有方法的概念。 - sepp2k
8个回答

175

protected方法可以被定义类或其子类的任何实例调用。

private方法只能从调用对象内部调用。无法直接访问另一个实例的私有方法。

这里是一个快速的实际例子:

def compare_to(x)
 self.some_method <=> x.some_method
end

some_method不能在此处声明为private。 它必须是protected,因为您需要它来支持显式的接收器。 您通常可以将典型的内部辅助方法定义为private,因为它们从不需要像这样被调用。

重要的是要注意,这与Java或C ++的工作方式不同。 在Ruby中,private类似于Java / C ++中的protected,因为子类可以访问该方法。 在Ruby中,没有办法像Java中的private那样限制从其子类访问方法。

在Ruby中,可见性基本上是一种“建议”,因为您始终可以使用send访问一个方法:

irb(main):001:0> class A
irb(main):002:1>   private
irb(main):003:1>   def not_so_private_method
irb(main):004:2>     puts "Hello World"
irb(main):005:2>   end
irb(main):006:1> end
=> nil

irb(main):007:0> foo = A.new
=> #<A:0x31688f>

irb(main):009:0> foo.send :not_so_private_method
Hello World
=> nil

9
好的,这样就合理多了。我的误解是认为privateprotected与子类是否能继承方法有关,但实际上它们决定了方法可以从哪里调用。谢谢! - Kyle Slattery
3
默认情况下,在生成文档时,RDoc会忽略私有方法,但不会忽略受保护的方法。您可以始终使用“--all”标志来包括它们。 - jasoares
但是如果你真的想要它私有,你不能覆盖 send 吗? - Cyoce

83

区别

  • 任何人都可以调用您的公共方法。
  • 您可以调用受保护的方法,或者您类的另一个成员(或后代类)可以从外部调用您的受保护的方法。没有其他人可以。
  • 只有您可以调用私有方法,因为它们只能使用隐式接收器self调用。即使是您自己也不能调用self.some_private_method;您必须使用隐含的self调用private_method
    • iGEL指出:"然而,有一个例外。如果您有一个私有方法age=,您可以(并且必须)使用self调用它以将其与局部变量分开。"
    • Ruby 2.7开始,self接收器可以是显式的,允许使用self.some_private_method。(任何其他显式接收器仍然被禁止,即使运行时值与self相同。)

在Ruby中,这些区别只是一个程序员给另一个程序员的建议。非公共方法是一种说“我保留更改这个的权利;不要依赖它。”的方式。但是,您仍然可以使用send的锋利剪刀调用任何方法。

简短教程

# dwarf.rb
class Dwarf
  include Comparable

  def initialize(name, age, beard_strength)
    @name           = name
    @age            = age
    @beard_strength = beard_strength
  end

  attr_reader :name, :age, :beard_strength
  public    :name
  private   :age
  protected :beard_strength

  # Comparable module will use this comparison method for >, <, ==, etc.
  def <=>(other_dwarf)
    # One dwarf is allowed to call this method on another
    beard_strength <=> other_dwarf.beard_strength
  end

  def greet
    "Lo, I am #{name}, and have mined these #{age} years.\
       My beard is #{beard_strength} strong!"
  end

  def blurt
    # Not allowed to do this: private methods can't have an explicit receiver
    "My age is #{self.age}!"
  end
end

require 'irb'; IRB.start

然后您可以运行ruby dwarf.rb并执行以下操作:
gloin = Dwarf.new('Gloin', 253, 7)
gimli = Dwarf.new('Gimli', 62,  9)

gloin > gimli         # false
gimli > gloin         # true

gimli.name            # 'Gimli'
gimli.age             # NoMethodError: private method `age'
                         called for #<Dwarf:0x007ff552140128>

gimli.beard_strength # NoMethodError: protected method `beard_strength'
                        called for #<Dwarf:0x007ff552140128>

gimli.greet          # "Lo, I am Gimli, and have mined these 62 years.\
                           My beard is 9 strong!"

gimli.blurt          # private method `age' called for #<Dwarf:0x007ff552140128>

9
解释很好!但是有一个例外。如果你有一个私有方法 age=,你可以(也必须)使用self来调用它以将其与局部变量分开。 - iGEL
如果你将“greet”方法设为受保护的,那么为什么不能执行“gimli.greet”呢?既然gimli是Dwarf类的成员,它不应该能够自由调用这个方法吗? - JoeyC
@JoeyC 因为当你执行 gimli.greet 时,gimli 不是调用者,而是接收者。调用者是“顶层执行环境”,实际上是 Object 的一个临时实例。试试这个:ruby -e 'p self; p self.class' - Kelvin

57

Ruby中的私有方法:

如果一个方法在Ruby中是私有的,那么它就不能被显式地调用(即不能指定对象),只能隐式地调用。它可以被定义它的类以及该类的子类隐式调用。

以下示例将更好地说明这一点:

1) 一个具有私有方法class_name的Animal类

class Animal
  def intro_animal
    class_name
  end
  private
  def class_name
    "I am a #{self.class}"
  end
end

在这种情况下:

n = Animal.new
n.intro_animal #=>I am a Animal
n.class_name #=>error: private method `class_name' called

2) 一个名为Amphibian的Animal子类:

class Amphibian < Animal
  def intro_amphibian
    class_name
  end 
end 

在这种情况下:

  n= Amphibian.new
  n.intro_amphibian #=>I am a Amphibian
  n.class_name #=>error: private method `class_name' called

正如您可以看到的那样,私有方法只能通过隐式调用来调用。它们不能由显式接收器调用。出于同样的原因,私有方法不能在定义类的层次结构之外被调用。

Ruby中的受保护方法:

如果Ruby中的方法是受保护的,则定义类及其子类都可以通过隐式调用来调用它。此外,只要接收器为self或与self相同类别的接收器,它们还可以通过显式接收器进行调用:

1)一个带有受保护方法protect_me的动物类

class Animal
  def animal_call
    protect_me
  end
  protected
  def protect_me
    p "protect_me called from #{self.class}"
  end  
end

在这种情况下:

n= Animal.new
n.animal_call #=> protect_me called from Animal
n.protect_me #=>error: protected method `protect_me' called

2) 一种哺乳动物类,继承自动物类

class Mammal < Animal
  def mammal_call
    protect_me
  end
end 

在这种情况下

n= Mammal.new
n.mammal_call #=> protect_me called from Mammal

3) 一种两栖类从动物类(与哺乳动物类相同)继承而来。

class Amphibian < Animal
  def amphi_call
    Mammal.new.protect_me #Receiver same as self
    self.protect_me  #Receiver is self
  end   
end

在这种情况下

n= Amphibian.new
n.amphi_call #=> protect_me called from Mammal
             #=> protect_me called from Amphibian  

4) 一个名为Tree的类

class Tree
  def tree_call
    Mammal.new.protect_me #Receiver is not same as self
  end
end

在这种情况下:

n= Tree.new
n.tree_call #=>error: protected method `protect_me' called for #<Mammal:0x13410c0>

7
考虑Java中的私有方法。它可以从同一类中调用,当然也可以被同一类的另一个实例调用:
public class Foo {

   private void myPrivateMethod() {
     //stuff
   }

   private void anotherMethod() {
       myPrivateMethod(); //calls on self, no explicit receiver
       Foo foo = new Foo();
       foo.myPrivateMethod(); //this works
   }
}

所以,如果调用者是同一类的另一个实例,则我的私有方法实际上可以从“外部”访问。这使得它似乎并不是那么私有。
另一方面,在Ruby中,私有方法确实只是针对当前实例而言的私有方法。这就是删除显式接收器选项所提供的。
另一方面,我应该指出,在Ruby社区中通常不使用这些可见性控制,因为Ruby给你绕过它们的方法。与Java世界不同,倾向于使所有内容都可访问,并信任其他开发人员不会搞砸事情。

9
“在 Ruby 社区中,不使用这些可见性控制是相当普遍的。”- 这可能是真的,但我认为我们应该使用它们。就像常量一样,它们不是手铐,而是程序员之间的通信:“我建议你别碰这个。” 你可以依赖我的公共方法; 我可能会在不提前警告的情况下更改我的私有方法,因为我认为它们是实现细节。 - Nathan Long
“然而,在 Ruby 中,私有方法确实只是为当前实例保密的。” 这并不是真的。你仍然可能会意外地覆盖父类中的私有方法(有些类甚至将此列为其 API 的一部分)。 - Franklin Yu
1
@FranklinYu,这与他所写的内容无关;Ruby中的隐私是关于_对象_而不是_类_,它是关于_调用_方法而不是_定义_它们。私有方法只能被同一对象的另一个方法调用;它与定义该方法的类无关。 - philomory

5

有何不同?

私有方法解释

@freddie = Person.new
@freddie.hows_it_going?
# => "oh dear, i'm in great pain!"

class Person   
    # public method
    def hows_it_going?
        how_are_your_underpants_feeling?
    end

    private

    def how_are_your_underpants_feeling? # private method
        puts "oh dear, i'm in great pain!"
    end
end

考虑到这是一个公共方法,我们可以向Freddie询问事情的进展情况,这是完全合法的,并且是正常和被接受的。

但是...唯一知道Freddie内裤状况的人是Freddie本人。让随机陌生人去伸手进Freddie的内裤是绝对不行的 - 不行,不行 - 这非常私人化,我们不希望将私人信息暴露给外部世界。换句话说,我们可能不想将可变数据暴露给世界上的任何调用者。有人可能会改变一个值,而我们将面临着试图发现错误来源的痛苦。

@freddie.how_are_your_underpants_feeling?
# => # NoMethodError: private method `how_are_your_underpants_feeling?' called

保护方法解释

考虑以下内容:

class Person    
    protected

    def hand_over_the_credit_card! # protected method
        puts "Lawd have mercy. Whatever. Here it is: 1234-4567-8910"
    end
end

class Rib < Person
end

class Wife < Rib # wife inherits from Rib
    def i_am_buying_another_handbag_with_your_card(husband)        
        husband.hand_over_the_credit_card! # equalityInAction    
    end
end

@husband = Person.new
@mrs = Wife.new
@mrs.i_am_buying_another_handbag_with_your_card(@husband)
# => puts "Lawd have mercy. Whatever. Here it is: 1234-4567-8910"

我们对于 mrs 能够获取到我们的信用卡信息还算可以接受,因为 mrs 是我们的血脉,继承自 Person,所以她可以把钱花在一些鞋子上等等,但我们不希望其他任意个人可以获取到我们的信用卡信息。

如果我们试图在子类外部执行这个操作,它将会失败:

@mrs = Wife.new
@mrs.gimme_your_credit_card!
# => protected method hand_over_the_credit_card! called for #<Wife:0x00005567b5865818> (NoMethodError)

摘要

  • 私有方法只能从内部调用,并且不能带 "显式接收器"。(严格来讲,你可以使用一点 Ruby 魔法访问私有方法,但我暂时会忽略它)。
  • 受保护的方法可以在子类中被调用。
  • 我使用了例子/比喻来帮助您生动地记住。

4
在 Ruby 中,私有方法可以通过子类访问的部分原因是因为 Ruby 类的继承仅是对包含模块进行了薄薄的糖衣 - 实际上,Ruby 的类是一种提供继承等功能的模块。请注意保留 HTML 标签。

http://ruby-doc.org/core-2.0.0/Class.html

这意味着子类“包含”父类,因此实际上子类中定义了父类的函数,包括私有函数
在其他编程语言中,调用方法涉及将方法名称冒泡到父类层次结构并找到响应该方法的第一个父类。相比之下,在Ruby中,虽然仍然存在父类层次结构,但父类的方法直接包含在子类定义的方法列表中。

3
Java和Ruby的权限控制比较:如果在Java中声明方法为private,则只能被同一类中的其他方法访问。如果将方法声明为protected,则同一包中存在的其他类以及不同包中的子类可以访问该方法。当一个方法是public时,它对所有人都是可见的。在Java中,访问控制可见性概念取决于这些类在继承/包层次结构中所处的位置。
而在Ruby中,继承层次结构或包/模块无关紧要。这完全取决于哪个对象是方法的接收者。
对于Ruby中的私有方法,它永远不能使用显式接收器调用。我们只能使用隐式接收器调用私有方法。
这也意味着我们可以从声明它的类以及该类的所有子类中调用私有方法。
class Test1
  def main_method
    method_private
  end

  private
  def method_private
    puts "Inside methodPrivate for #{self.class}"
  end
end

class Test2 < Test1
  def main_method
    method_private
  end
end

Test1.new.main_method
Test2.new.main_method

Inside methodPrivate for Test1
Inside methodPrivate for Test2

class Test3 < Test1
  def main_method
    self.method_private #We were trying to call a private method with an explicit receiver and if called in the same class with self would fail.
  end
end

Test1.new.main_method
This will throw NoMethodError

您不能从定义它的类层次结构之外的地方调用私有方法。

受保护的方法可以像私有方法一样使用隐式接收器进行调用。此外,如果接收器是“self”或“同一类的对象”,则还可以通过显式接收器(仅限)调用受保护的方法。

 class Test1
  def main_method
    method_protected
  end

  protected
  def method_protected
    puts "InSide method_protected for #{self.class}"
  end
end

class Test2 < Test1
  def main_method
    method_protected # called by implicit receiver
  end
end

class Test3 < Test1
  def main_method
    self.method_protected # called by explicit receiver "an object of the same class"
  end
end


InSide method_protected for Test1
InSide method_protected for Test2
InSide method_protected for Test3


class Test4 < Test1
  def main_method
    Test2.new.method_protected # "Test2.new is the same type of object as self"
  end
end

Test4.new.main_method

class Test5
  def main_method
    Test2.new.method_protected
  end
end

Test5.new.main_method
This would fail as object Test5 is not subclass of Test1
Consider Public methods with maximum visibility

摘要

公共方法:公共方法具有最大的可见性。

受保护的方法:受保护的方法可以通过隐式接收器进行调用,就像私有方法一样。此外,只有当接收器为“self”或“与同一类的对象”时,才可以通过显式接收器调用受保护的方法。

私有方法:对于 Ruby 中的私有方法,它永远无法使用显式接收器进行调用。我们只能使用隐式接收器调用私有方法。这也意味着我们可以从声明私有方法的类以及该类的所有子类中调用私有方法。


0

首先,有三种访问修饰符,它们定义了它们的作用域。

  1. Public -> 可以在类外任何地方访问。
  2. Private -> 无法在类外访问。
  3. Protected -> 此方法不可在任何地方访问,只能在其定义的范围内访问。

但我有一个解决方案,适用于所有方法,如何访问将进行深入解释。

class Test
  attr_reader :name

  def initialize(name)
    @name = name
  end
  
  def add_two(number)
    @number = number 
  end
  
  def view_address
    address("Anyaddress")
  end
  
  private 

  def address(add)
    @add = add
  end
  
  protected 

  def user_name(name)
    # p 'call method'
    @name = name
  end
end

class Result < Test
  def new_user
    user_name("test355")
  end
end
  1. 对象列表
  2. p test = Test.new("test")
  3. p test.name
  4. p test.add_two(3)
  5. 列表项
  6. p test.view_address
  7. p r = Result.new("")
  8. p r.new_user

编辑代码时出现一些问题。第二类在单行中显示前一个帖子。现在我将解释如何访问所有方法。首先创建Test类对象,但是无法从类外部访问私有方法,因此需要访问私有方法。我们通过主对象创建view_address方法进行访问。并且通过创建继承来访问受保护的方法。 - hardik

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