24 Jan 2016
En el lenguaje de programación Ruby, los métodos definidos vienen en dos variantes: métodos de instancia y métodos de clase. Los métodos de instancia están disponibles después de inicializar un objeto, creando una instancia. Los métodos de clase, por otro lado, están disponibles sin crear una instancia de la clase sobre la que se definen.
Los métodos Ruby pueden variar en visibilidad. Los métodos públicos están disponibles en cualquier contexto, mientras que la disponibilidad de los métodos privados está restringida dentro de la instancia de una clase y sus descendientes. Un tercer ámbito de visibilidad, protected, se comporta de manera similar a los métodos privados, pero los métodos protegidos pueden ser llamados por otras instancias de la misma clase.
Para un repaso rápido, los métodos de instancia pública y privada se ven así:
class Dog def do_trick bark end private def bark puts 'woof woof' endend
Cuando se llama al método público:
> dog = Dog.new> dog.do_trick# => woof woof
Y el método privado:
> dog = Dog.new> dog.bark# => NoMethodError: private method `bark' called for <Dog>
Los métodos de clase privada pueden no ser tan comunes como los métodos de instancia privada, pero todavía tienen su lugar. Por ejemplo, un método de clase puede requerir métodos auxiliares internos para completar su función. Cualquiera que sea la razón, definir métodos de clase privada tiene valor, pero no siempre es intuitivo.
Este ejemplo Dog
class necesita mantener una lista de tricks
que se utilizará dentro de los otros métodos de clase pública. Esta lista no debe ser accesible para ninguna persona que llame fuera de la clase Dog
.
El camino equivocado
Un primer paso al escribir el método privado tricks
podría parecer:
class Dog private def self.tricks endend
Sin embargo, al probar la visibilidad del método tricks
:
> Dog.tricks# =>
No se produjo ningún error indicando que se llamó a un método privado, este método es completamente público.
¿Por qué?
La razón por la que el código anterior no produjo un método privado tiene que ver con la jerarquía de objetos de Ruby, las interacciones entre clases internas, las instancias de esas clases y las clases propias. Un artículo detallado sobre cómo los objetos de Ruby funcionan entre sí se puede encontrar en un artículo anterior.
Al definir métodos en una clase, el contexto predeterminado y el contexto introducido por la declaración de método self.
son distintos.
El alcance del método privado no se puede usar de la manera anterior, ya que no maneja el cambio de contexto entre los métodos definidos en la clase Dog
y definidos dentro del contexto self
.
¿cuáles son las alternativas?
class < < self
Una forma alternativa de definir métodos de clase en un objeto es usar la sintaxis class << self
. Esta sintaxis abre una clase propia para el argumento suministrado. En este ejemplo, self
dentro de la clase Dog
abrirá la clase propia Dog's
.
class Dog class << self private def tricks end endend
Una cosa a tener en cuenta es que al definir métodos como este, la declaración ha cambiado de def self.tricks
a simplemente def tricks
.
Ahora, el alcance privado se conserva y se logra el comportamiento esperado:
> Dog.tricks# => NoMethodError: private method `tricks' called for Dog:Class
Módulos
Los módulos en ruby son colecciones de métodos y constantes. Estas colecciones se pueden utilizar como herramientas de encapsulación o, en este caso, como alternativas a la definición de métodos de clases públicas y privadas.
Cuando una clase extends
un módulo, todos los métodos dentro de ese módulo se convierten en métodos de clase en la clase de asignatura*. Este patrón se puede usar para definir el método tricks
en Dog
.
*Nota: Esto solo se refiere a los métodos definidos en un «sentido típico» cualquier definición de método def self.
o def Dog.
en un módulo no se convierte automáticamente en métodos de clase de la misma manera cuando extended
.
class Dog module ClassMethods private def tricks end end extend ClassMethodsend> Dog.tricks# => NoMethodError: private method `tricks' called for Dog:Class
Un beneficio de este enfoque es la legibilidad. El módulo llamado ClassMethods
, que está contenido y, por lo tanto, encapsulado por la clase Dog
, es el hogar claro para todos los métodos de clase deseados.
Los modificadores de visibilidad como private
se comportan en consecuencia y un simple extend
en la parte inferior de la definición del módulo lo reúne todo.
private_class_method
Un tercer enfoque es usar el método incorporado Module#private_class_method
. El propósito de este método es cambiar la visibilidad de los métodos de clase existentes.
A diferencia de las otras dos soluciones, esta no requiere un estilo diferente de definición de método de la solución incorrecta:
class Dog def self.tricks end private_class_method :tricksend> Dog.tricks# => NoMethodError: private method `tricks' called for Dog:Class
Este método también puede ser alineado durante la definición del método de la clase:
class Dog private_class_method def self.tricks endend> Dog.tricks# => NoMethodError: private method `tricks' called for Dog:Class
Si un método de una sola clase debe ser privado y guardar líneas es muy importante, este estilo podría ser aplicable; pero, ciertamente sacrifica un nivel de legibilidad.
Una fortaleza importante de private_class_method
es su explicitud. A diferencia de los otros enfoques, este no requiere una definición de módulo especial o un cambio de contexto de definición de método.
Lectura adicional
Las soluciones mencionadas anteriormente harán el trabajo, pero es posible que no respondan a las preguntas persistentes sobre por qué las cosas funcionan de la manera en que lo hacen. Algunos excelentes artículos sobre la clase propia de ruby y los pensamientos de Matz sobre el diseño del método Ruby deberían ayudar a pintar una imagen más concisa de por qué la definición del método de clase privada de Ruby es compleja.