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


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

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

Sin embargo, parece que protected y private actúan de la misma manera, excepto por el hecho de que no se puede llamar a private métodos con un explícito receptor (es decir, self.protected_method funciona, pero self.private_method no).

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

Author: Andrew Grimm, 2010-08-21

6 answers

protected los métodos pueden ser llamados por cualquier instancia de la clase definidora o sus subclases.

private los métodos solo se pueden llamar desde el objeto que llama. No puede acceder directamente a los métodos privados de otra instancia.

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

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

some_method no puede estar private aquí. Debe ser protected porque lo necesita para soportar receptores explícitos. Sus métodos de ayuda internos típicos generalmente pueden ser private ya que nunca necesitan ser llamados como este.

Es importante tener en cuenta que esto es diferente de la forma en que funciona Java o C++. private en Ruby es similar a protected en 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 con private en Java.

La visibilidad en Ruby es en gran medida una "recomendación" de todos modos, ya que siempre puede obtener acceso a un método utilizando 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
 143
Author: dbyrne,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-30 23:35:33

La diferencia

  • Cualquiera puede llamar a sus métodos públicos.
  • Puede llamar a sus métodos protegidos, o 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 se pueden llamar con un receptor implícito de self. Incluso tú no puedes llamar self.some_private_method; debes llamar private_method con self implícito. (iGEL señala: "Hay es una excepción, sin embargo. Si tiene un método privado age=, puede (y tiene que) llamarlo con self para separarlo de las variables locales.")

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 todavía obtienes las tijeras afiladas de send y puedes llamar a cualquier método que quieras.

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 ejecutar ruby dwarf.rb y 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>
 73
Author: Nathan Long,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:26

Métodos privados en Ruby:

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

Los siguientes ejemplos lo ilustrarán mejor: {[15]]}

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 Anfibios:

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 llamar implícitamente. No pueden ser llamados por receptores explícitos. Por la misma razón, los métodos privados no pueden ser llamados fuera de la jerarquía de la clase definidora.

Métodos protegidos en Ruby:

Si un método está protegido en Ruby, entonces puede ser llamado implícitamente tanto por la clase definidora como por sus subclases. Además, también pueden ser llamados por un explícito receptor siempre y cuando el receptor sea uno mismo o de la misma clase que el de uno mismo:

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íferos que se hereda de la clase de animales

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 de mamíferos)

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 Tree

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>
 39
Author: Aaditi Jain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-20 20:05:19

Considere un método privado en Java. Puede ser llamado desde dentro de la misma clase, por supuesto, pero también puede ser llamado por 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
   }
}

Así que my si la persona que llama es una instancia diferente de mi misma clase my mi método privado es realmente accesible desde el "exterior", por así decirlo. Esto en realidad 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, sin duda debo señalar que es bastante común en la comunidad de Ruby no usar estos controles de visibilidad en absoluto, dado que Ruby te da formas de sortearlos de todos modos. A diferencia del mundo Java, la tendencia es hacer que todo sea accesible y confiar en otros desarrolladores para no arruinar las cosas.

 7
Author: Jacob Mattison,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-20 20:27:23

Comparación de controles de acceso de Java contra 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 es declarado protegido, puede ser accedido por otras clases que existen dentro del mismo paquete, así como por 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 herencia / paquete jerarquía.

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. Solo podemos 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 está declarado, 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 puedes llame al método privado desde fuera de la jerarquía de clases donde fue definido.

Método protegido se puede llamar con un receptor implícito, como como privado. Además, el método protegido también puede ser llamado por un receptor explícito (solo) si el receptor es "self" 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

Protected: Se puede llamar al método Protected con un receptor implícito, como si fuera private. En addition protected method también puede ser llamado por un receptor explícito (solo) si el receptor es "self" o "un objeto de la misma clase".

Private: Para un método privado en Ruby, nunca se puede llamar con un receptor explícito. Solo podemos 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 está declarado, así como a todas las subclases de esta clase.

 2
Author: Neha Chopra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-18 03:50:49

Parte de la razón por la que los métodos privados pueden ser accedidos por subclases en Ruby es que la herencia de Ruby con clases es endulzar ligeramente 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 padre de modo que efectivamente las funciones de la clase padre, incluyendo funciones privadas , se definen en la subclase también.

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

 0
Author: madumlao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-21 01:27:12