¿Por qué Ruby tiene métodos privados y protegidos?

141

Antes de leer este artículo , pensé que el control de acceso en Ruby funcionaba así:

  • public- Se puede acceder por cualquier objeto (por ejemplo Obj.new.public_method)
  • protected - solo se puede acceder desde el propio objeto, así como desde cualquier subclase
  • private - igual que protegido, pero el método no existe en subclases

Sin embargo, parece ser que protectedy privateactúan de la misma, excepto por el hecho de que no se puede llamar a privatelos métodos con un receptor explícita (es decir, self.protected_methodlas obras, pero self.private_methodno lo hace).

¿Cuál es el punto de esto? ¿Cuándo hay un escenario en el que no desea que se llame a su método con un receptor explícito?

Kyle Slattery
fuente
3
Si Objectse permitiera a todas las instancias llamar a los métodos privados de cualquier otra instancia de Object, sería posible decir cosas como 5.puts("hello world").
sepp2k

Respuestas:

161

protected cualquier instancia de la clase definitoria o sus subclases puede llamar a los métodos.

privatelos métodos solo se pueden invocar desde el objeto que realiza la llamada. No puede acceder a los métodos privados de otra instancia directamente.

Aquí hay un ejemplo práctico rápido:

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

some_methodNo puede estar privateaquí. Debe ser protectedporque lo necesita para admitir receptores explícitos. Sus métodos de ayuda interna típicos generalmente pueden ser privateya que nunca necesitan llamarse así.

Es importante tener en cuenta que esto es diferente de la forma en que funciona Java o C ++. privateen Ruby es similar a protecteden Java / C ++ en que las subclases tienen acceso al método. En Ruby, no hay forma de restringir el acceso a un método desde sus subclases como puede hacerlo privateen Java.

La visibilidad en Ruby es en gran medida una "recomendación" de todos modos ya que siempre puedes obtener acceso a un método usando 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
dbyrne
fuente
9
Ah, está bien, eso tiene mucho más sentido. Mi malentendido vino de pensar privatevs protectedtener que ver si una subclase podría heredar un método, pero en realidad se trata de dónde se puede llamar al método. ¡Gracias!
Kyle Slattery
3
RDoc también ignora los métodos privados de forma predeterminada al generar documentación, mientras que los protegidos no lo son. Siempre puede usar la bandera --todos para incluirlos.
jasoares
Pero si realmente lo quieres privado, ¿no puedes anularlo send?
Cyoce
78

La diferencia

  • Cualquiera puede llamar a sus métodos públicos.
  • Puede llamar a sus métodos protegidos, u otro miembro de su clase (o una clase descendiente) puede llamar a sus métodos protegidos desde el exterior. Nadie más puede.
  • Solo usted puede llamar a sus métodos privados, porque solo pueden llamarse con un receptor implícito de self. Ni siquiera tú puedes llamar self.some_private_method; debes llamar private_methodcon selfimplícito.
    • iGEL señala: "Sin embargo, hay una excepción. Si tiene un método privado age =, puede (y debe) llamarlo a sí mismo para separarlo de las variables locales".
    • Desde Ruby 2.7, el selfreceptor puede ser explícito, self.some_private_methodestá permitido. (Cualquier otro receptor explícito aún no está permitido, incluso si el valor de tiempo de ejecución es el mismo que self).

En Ruby, estas distinciones son solo consejos de un programador a otro. Los métodos no públicos son una forma de decir "Me reservo el derecho de cambiar esto; no dependa de ello". Pero aún obtienes las tijeras afiladas sendy puedes llamar a cualquier método que desees.

Un breve tutorial

# 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

Entonces puedes correr ruby dwarf.rby hacer esto:

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>
Nathan Long
fuente
8
Buena explicación! Hay una excepción, sin embargo. Si tiene un método privado age=, puede (y tiene que) llamarlo selfpara separarlo de las variables locales.
iGEL
Si hiciste "saludar" un método protegido, ¿por qué no puedes hacer un saludo? Dado que Gimli es miembro de la clase Enana, ¿no debería poder llamar a este método sin acoso?
JoeyC
@JoeyC porque cuando lo haces gimli.greet, gimlino es la persona que llama, sino el receptor. La persona que llama es el "entorno de ejecución de nivel superior", que en realidad es una instancia ad-hoc de Object. Prueba esto:ruby -e 'p self; p self.class'
Kelvin, el
52

Métodos privados en Ruby:

Si un método es privado en Ruby, entonces no puede ser invocado por un receptor explícito (objeto). Solo se puede llamar implícitamente. Se puede llamar implícitamente por la clase en la que se ha descrito, así como por las subclases de esta clase.

Los siguientes ejemplos lo ilustrarán mejor:

1) Una clase Animal con método privado class_name

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

En este caso:

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

2) Una subclase de Animal llamada Anfibio:

class Amphibian < Animal
  def intro_amphibian
    class_name
  end 
end 

En este caso:

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

Como puede ver, los métodos privados solo se pueden invocar implícitamente. No pueden ser llamados por receptores explícitos. Por la misma razón, los métodos privados no se pueden invocar fuera de la jerarquía de la clase definitoria.

Métodos protegidos en Ruby:

Si un método está protegido en Ruby, la clase definitoria y sus subclases pueden invocarlo implícitamente. Además, también pueden ser llamados por un receptor explícito siempre que el receptor sea propio o de la misma clase que el propio:

1) Una clase Animal con método protegido protect_me

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

En este caso:

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

2) Una clase de mamífero que se hereda de la clase animal.

