¿Cuándo usar clases anidadas y clases anidadas en módulos?

144

Estoy bastante familiarizado con cuándo usar subclases y módulos, pero más recientemente he estado viendo clases anidadas como esta:

class Foo
  class Bar
    # do some useful things
  end
end

Además de clases anidadas en módulos de esta manera:

module Baz
  class Quux
    # more code
  end
end

O bien la documentación y los artículos son escasos o no estoy bien informado sobre el tema para buscar los términos de búsqueda correctos, pero parece que no puedo encontrar mucha información sobre el tema.

¿Podría alguien proporcionar ejemplos o enlaces a publicaciones sobre por qué / cuándo se utilizarían esas técnicas?

cmhobbs
fuente

Respuestas:

140

Otros lenguajes OOP tienen clases internas que no se pueden instanciar sin estar vinculadas a una clase de nivel superior. Por ejemplo, en Java,

class Car {
    class Wheel { }
}

solo los métodos en la Carclase pueden crear Wheels.

Ruby no tiene ese comportamiento.

En rubí

class Car
  class Wheel
  end
end

difiere de

class Car
end

class Wheel
end

sólo en el nombre de la clase Wheelvs Car::Wheel. Esta diferencia de nombre puede hacer explícito a los programadores que la Car::Wheelclase solo puede representar una rueda de automóvil, en lugar de una rueda general. Anidar definiciones de clase en Ruby es una cuestión de preferencia, pero tiene un propósito en el sentido de que impone un contrato más fuerte entre las dos clases y al hacerlo transmite más información sobre ellas y sus usos.

Pero para el intérprete de Ruby, solo es una diferencia de nombre.

En cuanto a su segunda observación, las clases anidadas dentro de los módulos generalmente se usan para asignar espacios de nombres a las clases. Por ejemplo:

module ActiveRecord
  class Base
  end
end

difiere de

module ActionMailer
  class Base
  end
end

Aunque este no es el único uso de clases anidadas dentro de módulos, generalmente es el más común.

Pan Thomakos
fuente
55
@rubyprince, no estoy seguro de lo que quieres decir al establecer una relación entre Car.newy Car::Wheel.new. Definitivamente no necesita inicializar un Carobjeto para inicializar un Car::Wheelobjeto en Ruby, pero la Carclase debe cargarse y ejecutarse para Car::Wheelque se pueda usar.
Pan Thomakos
30
@Pan, estás confundiendo las clases internas de Java y las clases de Ruby con espacios de nombres. Una clase anidada Java no estática se denomina clase interna y existe solo dentro de una instancia de la clase externa. Hay un campo oculto que permite referencias externas. La clase interna Ruby simplemente tiene un espacio de nombres y no está "vinculada" a la clase que la encierra de ninguna manera.Es equivalente a una clase Java estática (anidada) . Sí, la respuesta tiene muchos votos, pero no es completamente precisa.
DigitalRoss
77
No tengo idea de cómo esta respuesta obtuvo 60 votos a favor, y mucho menos fue aceptado por el OP. Literalmente no hay una sola declaración verdadera aquí. Ruby no tiene clases anidadas como Beta o Newspeak. No hay absolutamente ninguna relación entre Cary Car::Wheel. Los módulos (y, por lo tanto, las clases) son simplemente espacios de nombres para constantes, no existe una clase anidada o un módulo anidado en Ruby.
Jörg W Mittag
44
La única diferencia entre los dos es la resolución constante (que es léxica y, por lo tanto, obviamente diferente porque los dos fragmentos son léxicamente diferentes). Sin embargo, no hay absolutamente ninguna diferencia entre los dos con respecto a las clases involucradas. Simplemente hay dos clases completamente no relacionadas. Período. Ruby no tiene clases anidadas / internas. Su definición de clases anidadas es correcto, pero simplemente no se aplica a Ruby, como se puede comprobar trivialmente: Car::Wheel.new. Auge. Acabo de construir un Wheelobjeto que no está anidado dentro de un Carobjeto.
Jörg W Mittag
10
Esta publicación es muy engañosa sin leer todo el hilo de comentarios.
Nathan
50

En Ruby, definir una clase anidada es similar a definir una clase en un módulo. En realidad, no fuerza una asociación entre las clases, solo crea un espacio de nombres para las constantes. (Los nombres de clase y módulo son constantes).

La respuesta aceptada no era correcta sobre nada. 1 En el ejemplo a continuación, creo una instancia de la clase encerrada léxicamente sin que exista una instancia de la clase encerradora.

class A; class B; end; end
A::B.new

Las ventajas son las mismas que para los módulos: encapsulación, agrupación de código utilizado en un solo lugar y colocación de código más cerca de donde se utiliza. Un proyecto grande puede tener un módulo externo que ocurre una y otra vez en cada archivo fuente y contiene muchas definiciones de clase. Cuando los diversos marcos y códigos de biblioteca hacen esto, solo contribuyen con un nombre al nivel superior, lo que reduce la posibilidad de conflictos. Prosaico, sin duda, pero es por eso que se usan.

El uso de una clase en lugar de un módulo para definir el espacio de nombres externo puede tener sentido en un programa o script de un archivo, o si ya usa la clase de nivel superior para algo, o si realmente va a agregar código para vincular las clases juntas en verdadera clase interna estilo de . Ruby no tiene clases internas, pero nada le impide crear el mismo comportamiento en el código. Hacer referencia a los objetos externos de los internos aún requerirá puntear desde la instancia del objeto externo, pero anidar las clases sugerirá que esto es lo que podría estar haciendo. Un programa cuidadosamente modularizado siempre puede crear primero las clases que lo encierran, y razonablemente se pueden descomponer con clases anidadas o internas. No puedes llamar newa un módulo.

