Estuvimos explorando algo de la sintaxis de Ruby, algunos comandos disponibles
y hasta alguna función predefinida. Pero aún no vimos lo que es un ciclo
for
, y nos quedan pendientes más métodos de objetos, strings y números.
Pero para entender mejor a Ruby, pasemos ahora a una de sus características centrales: la programación de objetos, basados en clases, herencias y algo más.
Nota: escribí 'algo más' porque si bien Ruby tiene una gran influencia de Smalltalk, donde todo es un objeto de una clase, también tiene algunas variantes que respetan ese modelo, pero van un poco más allá, como mixins y métodos por instancia, implementados en una clase especial. Pero no nos adelantemos
Hay una clase predefinida, llamada Object
. Podemos crear una instancia
de esa clase, usando:
miobjeto = Object.new
Notablemente, el new
es un método de la clase. Y como tal, se invoca
colocándolo luego del nombre de la clase (que es una instancia de la clase Class
)
con un punto intermedio.
Si ejecutan esta creación de objeto en el Ruby interactivo, recibirán como valor algo como:
#<Object:0x00000002b85e50>
indicando la clase de la cual es instancia ese nuevo objeto, y un número interno
asignado a esa instancia, exhibido en hexadecimal. Es similar a los hash code
de Java y .NET.
Nota: De nuevo, se ve acá la influencia de Smalltalk, donde el
new
es un método de clase y se puede sobreescribir y hasta recibir parámetros. Lo mismo pasa en Ruby. También se nota la influencia de Smalltalk en la existencia de un métodoinitialize
.
Hay clases ya definidas. Pero ¿cómo definimos nuestras propias clases?
class Perro
def set_nombre(unNombre)
@nombre = unNombre
end
end
Primero, una clase se puede definir en cualquier momento, dentro de un
archivo .rb
o en forma interactiva en irb
. Segundo, vemos que la definición
de una clase contiene comandos/expresiones (recuerden que en Ruby todo son
expresiones, solamente que yo todavía llamo a algunas cosas comandos), y
termina en un end
.
Los def
que pongamos en el cuerpo de la clase definen métodos de las instancias.
Y apareció algo nuevo: el @nombre
es un nombre de variable de instancia. Como
pasó con las variables globales, Ruby distingue los distintos alcances por cómo
comienza el nombre de variable. Esa variable @nombre
residirá en cada
instancia de nuestra clase Perro
, y no se podrá acceder directamente desde
afuera del objeto.
Nota: No los quiero confundir ahora, pero hay formas de acceder a esa variable de instancia desde fuera del objeto. Lo veremos más adelante, cuando veamos las capacidades de reflection de Ruby.
Y ahora, ¿cómo creamos un objeto de la clase Perro
? Podemos usar
neron = Perro.new
neron.set_name "Nerón"
Si estamos en irb
y evaluamos neron
el resultado será algo como:
#<Perro:0x00000002351db0>
Vemos que no podemos ver el nombre que contiene esta instancia. Aprendamos algo nuevo. Todo objeto
en Ruby tiene un método inspect
. Evaluamos
neron.inspect
y obtenemos
#<Perro:0x00000002351db0 @nombre="Nerón">
Siempre podemos apelar a este método si tenemos alguna duda sobre el estado de un objeto.
Pero quisiéramos ahora un método para obtener el nombre de nuestra mascota. En otros lenguajes, no podríamos
extender la clase luego de haberla definida. Pero acá aparece una característica destacable de Ruby: las clases
están abiertas a ser extendidas. ¿Qué significa esto? Veamos de ingresar en cualquier momento de irb
o en cualquier
archivo .rb
:
class Perro
def get_nombre
return @nombre
end
end
Y luego de esto, todas las instancias de la clase Perro
tienen un nuevo método get_nombre
. Podemos
ahora evaluar:
neron.get_nombre
En nuestro ejemplo, creamos nuestra mascota, pero el nombre se lo damos luego de crearla. Quisiéramos ahora
poder darle nombre ya al crearlo. Aparece el método initialize
:
class Perro
def initialize(nombre)
@nombre = nombre
end
end
Ahora podemos repetir la creación de la instancia usando:
fido = Perro.new('Fido')
¿Que pasó? Sucede que el método de clase new
acepta una cantidad variable de parámetros. Y luego de crear la instancia,
llama al método de instancia initialize
, si existe. Le pasa los mismos parámetros que el propio new
recibió.
(A completar: redifinición de new)
Supongamos que estamos programando un juego, donde el jugador pasea por salas de un laberinto. En cada sala puede encontrar cosas, desde joyas hasta armas. Digamos que cada cosa tiene un nombre. Podemos definir la clase:
class Cosa
def initialize(nombre)
@nombre = nombre
end
def get_nombre
return @nombre
end
def set_nombre(nombre)
@nombre = nombre
end
end
Ahora quisiéramos definir las clases Arma
y Joya
, que herede toda la funcionalidad de Cosa
. La forma de hacerlo
en Ruby es:
class Arma < Cosa
# definiciones
end
class Joya < Cosa
# definiciones
end
(A completar: self, super y los métodos heredados)
(A completar: métodos de clase)
(A completar: atributos)
(A completar: ejecución de comandos en la definición de una clase)