Struct y OpenStruct en Ruby

19 Aug
Published by ApuX in

Tags 

Ruby

Ruby incluye las clases Struct y OpenStruct, que son muy útiles para ciertas tareas. Las clases funcionan como una estructura de datos simple, similar a un hash, pero que también permiten acceder a sus atributos llamándolos directamente sobre el objeto y no sólo mediante los corchetes.

Struct

Veamos un ejemplo con Struct:

Computadora = Struct.new :memoria, :disco_duro
computadora = Computadora.new
computadora[:memoria] = '4 MB'
computadora.disco_duro = '500 GB'
 
# Acceder a sus datos por medio de []
computadora[:memoria] # => '4 MB'
computadora[:disco_duro] # => '500 GB'
 
# Acceder a sus datos como atributos
computadora.memoria # => '4 MB'
computadora.disco_duro # => '500 GB' 

La linea 1 del script define una estructura indicando los campos que contendrá. Esta instrucción es muy interesante: en teoría nos debería regresar un objeto Struct, pero no es así, en realidad nos regresa una clase que se puede asignar a una variable (o una constante, como en este caso), y es posible generar objectos a partir de ella, como sucede en la linea 2. En la linea 3 se asigna un valor a uno de los atributos del objeto usando la sintaxis de un hash, y en la linea 4 se hace lo propio con el otro atributo, usando la sintaxis de método. Las líneas 7 y 8, y las líneas 11 y 12 muestran que los datos se pueden acceder, también, de ambas maneras.

Struct permite además, definir métodos como parte de la estructura. Veamos:

Computadora = Struct.new(:memoria, :disco_duro) do
  def descripcion
    "Computadora con #{memoria} de memoria y #{disco_duro} de disco duro."
  end
end

computadora = Computadora.new('4 MB', '500 GB') puts computadora.descripcion

=> Computadora con 4 MB de memoria y 500 GB de disco duro.

En ese ejemplo podemos ver cómo se define un método para Struct y además, vemos que la creación de los objetos se puede hacer pasando como parámetros los datos que contendrá. El orden de los parámetros debe corresponder con el que se definió la estructura (líneas 1 y 7).

Como vemos, es una clase muy versátil, que nos permite crear objetos con cierto comportamiento sin la necesidad de crear una clase para ello.

OpenStruct

OpenStruct se comporta de manera similar, sólo que permite agregar atributos al vuelo, es decir, sin la necesidad de definirlos previamente. Veamos el siguiente ejemplo:

require 'ostruct'
 
computadora = OpenStruct.new memoria: '4 MB', disco_duro: '500 GB'
computadora.memoria # => '4 MB'
computadora[:disco_duro] # => '500 GB'
 
# los atributos se pueden crear 'al vuelo'
computadora.otro_componente = 'desconocido'
puts computadora.otro_componente # => 'desconocido'
puts computadora[:otro_componente] # => 'desconocido'

Lo primero que hay que notar es que es necesario incluir la librería ostruct porque no es parte del core sino de stdlib; esto se hace en la línea 1. En la línea 3 se crea un objecto OpenStruct y se inicializa desde el momento de su creación. Aquí, a diferencia de Struct, no se crea una clase intermedia, sino directamente el objeto. Para inicializarlo se puede asignar directamente un hash, como en este ejemplo. Las líneas 4 y 5 verifican que los valores se puedan acceder de la misma manera que con Struct. En la línea 8 se asigna un valor a un atributo que no se ha definido. Al momento de inicializar nuestra estructura, no se indicó que aceptaría valores para otro_componente, pero OpenStruct sabe cómo tratar estos casos, creando este atributo al vuelo. En las líneas 9 y 10 vemos que podemos acceder a él sin problemas.

Otra diferencia importante es que OpenStruct no permite definir métodos en su declaración, como sí lo permite Struct.

Conclusiones

Las clases Struct y OpenStruct proveen una forma simple de crear estructuras de datos con el comportamiento de una clase. Esto es particularmente útil para realizar refactors, en donde todavía no tenemos claro qué nuevas clases se necesitan crear, y usamos OpenStruct o Struct como un paso intermedio. Obviamente, si la estructura se vuelve mas compleja y requiere mas esfuerzo para mantenerla, es hora de trasladarla a una clase en forma.

Las ligas de referencia para ambas clases son: Struct y OpenStruct