Puede usar el patrón general incluso para scripts, donde el espacio de nombres no es muy necesario, solo por diversión y práctica ...

#!/usr/bin/env ruby

class A
  class Realwork_A
    ...
  end
  class Realwork_B
    ...
  end

  def run
    ...
  end

  self
end.new.run
DigitalRoss
fuente
15
Por favor, bastante por favor, no llames a esto una clase interna. No es. La clase noB está dentro de la clase . La constante tiene un espacio de nombres dentro de la clase , pero no hay absolutamente ninguna relación entre el objeto al que hace referencia (que en este caso es una clase) y la clase a la que hace referencia . A BABA
Jörg W Mittag
2
Ok, la terminología "interna" eliminada. Buen punto. Para aquellos que no siguen el argumento anterior, la razón de la disputa es que cuando haces algo como esto en, digamos, Java, los objetos de la clase interna (y aquí estoy usando el término canónicamente) contienen una referencia a La clase externa y las variables de instancia externa pueden ser referenciadas por métodos de clase interna. Nada de eso sucede en Ruby a menos que los vincules con el código. Y sabes, si ese código estaba presente en la clase adjunta, ejem, entonces apuesto a que razonablemente podrías llamar a Bar una clase interna.
DigitalRoss
1
Para mí, esta afirmación es la que más me ayuda a la hora de decidir entre un módulo y una clase: You can't call new on a module.así que, en términos básicos, si solo quiero crear espacios de nombres para algunas clases y nunca necesito crear una instancia de la "clase" externa, entonces Yo usaría un módulo externo. Pero si quiero crear una instancia de la "clase" envolvente / externa, la convertiría en una Clase en lugar de un Módulo. Al menos esto tiene sentido para mí.
FireDragon
@FireDragon u otro caso de uso podría ser que desee una clase que sea una Fábrica, de la cual las subclases hereden, y su fábrica es responsable de crear instancias de las subclases. En esa situación, su Fábrica no puede ser un módulo porque no puede heredar de un módulo, por lo que es una clase primaria que no crea una instancia (y puede 'espacio de nombres' si son hijos si lo desea, algo así como un módulo)
rmcsharry
15

Probablemente quiera usar esto para agrupar sus clases en un módulo. Una especie de espacio de nombres.

por ejemplo, la gema de Twitter usa espacios de nombres para lograr esto:

Twitter::Client.new

Twitter::Search.new

Entonces, ambos Clienty las Searchclases viven bajo el Twittermódulo.

Si desea verificar las fuentes, el código para ambas clases se puede encontrar aquí y aquí .

¡Espero que esto ayude!

Pablo Fernández
fuente
3
Enlace actualizado gema de Twitter: github.com/sferik/twitter/tree/master/lib/twitter
kode
6

Hay otra diferencia entre las clases anidadas y los módulos anidados en Ruby antes de 2.5 que otras respuestas no pudieron cubrir y creo que deben mencionarse aquí. Es el proceso de búsqueda.

En resumen: debido a la búsqueda constante de nivel superior en Ruby antes de 2.5, Ruby puede terminar buscando su clase anidada en el lugar incorrecto ( Objecten particular) si usa clases anidadas.

En Ruby anterior a 2.5:
Estructura de clase anidada: suponga que tiene una clase X, con clase anidada Y, o X::Y. Y luego tienes una clase de nivel superior llamada también Y. Si X::Yno está cargado, entonces ocurre lo siguiente cuando se llama X::Y:

Al no haber encontrado Yen X, Ruby va a tratar de mirar hacia arriba en los antepasados de X. Ya queXes una clase y no un módulo, tiene ancestros, entre los cuales se encuentran [Object, Kernel, BasicObject]. Entonces, trata de buscar Yadentro Object, donde lo encuentra con éxito.

Sin embargo, es el nivel superior Yy no X::Y. Recibirá esta advertencia:

warning: toplevel constant Y referenced by X::Y


Estructura de módulo anidado: suponga que en el ejemplo anterior Xes un módulo y no una clase.

Un módulo solo se tiene a sí mismo como ancestro: X.ancestorsproduciría [X].

En este caso, Ruby no podrá buscar a Yuno de sus antepasados Xy lanzará un NameError. Los rieles (o cualquier otro marco con carga automática) intentarán cargarse X::Ydespués de eso.

Consulte este artículo para obtener más información: https://blog.jetbrains.com/ruby/2017/03/why-you-should-not-use-a-class-as-a-namespace-in-rails-applications/

In Ruby 2.5: Se
eliminó la búsqueda constante de nivel superior.
Puede usar clases anidadas sin temor a encontrar este error.

Timur Nugmanov
fuente
3

Además de las respuestas anteriores: Módulo en Ruby es una clase

$ irb
> module Some end
=> nil
> Some.class
=> Module
> Module.superclass
=> Object
demas
fuente
11
¡Sería más exacto decir que 'clase en Ruby es un módulo'!
Tim Diggins
2
Todo en Ruby puede ser un objeto, pero llamar al módulo una clase no parece correcto: irb (main): 005: 0> Class.ancestors.reverse => [BasicObject, Kernel, Object, Module, Class]
Chad M
y aquí llegamos a la definición de "es"
ahnbizcad
Tenga en cuenta que los módulos no pueden ser instanciados. Es decir, no es posible crear objetos desde un módulo. Entonces los módulos, a diferencia de las clases, no tienen un método new. Entonces, aunque puede decir que los módulos son una clase (minúsculas), no son lo mismo que una clase (mayúsculas) :)
rmcsharry