Entendiendo los objetos en ruby

Gonzalo Sánchez D
6 min readJan 20, 2015

--

Para muchos programadores ha sido complicado aprender a programar con objetos debido a la gran cantidad de conceptos que se introducen simultaneamente, pero a mi parecer es porque simplemente la teoría está explicada con una mala metáfora.

Se suele decir que las clases (la forma de definir objetos) son recetas, o son un esqueleto, y es mucho más fácil explicar que son los objetos y para que sirven sin entendemos a las clases como el molde, por ejemplo un molde de una pieza de lego y a los objetos como productos de este molde, o sea los legos.

Requisito (Entender funciones, paso de parámetros, return)

Los objetos tienen un identificador (por ejemplo nº serie), propiedades como el color y tamaño; y comportamientos como acoplarse y doler infinito si uno los pisa.

El como se definen las clases y objetos depende del lenguaje de programación, en ruby que es un lenguaje que a mi me fascina por su simpleza se definen así:

class MoldeLego
end

Para crear un objeto a partir de ese molde hay que instanciarlo (otra palabra famosa dentro del mundo orientado a objetos), para instanciar basta con utilizar el nombre de la clase con el método new, o sea:

lego1 = MoldeLego.new

Este lego tiene un identificador, que se puede capturar en ruby con lego1.object_id pero todavía no tiene comportamiento ni propiedades, ahora el secreto para entender bien esta parte es que tanto el molde (la clase) como el objeto pueden tener comportamientos. Cuando los comportamientos son del molde se llaman métodos de clase, cuando lo son del objeto se llaman métodos de instancia.

Para agregar un método de instancia, (o sea un comportamiento del lego) lo hacemos de la siguiente forma:

class MoldeLego
def metodoDeInstancia()
end
end

Por ejemplo algo común sería que el lego tuviera la capacidad de acoplarse con otro lego, para eso el método tendría que ser capaz de recibir como parámetro el otro lego y devolver ahora un nuevo lego más grande.

class MoldeLego
def acoplar(otro_lego)
return MoldeLego.new()
end
end

El problema es que para guardar cualquier estado del lego necesitamos variables de instancias, y para eso necesitamos definirlas en el constructor que en el caso de ruby se llama initialize() y si, este método también es un método de instancia, pero además tiene la gracia que se llama sólo cuando se instancia un objeto a través de new.

class MoldeLego
def initialize()
@size = 1 # las variables de instancia se definen con una @
end
end

Cada vez que creamos un lego este tendrá tamaño 1,

lego1 = MoldeLego.new # Este Lego ahora tiene tamaño 1

Hagamos ahora un pequeño resumen:

Los objetos son instancias de una clase, cuando se utilizar la expresión clase.new entonces es cuando se instancia el objeto y automáticamente se llama al constructor, todo lo definido dentro del constructor se ejecuta.

Los constructores sirven principalmente para darle valores iniciales a las variables de instancias.

Volviendo a nuestro ejemplo, si queremos rescatar el tamaño tenemos un problema, no se puede acceder directamente a los estados internos de un objeto debido al principio de encapsulamiento, para acceder a los estados tenemos que crear getters y setters, o sea métodos para obtener los estados (getters) y métodos para cambiarlos (setters), así que nuestro MoldeLego quedaría así:

class MoldeLego
def initialize()
@size = 1 # las variables de instancia se definen con una @
end
def getSize()
return size
end
def setSize(new_size)
@size = new_size
end
end

Para usarlo, lo haríamos de la siguiente forma:

lego1 = MoldeLego.new # Este Lego ahora tiene tamaño 1
lego1.setSize(5)
lego1.getSize()

Exista una mejor forma de definir los getters y setters, que es hacerlo de tal manera que pareciera que estamos trabajando directamente sobre el atributo.

o sea:

class MoldeLego
def initialize()
@size = 1 # las variables de instancia se definen con una @
end
def size()
return size
end
def size=(new_size)
@size = new_size
end
end

de esta forma podemos ocupar el objeto así:

lego1 = MoldeLego.new # Este Lego ahora tiene tamaño 1
lego1.size # devuelve 1, pues es el tamaño original
lego1.size = 5
lego1.size # devuelve 5, ya que lo cambiamos

ahora en ruby es posible definir de forma simultánea la variable, los getters y setters a través del attr_accessor:

