Página original: http://pine.fm/LearnToProgram/?Chapter=09
Hasta ahora hemos visto diferentes tipos, o clases, de objetos: cadenas (string), enteros (integer), flotantes (floats), arreglos (arrays) y algunos cuantos objetos especiales (true, false y nil) de los cuales hablaremos mas tarde. En Ruby, estas clases siempre se escriben en ingles y llevan la primer letra mayúscula: String, Integer, Float, Array… etc. Generalmente, si queremos crear un nuevo objeto de una cierta clase, usamos new:
1 2 3 4 5 6 7 | a = Array.new + [12345] # Agregamos el arreglo b = String.new + 'hola' # Agregamos la cadena c = Time.new puts 'a = '+a.to_s puts 'b = '+b.to_s puts 'c = '+c.to_s |
a = 12345 b = hola c = Thu May 14 12:01:13 -0500 2009
Ya que podemos crear arreglos y cadenas usando [...] y ‘…’ respectivamente, rara vez los creamos usando new. (Aunque no es tan obvio para el ejemplo anterior, String.new crea una cadena vacía, y Array.new crea un arreglo vacío.) Los números son una excepción, no podemos crear un entero con Integer.new. Solo tenemos que escribir el entero.
La clase Time
Los objetos Time representan un momento en el tiempo. Puedes sumar (o restar) números al tiempo para obtener nuevos momentos: agregar 1.5 a un momento hace que aparezca el tiempo con un segundo y medio de mas:
1 2 3 4 5 | tiempo = Time.new # El momento en que se escribió este texto tiempo2 = tiempo + 60 # Un minuto mas tarde puts tiempo puts tiempo2 |
Thu May 14 12:09:34 -0500 2009 Thu May 14 12:10:34 -0500 2009
También puedes especificar un momento en el tiempo usando Time.mktime:
1 2 | puts Time.mktime(2000, 1, 1) # Y2K puts Time.mktime(1976, 8, 3, 10, 11) # Cuando nació el autor |
Sat Jan 01 00:00:00 -0600 2000 Tue Aug 03 10:11:00 -0600 1976
Los paréntesis son para agrupar los parámetros de mktime. Mientras mas parámetros agregues, mas preciso será el tiempo.
Puedes comparar el tiempo usando los métodos de comparación (un tiempo anterior es menor a un tiempo mas tarde), y si restas un tiempo de otro, puedes obtener los segundos que hay entre ellos.
Unas cosas para intentar
Mil millones de segundos… Investiga en que segundo naciste (si se puede). Y después averigua cuando cumplirás (o cuando cumpliste) mil millones de segundos, y escribelo en tu agenda para que no lo olvides.
Feliz cumpleaños… Pregunta a una persona el año en que nació, luego el mes, y luego el día. Calcula su edad y felicitalo por cada cumpleaños que haya tenido.
La clase Hash
Otra clase muy útil es la Hash. Un Hash es muy parecido a un arreglo (array): tiene muchos elementos que pueden apuntar a varios objetos. Sin embargo, en un arreglo, los elementos están alineados en una sola fila y cada uno esta enumerado (desde el cero). En un hash, los elementos no están en fila (solo están agrupados), y puedes usar cualquier objeto para referir a un espacio, no solo un numero. Es bueno usar hashes cuando tienes un montón de cosas de las que quieres mantener registro, pero no necesariamente tienen que estar en orden. Por ejemplo, los colores usados en el código del tutorial:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | arregloColores = [] # Que sería lo mismo que Array.new hashColores = {} # Que sería lo mismo que Hash.new arregloColores [0] = 'verde' arregloColores [1] = 'negro' arregloColores [2] = 'cafe' hashColores ['comentarios'] = 'verde' hashColores ['variables'] = 'negro' hashColores ['cadenas'] = 'cafe' arregloColores.each do |color| puts color end hashColores.each do |tipoDeCodigo, color| puts tipoDeCodigo + ': ' + color end |
verde negro cafe comentarios: verde cadenas: cafe variables: negro
Si uso un arreglo, tengo que recordar que el elemento 0 es para comentarios, el 1 es para variables, etc. Pero si usamos un hash, es mucho mas sencillo. El elemento ‘comentarios’ guarda el color de los comentarios. No hay que recordar nada. Tal vez te diste cuenta que, cuando usamos each, los objetos en el hash no salieron en el mismo orden que los metimos. Los arreglos son para mantener las cosas en orden, un hash, no.
Aunque normalmente se utilizan cadenas para nombrar a los elementos de un hash. Se puede usar cualquier tipo de objetos, entre ellos un mismo hash o hasta un arreglo (aunque no se me ocurre ninguna razón para hacer eso…):
1 2 3 4 5 | hashRaro = Hash.new hashRaro [12] = 'monos' hashRaro [[]] = 'vacio' hashRaro [Time.new] = 'no hay mejor tiempo que el presente' |
Los arreglos y hash son buenos para diferentes cosas, y es tu decisión usar cada uno para algún problema en particular.
Extendiendo las clases
Al final del ultimo capítulo, escribiste un programa que nos regresaba escrito en letras, los números que escribiéramos. No era un método de entero, era solo un “programa” genérico de métodos. Pero, ¿no sería mejor si en lugar de tener que escribir numeroEspanol 22, escribieramos 22.to_es? Veamos como se podría hacer algo asi:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Integer def to_es if self == 5 espanol = 'cinco' else espanol = 'cincuenta y ocho' end espanol end end # Probemoslo con un par de numeros puts 5.to_es puts 58.to_es |
cinco cincuenta y ocho
Bueno, parece que funciona. :)
Así que hemos definido un método entero entrando en la clase Integer, definiendo el método ahí, y saliendo de la clase. Ahora todos los enteros tienen este método (bastante incompleto). De hecho, si no te gusta como trabaja el método to_s, puedes redefinirlo mas o menos de la misma manera, aunque no es recomendable. Es mejor dejar estos viejos métodos tal y como están y mejor hacer unos nuevos si queremos hacer algo diferente.
Creando Clases
Ya hemos visto diversas clases de objetos. Sin embargo, de vez en cuando nos encontraremos que hay tipos de objetos que Ruby no tiene. Por suerte, crear una clase nueva es tan sencillo como extender una clase vieja. Supongamos que queremos hacer unos dados en Ruby. Así es como haríamos la clase Dado:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Dado def echar 1 + rand(6) end end # Hagamos un par de dados... dados = [Dado.new, Dado.new] # ... y echemoslos dados.each do |dado| puts dado.echar end |
4 5
(Si te saltaste la sección de números aleatorios, rand(6) nos da un numero aleatorio entre 0 y 5.)
Y eso es todo! Objetos de nuestra propia creación.
Podemos definir todo tipo de métodos para nuestros objetos… pero hay algo que no estamos tomando en cuenta. Trabajar con estos objetos se siente como si estuviéramos programando antes de aprender acerca de las variables. Mira los dados por ejemplo. Podemos echarlos, y cada vez que lo hacemos nos da diferentes números. Pero si quisiéramos mantener ese número, tendríamos que crear una variable que apunte a ese número.
Sin embargo, si intentamos guardar el numero que salió en una variable (local) de echar, se borraría una vez que echar terminara. Necesitaríamos guardar el número en otro tipo de variable:
Variables de Instancia
Normalmente, cuando queremos hablar de una cadena, solo le decimos una cadena. Sin embargo, también podríamos decirle objeto cadena (string object). Algunas programadores pueden llamar a una instancia de la clase String, pero es solo una manera elegante (y larga) de decir string. Una instancia de una clase es un objeto de esa clase.
Así que una variable de instancia no es mas que una variable del objeto. Las variables locales de los métodos duran hasta que el método haya terminado. Una variable de instancia de objeto, por otro lado, dura tanto como dure el objeto. Para diferenciar una variable de instancia de una local, las de instancia tienen una @ al inicio de su nombre:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Dado def echar @numeroMostrado = 1 + rand(6) end def mostrar @numeroMostrado end end dado = Dado.new dado.echar puts dado.mostrar puts dado.mostrar dado.echar puts dado.mostrar puts dado.mostrar |
6 6 5 5
Perfecto! Así que echar, echa los dados y mostrar nos dice en cual numero que se esta mostrando. Pero, ¿que sucede si intentamos ver el numero mostrado antes de echar los dados?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Dado def echar @numeroMostrado = 1 + rand(6) end def mostrar @numeroMostrado end end # Como no voy a usar este dado otra vez, # No necesito guardarlo en una variable puts Dado.new.mostrar |
nil
Bueno, por lo menos no nos regreso un error. Aun así, no tiene sentido “des-echar” un dado, o lo que sea que nil significa aquí. Estaría mejor si pudiéramos tener un número echado justo cuando se crea el objeto. Para eso es inicializar (initialize):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class Dado def initialize # Solo echaré el dado, aunque # podriamos hacer algo mas si quisieramos # como hacer que el dado muestre el 6. echar end def echar @numeroMostrado = 1 + rand(6) end def mostrar @numeroMostrado end end puts Dado.new.mostrar |
3
Cuando se crea un objeto, el método initialize (si es que está definido) siempre es llamado.
Nuestros dados están casi perfectos. Lo único que faltaría es una manera de decir que número mostrar… ¿porque no escribes un método llamado trampa que haga eso? Regresa cuando lo hayas hecho (y probado que funciona). Asegurate que no puedan hacer que el dado muestre un 7.
Ahora veamos otro ejemplo para que nos quede mas claro como funciona. Digamos que queremos hacer una mascota virtual, un bebé dragón. Como la mayoría de los bebés, debe de poder comer, dormir y defecar, lo que significa que necesitaremos poder darle de comer, ponerlo en la cama y pasearlo. Internamente, nuestro dragón necesitara mantener un registro de si esta hambriento, cansado o si tiene que ir al baño, pero no podremos ver eso cuando interactuemos con el dragón, exactamente como pasa con un bebé humano, no podemos preguntarle “¿Tienes hambre?”. También le agregaremos otras maneras divertidas para interactuar con nuestro bebé dragón, y cuando nazca le pondremos un nombre. (Lo que sea que pases al nuevo método es pasado también al método initialize). Bien, comencemos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | class Dragon def initialize nombre @nombre = nombre @dormido = false @algoenElEstomago = 10 # Está lleno @algoenElIntestino = 0 # No tiene que ir al baño puts @nombre + ' ha nacido.' end def alimentar puts 'Alimentas a ' + @nombre + '.' @algoenElEstomago = 10 tiempoPasa end def caminar puts 'Haces que ' + @nombre + ' camine.' @algoenElEstomago = 0 tiempoPasa end def acostar puts 'Haces que ' + @nombre + ' se acueste.' @dormido = true 3.times do if @dormido tiempoPasa end if @dormido puts @nombre + ' ronca, llenando la habitacion de humo.' end end if @dormido @dormido = false puts @nombre + ' se esta despertando lentamente.' end end def jugar puts 'Juegas con ' + @nombre + '.' puts @nombre + ' se rie y quema tus pestañas.' tiempoPasa end def arrullar puts 'Arrullas suavemente a ' + @nombre + '.' @dormido = true puts 'El se comienza a dormir...' tiempoPasa if @dormido @dormido = false puts '... pero despierta cuando dejas de arrullarlo.' end end private # "private" significa que los metodos definidos aqui son # metodos internos del objeto. (Puedes alimentar # al dragon, pero no le puedes preguntar si está hambriento.) def hambriento? # Los nombres de los metodos pueden terminar con "?". # Normalmente, solo hacemos esto si el metodo # regresa un valor de true o false. @algoenElEstomago <= 2 end def ganasdeir? @algoenElIntestino >= 8 end def tiempoPasa if @algoenElEstomago > 0 # Movemos comida del estomago al intestino @algoenElEstomago = @algoenElEstomago -1 @algoenElIntestino = @algoenElIntestino +1 else # Nuestro dragon esta hambriento! if @dormido @dormido = false puts 'Se despierta de repente!' end puts @nombre + ' se esta muriendo de hambre! Se siente desesperado, TE ODIA!!' exit # Esto hace que termine el programa end if @algoenElIntestino >= 10 @algoenElIntestino = 0 puts 'Ups! ' + @nombre + ' tuvo un accidente...' end if hambriento? if @dormido @dormido = false puts 'Se despierta de repente!' end puts 'El estomago de ' + @nombre + ' esta gruñiendo...' end if ganasdeir? if @dormido @dormido = false puts 'Se despierta de repente!' end puts @nombre + ' comienza a bailar de que no se aguanta las ganas...' end end end mascota = Dragon.new 'Norberto' mascota.alimentar mascota.jugar mascota.caminar mascota.acostar mascota.arrullar mascota.acostar mascota.acostar mascota.acostar mascota.acostar |
Norberto ha nacido. Alimentas a Norberto. Juegas con Norberto. Norberto se rie y quema tus pestañas. Haces que Norberto camine. Norberto se esta muriendo de hambre! Se siente desesperado, TE ODIA!!
Sería mejor si fuera un programa interactivo, pero puedes hacer esa parte después. Solo quería mostrar las partes directamente relacionadas con crear una nueva clase llamada dragón.
Vimos unas cuantas cosas nuevas en el ejemplo. La primera es simple: exit termina el programa en ese momento. La segunda es la palabra private la cual metimos justo en la mitad de la definición de nuestra clase. Podría haberla dejado fuera, pero quería reforzar la idea de que hay ciertos métodos que puedes hacer al dragón, y que hay otros que suceden dentro del dragón. Puedes pensar que estas suceden como cuando uno usa un carro, a no ser que seas un mecánico automotriz, todo lo que necesitas saber es como usar el acelerador, freno y volante. Un programador podría decir que es la interfaz publica de tu carro. Como la bolsa de aire sabe cuando debe de salir, es algo interno del carro; y un usuario normal (conductor) no necesita saber todo esto.
De hecho, para un ejemplo mas concreto, hablemos de como debes de representar un carro en un videojuego. Primero, vas a decidir como quieres que se vea la interfaz publica; en otras palabras, cuales métodos van a poder ser llamados por la gente en uno de tus objetos carro? Van a poder presionar el acelerador y el freno, pero también van a poder especificar que tan fuerte están presionando los pedales. (Hay una gran diferencia entre presionarlo ligeramente y a fondo.) También van a necesitar el volante, y una vez mas, van a necesitar decir que tan fuerte están girando el volante. Y, si quieres ir un poco mas lejos, agregamos el clutch, intermitentes, direccionales, lanzacohetes, nitro, capacitor de flujo, etc… depende de que tipo de juego estés haciendo.
De manera interna en un carro, van a estar sucediendo muchísimo mas cosas que esas; otras cosas que un carro necesita son la velocidad, dirección y la posición. Estos atributos podrían ser modificados presionando el acelerador o el freno y girando el volante, pero el usuario no podría poner la posición directamente (sería como teletransportarse). También podrías tener un registro de los daños, si se te poncho una llanta, etc. Estos serían internos a tu objeto carro.
Algunas cosas para intentar
- Haz una clase ArbolDeNaranjas. Debe de tener un método altura, que nos regrese la altura, y un método pasanDoceMeses, que, cuando es llamado, hace que el árbol envejezca un año. Cada año que pasa el árbol crece (el tiempo que consideres que un árbol de naranjas crece en un año), y después de un cierto numero de años (también decidelo tu) el árbol debe morir. En los primeros años no debe de producir fruta, pero después de un tiempo debe de hacerlo, y entre mas años tenga el árbol, debe de producir mas naranjas que uno mas joven. Y, por supuesto, uno debe de poder contarLasNaranjas (que nos regresa el numero de naranjas en el árbol), y recojerUnaNaranja (reduce el @numeroDeNaranjas de uno en uno, y nos regresa una cadena diciendonos cuantas naranjas quedan, o si ya no hay, que nos diga que ya no quedan mas naranjas para este año). Asegurate que las naranjas que no recojas un año, se caigan antes del año siguiente.
- Escribe un programa para que puedas interactuar con tu bebé dragón. Vas a poder escribir comandos como alimentar y caminar, y que esos comandos manden llamar los métodos de tu dragón. Por supuesto, como lo que vas a escribir son cadenas, vas a tener que escribir algún método que dirija, donde el programa revise cual cadena fue escrita y llame el método apropiado.
- Y eso sería todo respecto a este tema… Excepto que no he mencioné las clases para hacer cosas como mandar un correo, guardar y cargar archivos en la computadora, como crear ventanas y botones, o mundos en 3D o cualquier otra cosa! Bueno, es que hay tantas clases que puedes usar que no es posible que te enseñe todas; ni siquiera se que es lo que hacen la mayoría de ellas. Lo que puedo decirte es donde encontrar mas sobre ellas, para que puedas aprender sobre las que quieres usar al programar. Aunque antes de mandarte a ellas, hay otra característica de Ruby de la que debes de saber, algo que la mayoría de lenguajes no tiene pero que yo sin ellas no podría vivir: bloques y procedimientos.
© 2003-2009 Chris Pine
Etiquetas: Programación, Ruby, Tutorial