class Mammal < Animal
  def mammal_call
    protect_me
  end
end 

En este caso

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

3) Una clase de anfibios heredada de la clase Animal (igual que la clase mamífero)

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

En este caso

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

4) Una clase llamada Árbol

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

En este caso:

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

Considere un método privado en Java. Se puede llamar desde la misma clase, por supuesto, pero también se puede llamar desde otra instancia de esa misma clase:

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
   }
}

Entonces, si la persona que llama es una instancia diferente de mi misma clase, mi método privado es realmente accesible desde el "exterior", por así decirlo. En realidad, esto hace que no parezca tan privado.

En Ruby, por otro lado, un método privado realmente está destinado a ser privado solo para la instancia actual. Esto es lo que proporciona la eliminación de la opción de un receptor explícito.

Por otro lado, debo señalar que es bastante común en la comunidad de Ruby no utilizar estos controles de visibilidad, dado que Ruby te ofrece formas de sortearlos de todos modos. A diferencia del mundo de Java, la tendencia es hacer que todo sea accesible y confiar en que otros desarrolladores no arruinen las cosas.

Jacob Mattison
fuente
9
"Es bastante común en la comunidad de Ruby no usar estos controles de visibilidad en absoluto". Esto puede ser cierto, pero diría que deberíamos usarlos. Como las constantes, no son esposas, sino una comunicación de un programador a otro: "Te aconsejo que dejes esto en paz". Puedes confiar en mis métodos públicos; Puedo cambiar mis métodos privados sin previo aviso, porque los considero detalles de implementación.
Nathan Long
En Ruby, por otro lado, un método privado realmente debe ser privado solo para la instancia actual. " Esto no es verdad. Todavía puede sobrescribir accidentalmente métodos privados de su clase principal (y algunas clases incluso lo enumeran como parte de su API).
Franklin Yu
1
@FranklinYu Eso no tiene relación con lo que escribió; La privacidad en Ruby se trata de objetos , no de clases , y se trata de llamar a métodos, no de definirlos . Un método privado solo puede ser llamado por otro método del mismo objeto; no tiene nada que ver con en qué clase se definió el método.
Philomory
2

Parte de la razón por la cual las subclases pueden acceder a métodos privados en Ruby es que la herencia de Ruby con clases es una capa fina de azúcar sobre el Módulo incluye: en Ruby, una clase, de hecho, es un tipo de módulo que proporciona herencia, etc.

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

Lo que esto significa es que, básicamente, una subclase "incluye" la clase principal de manera que las funciones de la clase principal, incluidas las funciones privadas , también se definen en la subclase.

En otros lenguajes de programación, llamar a un método implica hacer burbujear el nombre del método en una jerarquía de clase primaria y encontrar la primera clase primaria que responde al método. Por el contrario, en Ruby, mientras la jerarquía de la clase principal todavía está allí, los métodos de la clase principal se incluyen directamente en la lista de métodos de la subclase que ha definido.

madumlao
fuente
2

Comparación de los controles de acceso de Java con Ruby: si el método se declara privado en Java, solo se puede acceder a él por otros métodos dentro de la misma clase. Si un método se declara protegido, otras clases que existen dentro del mismo paquete pueden acceder a él, así como las subclases de la clase en un paquete diferente. Cuando un método es público, es visible para todos. En Java, el concepto de visibilidad del control de acceso depende de dónde se encuentran estas clases en la jerarquía de herencia / paquete.

Mientras que en Ruby, la jerarquía de herencia o el paquete / módulo no encajan. Se trata de qué objeto es el receptor de un método.

Para un método privado en Ruby, nunca se puede llamar con un receptor explícito. Podemos (solo) llamar al método privado con un receptor implícito.

Esto también significa que podemos llamar a un método privado desde una clase en la que se declara, así como a todas las subclases de esta clase.

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

Nunca puede llamar al método privado desde fuera de la jerarquía de clases donde se definió.

El método protegido se puede llamar con un receptor implícito, como privado. Además, el método protegido también puede ser llamado por un receptor explícito (solo) si el receptor es "propio" o "un objeto de la misma clase".

 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

Resumen

Público: los métodos públicos tienen máxima visibilidad

Protegido: el método protegido se puede invocar con un receptor implícito, como privado. Además, el método protegido también puede ser llamado por un receptor explícito (solo) si el receptor es "propio" o "un objeto de la misma clase".

Privado: para un método privado en Ruby, nunca se puede llamar con un receptor explícito. Podemos (solo) llamar al método privado con un receptor implícito. Esto también significa que podemos llamar a un método privado desde una clase en la que se declara, así como a todas las subclases de esta clase.

Neha Chopra
fuente
0
First Three types of access specifiers and those define thier scope.
1.Public    ->  Access anywhere out side the class.
2.Private   ->  Can not access outside the class. 
3.Protected ->  This Method not access anywhere this method define 
                scope.

But i have a solution for this problem for all method how to access explain in depth. 

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. Lista de objetos
  2. p test = Test.new ("prueba")
  3. p test.name
  4. p test.add_two (3)
  5. Elemento de la lista
  6. p test.view_address
  7. pr = Result.new ("")
  8. p r.new_user
hardik
fuente
Algún problema al editar el código. Show de segunda clase en una sola línea de publicación anterior. Ahora explico cómo acceder a todos los métodos. Primero cree el objeto de clase de prueba. Pero el método privado no puede acceder a la clase externa y luego acceder al método privado. creamos acceso al método view_address a través del objeto principal. y también acceso a métodos protegidos para crear la herencia.
hardik