class MoldeLego
attr_accesor :size
def initialize()
@size = 1 # las variables de instancia se definen con una @
end
end

y para ocuparlo sería exactamente que la forma anterior, ahora volvamos a la idea de acoplar:

class MoldeLego
attr_accessor:size
# Pasamos un valor al constructor
# para poder crear legos de otros tamaños
# en caso de que se omita es de tamaño 1
def iniatilize(size_orig = 1)
@size = size_orig
end
def acoplar(otro_lego)
return MoldeLego.new(self.size + otro_lego.size) # devolvemos un lego de la suma del tamaño de los otros legos
end
end

¿Cómo se usa?

lego1 = MoldeLego.new
lego2 = MoldeLego.new
lego3 = lego1.acoplar(otro_lego)

Todavía podemos hacer una mejor más, en ruby al igual que otros lenguajes se puede redefinir una operación, en este caso vamos a redefinir la suma (sólo para los legos):

class MoldeLego
attr_accessor:size
# Pasamos un valor al constructor
# para poder crear legos de otros tamaños
# en caso de que se omita es de tamaño 1
def iniatilize(size_orig = 1)
@size = size_orig
end
def +(otro_lego)
return MoldeLego.new(self.size + otro_lego.size) # devolvemos un lego de la suma del tamaño de los otros legos
end
end

Gracias a eso ahora podemos simplemente escribir:

lego1 = MoldeLego.new
lego2 = MoldeLego.new
lego3 = lego1 + lego2

¿Que pasaría si quisiéramos contar cuantos legos se han creado?

Eso no lo podemos hacer con una variable de instancia, necesitamos que sea el molde cuente, (algo así como imprimir el número de serie en el producto), o sea es una propiedad de la clase, estas se llaman formalmente variables de clase, en ruby se crean con 2 @@:

class MoldeLego
attr_accessor:size
@@cuenta_legos = 0
end

A diferencia de la variables de instancias estas no requieren definirse en el constructor, pues existen para la clase, independiente de si existen legos o no.

ahora si quisiéramos contar cada vez que se crea un lego nuevo, podríamos hacerlo así:

class MoldeLego
attr_accessor:size
@@cuenta_legos = 0
def iniatilize(size_orig = 1)
@size = size_orig
@@cuenta_legos += 1
end
end

si quisiéramos obtener las variables de clase toparíamos con el mismo problema de encapuslamiento, tenemos que definir getters y setters para estas, ahora lamenteblemente la sintaxis en ruby no es tan bonita, se hace asi:

class MoldeLego
class << self; attr_accessor :cuenta_legos end
attr_accessor:size
@@cuenta_legos = 0
def iniatilize(size_orig = 1)
@size = size_orig
@@cuenta_legos += 1
end
end

Sólo una cosa está quedando en el tintero,¿ cómo crear métodos de clase más allá de los getters y setter?

Para eso vamos a ocupar self, para este ejemplo voy a definir un getter de la forma no tan bonita para la cuenta legos, sería así:

class MoldeLego
class << self; attr_accessor :cuenta_legos end
def self.getCuentaLegos()
return @@cuenta_legos
end
end

De los legos al molde:

Una de las curiosidades que tienen los lenguajes orientados a objetos es que desde los objetos podemos rescatar las propiedades de la clase, no así al revés, desde las intancias no podemos saber cuales son las propiedades de las intancias.

En la metáfora del lego podemos saber incluso de que color era el molde que lo fabricó sin embargo desde el molde no podemos saber de que color o cuantos legos se han acoplado a menos que llevemos específicamente la cuenta como en el caso del número de serie.

Para rescatar la clase a partir de la instancia basta con hacer:

lego.class.cuentaLegos

lo que es exactamente lo mismo que:

MoldeLego.cuentaLegos

Es importante no confundir esta metafora con la herencia, esta última es una característica de los objetos que les permite obtener los métodos y propiedades de sus padres, pero este tema lo tocaremos en un próximo artículo.

Con lo vista ya entendemos lo básico de objetos en Ruby (es bueno saber que con excepción de los attr_accessor este tutorial aplica a otros lenguajes orientados a objetos)

--

--

Gonzalo Sánchez D
Gonzalo Sánchez D

Written by Gonzalo Sánchez D

Lean entrepreneur, Ruby on Rails developer and Teacher. Analytics geek. Founder of www.desafiolatam.com

No responses yet