Aprende a programar, 9.Clases

Agosto 25th, 2009, 13:28H · Temas: Programación, Ruby, Tutoriales · Imprimir

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

Learn to Program, by Chris Pine

Etiquetas: , ,

Deja un comentario

Tu dirección de email no se publicará. Para mostrar tu foto en los comentarios usa Gravatar
Por favor, añade http://
aviso: se admite XHTML.

Suscríbete al comentario vía RSS

GeekColima

El blog de un geek sin dinero y sin gadgets... T_T