Skip to content

Latest commit

 

History

History
181 lines (137 loc) · 5.52 KB

05-clases.md

File metadata and controls

181 lines (137 loc) · 5.52 KB

Clases y Objetos

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

Creando un Objeto

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étodo initialize.

Nuestra Primera Clase

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.

Creando una Instancia

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

Inicializando una Instancia

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)

Herencia

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)