Programación Con Objetos - Resumen Mumuki

Descargar como docx, pdf o txt
Descargar como docx, pdf o txt
Está en la página 1de 55

Programación con Objetos

Resumen mumuki

Objetos y mensajes
Objeto: Es cualquier entidad que puede hacer algo por nosotros para resolver un problema.
Los objetos pueden hacer varias cosas y comunicarse entre sí.
Se usa un lenguaje llamado Ruby.

En este paradigma absolutamente todo es un objeto: también los números,


las cadenas de texto (o strings) y los booleanos.

Un aspecto muy importante de los objetos es que tienen identidad: cada


objeto sabe quién es y gracias a esto sabe también que es diferente de los
demás. Por ejemplo, Pepita sabe que ella es diferente de Norita, y viceversa.

En Ruby, podemos comparar por identidad a dos objetos utilizando el


operador == de la siguiente forma:

Pepita==Norita

Ya entendimos que en un ambiente hay objetos, y que cada uno de ellos tiene
identidad: sabe que es diferente de otro.

Para comunicarnos con los objetos, debemos enviarles mensajes. Cuando un


objeto recibe un mensaje, este responde haciendo algo.
Se le envía un mensaje para que realice una acción de la siguiente manera:
Pepita.cantar!
En el mundo de los objetos, sólo tiene sentido enviarle un mensaje a un objeto
si lo entiende, es decir, si sabe hacer algo como reacción a ese mensaje. De lo
contrario, se lanzará un error
Analicemos la sintaxis del envío de mensajes:
1. Pepita.energia es un envío de mensaje, también llamado colaboración;
2. energia es el mensaje;
3. energia es el nombre del mensaje (en este caso es igual, pero ya
veremos otros en los que no);
4. Pepita es el objeto receptor del mensaje.
Es importante respetar la sintaxis del envío de mensajes. Por ejemplo, las
siguientes NO son colaboraciones válidas, porque no funcionan o no hacen lo
que deben:

energia
Pepita energia
Pepita..energia
Como vimos, un objeto puede entender múltiples mensajes; a este conjunto
de mensajes que podemos enviarle lo denominamos interfaz. Por ejemplo, la
interfaz de Pepita es:

● energia: nos dice cuanta energía tiene (un número);


● cantar!: hace que cante;
● comer_lombriz!: hace que coma una lombriz;
● volar_en_circulos!: hace que vuele en circulos.

Lo cual también se puede graficar de la siguiente forma:

nil es la forma que tenemos en Ruby de representar a "la nada" (que, casualmente, ¡también
es un objeto!).

Cuando se envía un mensaje a un objeto, y este lo entiende, puede reaccionar


de dos formas diferentes:

● Podría producir un efecto, es decir hacer algo. Por ejemplo, el mensaje


cantar! reproduce el sonido del canto de Pepita.
● O también podría devolver otro objeto. Por ejemplo el mensaje energia
devuelve siempre un número.

En realidad, un mensaje podría reaccionar con una combinación de las formas


anteriores: tener un efecto y devolver algo. Pero esto es una muy mala idea.
Por ejemplo efecto que producen los mensajes comer_lombriz! y
volar_en_circulos! es el de alterar la energía de Pepita. En concreto:

● comer_lombriz! hace que la energia de Pepita aumente en 20 unidades;


● volar_en_circulos! hace que la energia de Pepita disminuya en 10
unidades.

Como convención, a los mensajes con efecto (es decir, que hacen algo) les
pondremos un signo de exclamación ! al final.

Podemos sacar dos conclusiones:

1. Los objetos no reaccionan necesariamente siempre igual a los mismos


mensajes. Podrían hacer cosas diferentes, o en este caso, devolver
objetos distintos.
2. ¡Un programa es simplemente una secuencia de envío de mensajes!

Puede haber más de un objeto que entienda el mismo mensaje. Notá que sin
embargo no todos los objetos están obligados a reaccionar de igual forma
ante el mismo mensaje. Ejemplo:

Pepita.cantar!
=> "pri pri pri"

Norita.cantar!
=> "priiiip priiiip"

Mercedes.cantar!
=> "♪ una voz antigua de viento y de sal ♫"

Esto significa que dos o más objetos pueden entender un mismo mensaje,
pero pueden comportarse de formas diferentes.

Recordá que la interfaz es el conjunto de mensajes que un objeto entiende.


Por lo tanto, si queremos ver cual interfaz comparten dos objetos, tenemos
que pensar en la intersección entre los conjuntos de mensajes de cada uno
(es decir, aquellos que son iguales).
Argumentos

Por ejemplo, si queremos que Pepita coma una cierta cantidad de alpiste que
no sea siempre la misma, necesitamos de alguna manera indicar cuál es esa
cantidad. Esto podemos escribirlo de la siguiente forma:

Pepita.comer_alpiste! 40

Allí, 40 es un argumento del mensaje, representa en este caso que vamos a


alimentar a pepita con 40 gramos de alpiste. Un mensaje podría tomar más de
un argumento, separados por coma.

Como ves, si enviás un mensaje con una cantidad incorrecta de argumentos...

Pepita.comer_alpiste! 6, Norita
# wrong number of arguments (2 for 1) (ArgumentError)

...el envío del mensaje también fallará.

Dicho de otra forma, un mensaje queda identificado no sólo por su nombre


sino también por la cantidad de parámetros que tiene: no es lo mismo
comer_alpiste! que comer_alpiste! 67 que comer_alpiste! 5, 6, son todos mensajes
distintos. Y en este caso, Pepita sólo entiende el segundo.

Un detalle: en Ruby, a veces, los paréntesis son opcionales. Por eso, cuando
no sean imprescindibles los omitiremos.
Es fácil ver que en Pepita.volar_hacia! Barreal el objeto receptor es Pepita, el
mensaje volar_hacia! y el argumento Barreal; pero ¿dónde queda eso de objeto y
mensaje cuando hacemos, por ejemplo, 2 + 3?

Como ya dijimos, todas nuestras interacciones en un ambiente de objetos


ocurren enviando mensajes y las operaciones aritméticas no son la excepción
a esta regla.

En el caso de 2 + 3 podemos hacer el mismo análisis:

● el objeto receptor es 2;
● el mensaje es +;
● el argumento es 3.

Y de hecho, ¡también podemos escribirlo como un envío de mensajes


convencional!

ej:

5.+ 6

= 11

3.< 27

=true

Pepita.== Norita

=false

En un mundo de objetos, todo lo que tenemos son objetos y mensajes. A


estos últimos, podemos distinguirlos según la forma en que se escriben:

Mensajes de palabra clave. Su nombre está compuesto por una o varias


palabras, puede terminar con un signo de exclamación ! o de pregunta ?, y se
envía mediante un punto. Además,

● pueden no tomar argumentos, como Rayuela.anio_de_edicion;


● o pueden tomar uno o más argumentos, separados por coma:
SanMartin.cruzar! LosAndes, Mula.

Operadores. Son todos aquellos cuyo "nombre" se compone de uno o más


símbolos, y se envían simplemente escribiendo dichos símbolos. En cuanto a
los argumentos,

● pueden no tomar ninguno, como la negación !true;


● o pueden tomar uno (y solo uno), como Orson == Garfield o energia + 80.

Como vimos, también se pueden escribir como mensajes de palabra clave


(aunque no parece buena idea escribir 1.== 2 en vez de 1 == 2 ).
Cualquier envío de mensajes que devuelva algo es una expresión válida, y
puede ser usada en cualquier lugar en que se espera un objeto. Por ejemplo,
las siguientes colaboraciones son válidas:

Fitito.cargar_nafta!(120 * 4)

Fitito.cargar_nafta!(Fitito.capacidad_tanque_nafta - Fitito.nafta_disponible) #Carga al Fitito lo


necesario para completar su tanque. Para ello le pregunta al Fitito su capacidad y la nafta que
tiene en este momento.

Definiendo objetos: métodos y estado


Inicialmente en el ambiente sólo existen objetos simples como números,
strings y booleanos.

En Ruby, si quisiéramos definir a Norita, escribiríamos el siguiente código:


module Norita
end
La definición de un objeto se inicia con la palabra reservada module, luego el
nombre del objeto (con la primera letra en mayúscula) y su fin se indica con
un end

Para que un objeto entienda un mensaje debemos "enseñarle" cómo hacerlo, y


para ello es necesario definir un método dentro de ese objeto:

module Pepita
def self.cantar!
end
end

Un método es, entonces, la descripción de qué hacer cuando se recibe un


mensaje del mismo nombre.

Dos cosas muy importantes a tener en cuenta :

● Todos los métodos comienzan con def y terminan con end. Si nos falta
alguna de estos dos la computadora no va a entender nuestra solución.
● Todos los métodos que pertenezcan al mismo objeto van dentro del
mismo module.

No te olvides de que el ! forma parte del nombre del mensaje y por lo tanto
tenés que escribirlo.
Acabamos de aprender una de las reglas fundamentales del envío de
mensajes: si a un objeto no le decímos cómo reaccionar ante un mensaje, y
se lo envíamos, no lo entenderá y nuestro programa se romperá. Y la forma de
hacer esto es definiendo un método.
Si solo se define un método pero no se le indica que hacer, tendremos un
método vacío, que solo evita que el programa se rompa, pero no hacen nada.
Para que el método haga algo se le debe indicar, por ejemplo:
module Pepita
@energia = 100

def self.volar_en_circulos!
@energia = @energia - 10
end
end

Decimos que Pepita conoce o tiene un nivel de energía, que es variable, e


inicialmente toma el valor 100. La energía es un atributo de nuestro objeto, y la
forma de asignarle un valor es escribiendo @energia = 100.

Por otro lado, cuando Pepita recibe el mensaje volar_en_circulos!, su energía


disminuye: se realiza una nueva asignación del atributo y pasa a valer lo que
valía antes (o sea, @energia), menos 10.

Como la operación @energia = @energia - 10 es tan común, se puede escribir


@energia -= 10. Como te imaginarás, también se puede hacer con la suma.

Los atributos (los cuales escribiremos anteponiendo @), son objetos que nos
permiten representar una característica de otro objeto. Un objeto conoce
todos sus atributos por lo que puede enviarles mensajes, tal como hicimos
con @energia.

Entonces, si le pude enviar mensajes a @energia, ¿eso significa que los números
también son objetos?

¡Claro que sí! ¡Todo-todo-todo es un objeto!

Hasta ahora los métodos que vimos solo producían un efecto. Si bien solo
pueden devolver una cosa, ¡pueden producir varios efectos!

Solo tenés que poner uno debajo del otro de la siguiente forma:
def self.comprar_libro!
@plata -= 300
@libros += 1
end
Antes te mostramos que si enviamos el mensaje energia, fallará:
Pepita.energia
undefined method `energia' for Pepita:Module (NoMethodError)

El motivo es simple: los atributos NO son mensajes.

Entonces, ¿cómo podríamos consultar la energía de Pepita? Definiendo un


método, ¡por supuesto!
module Pepita
#...atributos y métodos anteriores...

def energia
@energia
end
end

Los objetos pueden tener múltiples atributos y al conjunto de estos atributos


se lo denomina estado. Por ejemplo, si miramos a Pepita:
module Pepita
@energia = 100
@ciudad = Obera

#...etc...
end

Lo que podemos observar es que su estado está conformado por ciudad y


energia, dado que son sus atributos.

El estado es siempre privado, es decir, solo el objeto puede utilizar sus


atributos, lo que explica por qué las siguiente consultas que hicimos antes
fallaban.

A estos métodos que sirven para conocer el valor de un atributo los llamamos
métodos de acceso o simplemente accessors, por su nombre en inglés.
Contamos con el mensaje abs que entienden los números y nos retorna su
valor absoluto:
17.abs
=> 17

(-17).abs
=> 17

(1710 - 1040).abs
=> 670

(1040 - 1710).abs
=> 670

(1040 - 1710).abs / 2
=> 335

Un objeto (en ese caso, Pepita) le puede enviar mensajes a otro que conozca
(en ese caso, ciudades como Obera o BuenosAires):

module Pepita
# ...etc...

def self.volar_hacia!(destino)
@energia -= (@ciudad.kilometro - destino.kilometro).abs / 2
@ciudad = destino
end
end

Esto se conoce como delegar una responsabilidad, o simplemente, delegar: la


responsabilidad de saber en qué kilómetro se encuentra es de la ciudad, y no
de Pepita.

A veces nos va a pasar que un objeto tiene un método muy complejo, y nos
gustaría subdividirlo en problemas más chicos que el mismo objeto puede
resolver. Pero, ¿cómo se envía un objeto mensajes a sí mismo?

Un objeto puede enviarse un mensaje a sí mismo fácilmente usando self como


receptor del mensaje.
module Pepita
# ...etc...

def self.volar_hacia!(destino)
self.gastar_energia! destino #¡Ojo! No hicimos Pepita.gastar_energia!(destino)
@ciudad = destino
end

def self.gastar_energia!(destino)
@energia -= (@ciudad.kilometro - destino.kilometro).abs / 2
end
end

La delegación es la forma que tenemos en objetos de dividir en subtareas:


separar un problema grande en problemas más chicos para que nos resulte
más sencillo resolverlo.

A diferencia de lenguajes sin objetos, aquí debemos pensar dos cosas:

1. cómo dividir la subtarea, lo cual nos llevará a delegar ese


comportamiento en varios métodos;
2. qué objeto tendrá la responsabilidad de resolver esa tarea.

Polimorfismo y encapsulamiento
En Ruby, es una convención que los mensajes que devuelven booleanos (o
sea, verdadero o falso) terminen con un ?.

Intentá respetarla cuando crees tus propios mensajes, acordate que uno de
los objetivos del código es comunicar nuestras ideas a otras personas... y las
convenciones, muchas veces, nos ayudan con esto.

en programación existe una herramienta llamada alternativa condicional.

En Ruby, como en muchos otros lenguajes, esto se escribe con la palabra


reservada if. Por ejemplo:
module Jose
def self.acomodar_habitacion!
self.ordenar!
if self.tiene_sabanas_sucias?
self.cambiar_sabanas!
end
self.tender_la_cama!
end
end
Como acabamos de ver, la alternativa condicional es como en otros
lenguajes. La diferencia radica en su sintaxis, es decir, cómo la escribimos.
Hay veces que con un if alcanza, pero otras queremos hacer algo si no se
cumple una condición. Como ya te podrás imaginar, donde hay un if ¡cerca
anda un else!
module Jardinero
def self.cuidar!(planta)
if planta.necesita_agua?
3.times { self.regar! planta }
else
self.sacar_bichos! planta
end
end
end
times es un mensaje que entienden los números que sirve para ejecutar una
porción de código varias veces. En este caso regaríamos 3 veces la planta
recibida como argumento.

Algunas veces vamos a tener condiciones anidadas. En otras palabras, un if


dentro de un if o un else.
module Docente
def self.nota_conceptual(nota)
if nota > 8
"Sobresaliente"
else
if nota > 6
"Satisfactoria"
else
"No satisfactoria"
end
end
end
end
En Ruby, podemos simplicar la manera de escribir un if dentro un else con elsif.
Por ejemplo
def self.nota_conceptual(nota)
if nota > 8
"Sobresaliente"
else
if nota > 6
"Satisfactoria"
else
"No satisfactoria"
end
end
end

Lo podemos escribir:
def self.nota_conceptual(nota)
if nota > 8
"Sobresaliente"
elsif nota > 6
"Satisfactoria"
else
"No satisfactoria"
end
end
Una forma posible de cambiar el objeto al que le enviamos mensajes es
modificando el valor de un atributo.

¿Qué pasa si dos objetos, como Pepita, Norita o Pepo son capaces de
responder a un mismo mensaje? Podemos intercambiar un objeto por otro sin
notar la diferencia, como experimentaste recién.

Este concepto es fundamental en objetos, y lo conocemos como


polimorfismo. Decimos entonces que dos objetos son polimórficos cuando
pueden responder a un mismo conjunto de mensajes y hay un tercer objeto
que los usa indistintamente.

Dos objetos son polimórficos para un tercer objeto cuando este puede
enviarles los mismos mensajes, sin importar cómo respondan o qué otros
mensajes entiendan.

Aunque parezca que no tiene mucho sentido, es común que trabajando con
objetos necesitemos forzar el polimorfismo.

En este caso le podemos agregar a Norita un mensaje que no hace nada, con
el único objetivo de que sea polimórfica con sus compañeras aves.

En los ejercicios le habíamos incluido a Pachorra y Emilce un mensaje


firmar_contrato!(ave) que modificaba su estado, es decir, alguno de sus
atributos. A estos mensajes que solo modifican un atributo los conocemos
con el nombre de setters, porque vienen del inglés set que significa
establecer, ajustar, fijar.
Para estos casos, solemos utilizar una convención que se asemeja a la forma
que se modifican los atributos desde el propio objeto, pudiendo ejecutar el
siguiente código desde una consola:
Emilce.ave = Pepita

Esto se logra definiendo el método ave=, todo junto, como se ve a


continuación:
module Emilce
def self.ave=(ave_nueva)
@ave = ave_nueva
end

def self.entrenar_ave!
53.times { @ave.volar_en_circulos! }
@ave.comer_alpiste!(8)
end
end
Como ya te habíamos contado en una lección anterior, a estos métodos que
solo sirven para acceder o modificar un atributo los llamamos métodos de
acceso o accessors. Repasando, los setters son aquellos métodos que
establecen el valor del atributo. Mientras que los getters son aquellos que
devuelven el valor del atributo.

La convención en Ruby para estos métodos es:

● Los setters deben llevar el mismo nombre del atributo al que están
asociados, agregando un = al final.
● Los getters usan exactamente el mismo nombre que el atributo del cual
devuelven el valor pero sin el @.
● Aquellos getters que devuelven el valor de un atributo booleano llevan ?
al final.

Ya aprendiste cómo crear getters y setters para un atributo, pero ¿siempre


vamos a querer ambos?

La respuesta es que no, y a medida que desarrolles más programas y


dominios diferentes tendrás que construir tu propio criterio para decidir
cuándo sí y cuándo no.

Recordá la convención para nombrar los métodos de acceso que


mencionamos antes:

● Para los getters, que sirven para obtener el valor de un atributo, usamos
el mismo nombre que este.
● Para los setters, que sirven para fijar el valor de un atributo, usamos el
mismo nombre que este pero con un = al final.

Si hacemos bien las cosas, quien use nuestros objetos sólo verá lo que
necesite para poder interactuar con ellos. A esta idea la conocemos como
encapsulamiento, y es esencial para la separación de responsabilidades de la
que veníamos hablando.

Referencias
Hasta ahora, en objetos, un programa es simplemente una secuencia de
envíos de mensajes. Por ejemplo, éste es un programa que convierte en
mayúsculas al string "hola".
"hola".upcase
=> "HOLA"

Sin embargo, podemos hacer algo más: declarar variables. Por ejemplo,
podemos declarar una variable saludo, inicializarla con "hola", enviarle
mensajes y esperar el mismo resultado que para el programa anterior.
saludo = "hola"
saludo.upcase
=> "HOLA"

Hasta ahora habíamos visto que tenemos objetos y mensajes, y sólo le


podíamos enviar mensajes a los objetos, como Pepita, 14, u "hola". ¿Le
acabamos de enviar un mensaje a una variable?

Sí y no. Veamos por qué...

Hasta ahora venimos insistiendo con que, en la programación en objetos, le


enviamos mensajes a los objetos. ¡Y no mentimos!

Sucede que en realidad las cosas son un poco más complejas: no conocemos
a los objetos directamente, sino a través de etiquetas llamadas referencias.
Entonces cuando tenemos una declaración de variable como ésta...
saludo = "hola"

...lo que estamos haciendo es crear una referencia saludo que apunta al objeto
"hola", que representamos mediante una flechita:
Y cuando tenemos...
saludo.upcase

...le estamos enviando el mensaje upcase al objeto "hola", a través de la


referencia saludo, que es una variable.

¡Bien! Acabás de crear este ambiente, en criollo, el lugar donde viven los
objetos con los cuales podemos interactuar:

También podemos hacer cosas como "hola".size. Allí no hay ninguna variable:
¿dónde está la referencia en ese caso?

Como vemos, los objetos son las "bolitas" y las referencias, las "flechitas".
Pero, ¿cuál es la diferencia entre variable y referencia?

Sucede que hay muchos tipos de referencias, y una de ellas son las variables
del programa. Pero, ¿no podíamos enviarles mensajes "directamente" al
objeto? Por ejemplo, ¿dónde están las referencias en estos casos?:
#¿A qué referencia el envío upcase?
"ni hao".upcase
#¿Y a qué referencia el envío size?
saludo.upcase.size

¡Simple! Cuando enviamos mensajes a objetos literales como el 2, el true u


"hola", o expresiones, estamos conociendo a esos objetos a través de
referencias implícitas, que son temporales (sólo existen durante ese envío de
mensajes) y anónimas (no tienen un nombre asociado).
"ni hao".upcase
^
+-- Acá hay una referencia implícita al objeto "ni hao"

saludo.upcase.size
^
+-- Y acá, otra referencia implícita a "HOLA"

Por eso, si luego te interesa hacer más cosas con ese objeto, tenés que crear
una referencia explícita al mismo . Las referencias explícitas son las que
vimos hasta ahora. Por ejemplo:
saludoEnChino = "ni hao"

Supongamos que tenemos el siguiente programa:


otro_saludo = "buen día"

despedida = otro_saludo

Como vemos, estamos asignando otro_saludo a despedida. ¿Qué significa esto?


¿Acabamos de copiar el objeto "buen día", o más bien le dimos una nueva
etiqueta al mismo objeto? Dicho de otra forma: ¿apuntan ambas variables al
mismo objeto?
El mensaje equal? nos dice si dos objetos son el mismo. Veamos qué pasó
con las pruebas del ejercicio anterior:
otro_saludo = "buen día" # se crea la variable otro_saludo que referencia al objeto "buen día"

despedida = otro_saludo # se crea la variable despedida que, por asignarle la referencia


otro_saludo, apunta al mismo objeto

"buen día".equal? "buen día"

=> false

despedida.equal? "buen día"

=> false

En ambos casos el resultado fue false, dado que aquellos strings son objetos
distintos, a pesar de que tengan los mismos caracteres. Cada vez que
escribimos un string estamos creando un nuevo objeto. Sin embargo:
otro_saludo.equal? otro_saludo

=> true

despedida.equal? otro_saludo

=> true

¿Por qué? ¡Simple! Ambas referencias, otro_saludo y despedida, apuntan al


mismo objeto. La moraleja es que declarar una variable significa agregar una
nueva referencia al objeto existente, en lugar de copiarlo:

Distinto sería si hacemos:


otro_saludo = "buen día"

despedida = "buen día"

Lo cual da como resultado este ambiente:

Ya entendimos que dos strings con el mismo contenido no necesariamente


son el mismo objeto. Pero esto puede ser poco práctico . ¿Cómo hacemos si
realmente queremos saber si dos objetos, pese a no ser el mismo, tienen el
mismo estado?

Entonces, ¿qué pasa si lo que quiero es comparar los objetos no por su


identidad, sino por que representen la misma cosa?

Pensemos un caso concreto. ¿Hay forma de saber si dos strings representan


la misma secuencia de caracteres más allá de que no sean el mismo objeto?
¡Por supuesto que la hay! Y no debería sorprendernos a esta altura que se
trate de otro mensaje:
"hola" == "hola"

=> true

"hola" == "adiós"

=> false

"hola".equal? "hola"

=> false

El mensaje == nos permite comparar dos objetos por equivalencia; lo cual se


da típicamente cuando los objetos tienen el mismo estado. Y como vemos,
puede devolver true, aún cuando los dos objetos no sean el mismo.
¡Ojo! A diferencia de la identidad, que todos los objetos la entienden sin tener
que hacer nada especial, la equivalencia es un poco más complicada.

● Por defecto, si bien todos los objetos también la entienden, delega en la


identidad, así que muchas veces es lo mismo enviar uno u otro mensaje
● Y para que realmente compare a los objetos por su estado, vos tenés
que implementar este método a mano en cada objeto que crees. Los
siguientes objetos ya la implementan:
○ Listas
○ Números
○ Strings
○ Booleanos

¿Y qué hay de los objetos que veníamos definiendo hasta ahora? Por ejemplo

a Fito, le aumenta la felicidad cuando come:

module Fito

@felicidad = 100

def self.comer!(calorias)

@felicidad += calorias * 0.001

end

def self.felicidad

@felicidad

end

end
A objetos como Fito se los conocen como objetos bien conocidos: cuando los

definimos no sólo describimos su comportamiento (comer!(calorias) y felicidad)

y estado (@felicidad), sino que además les damos un nombre o etiqueta a

través de la cual podemos conocerlos. ¿Te suena?

¡Adiviná! Esas etiquetas también son referencias . Y son globales, es decir que

cualquier objeto o programa puede utilizarla.

Lo importante es que entiendas que siempre estamos enviando el mensaje al

objeto a través de una referencia.

Además de los que ya vimos, hay más tipos de referencias: los atributos.

Por ejemplo, si la golondrina Pepita conoce siempre su ciudad actual...

module Pepita

@energia = 100

def self.volar_en_circulos!

@energia -= 10

end

def self.ciudad=(una_ciudad)

@ciudad = una_ciudad
end

def self.ciudad

@ciudad

end

end

Y en algún momento esta pasa a ser Iruya, el diagrama de objetos será el

siguiente:

Nuevamente, acá vemos otro caso de múltiples referencias: el objeto que

representa a la ciudad de Iruya es globalmente conocido como Iruya, y también

conocido por Pepita como ciudad.

¿cuántas referencias a Iruya hay en este programa?

module Pepita

@energia=100

@ciudad=Iruya

def self.volar_en_circulos!

@energia-=10

end
def self.ciudad=(una_ciudad)

@ciudad=una_ciudad

end

def self.ciudad

@ciudad

end

end

module Iruya

end

Hay tres referencias a este objeto:

1. La propia referencia Iruya

2. El atributo @ciudad de Pepita

3. una_ciudad: porque los parámetros de los métodos ¡también son

referencias! Sólo que su vida es más corta: viven lo que dure la

evaluación del método en el que se pasan.

Miremos este método con más detenimiento:

def volar_en_circulos!

@energia = @energia - 10

end

Lo que estamos haciendo es cambiar la energía de Pepita: pasa de su valor

actual, @energia, a ese valor menos 10. Por ejemplo, pasa de 100 a 90.

¿Significa esto que el 100 se transforma en un 90 ?

No, en absoluto. @energia es una referencia a un objeto, que inicialmente

apunta al objeto 100:


Luego, la operación de asignación cambia ese apuntador, que pasa a

referenciar al 90:

Dada las siguientes definiciones:

module Pepita

@energia = 100

def volar_en_circulos!

@energia -= 10

end

def self.ciudad=(una_ciudad)

@ciudad = una_ciudad

end

end

module Iruya

end
¿Cuáles de las siguientes opciones son referencias?

Si bien:

● Pepita e Iruya son objetos bien conocidos;

● @energiay @ciudad son atributos;

● y una_ciudad es un parámetro;

¡Todas son referencias!

Colecciones

Una lista de objetos: es un tipo de colección en la cual los elementos pueden

repetirse. Es decir, el mismo objeto puede aparecer más de una vez.

Por ejemplo, la lista de números 2, 3, 3 y 9 se escribe así:


[2, 3, 3, 9]

La lista es un tipo de colección donde el orden importa. Por lo tanto, no es lo


mismo poner un objeto al principio que agregarlo al final.
Ejemplo:
Ya tenemos creada la Juegoteca con algunos juegos:
Todas las colecciones entienden una serie de mensajes que representan
operaciones o consultas básicas sobre la colección.
Por ejemplo, podemos agregar un elemento enviándole push a la colección o
quitarlo enviándole delete:
numeros_de_la_suerte = [6, 7, 42]
numeros_de_la_suerte.push 9
# Agrega el 9 a la lista...
numeros_de_la_suerte.delete 7
# ...y quita el 7.

También podemos saber saber si un elemento está en la colección usando


include?:

numeros_de_la_suerte.include? 6
# Devuelve true, porque contiene al 6...
numeros_de_la_suerte.include? 8
# ...devuelve false, porque no contiene al 8.

Finalmente, podemos saber la cantidad de elementos que tiene enviando size:


numeros_de_la_suerte.size
# Devuelve 3, porque contiene al 6, 42 y 9

Otro tipo muy común de colecciones son los sets (conjuntos), los cuales
tienen algunas diferencias con las listas:

● no admiten elementos repetidos;


● sus elementos no tienen un orden determinado.

Vamos a ver un ejemplo transforma una lista en un set utilizando to_set:


numeros_aleatorios = [1,27,8,7,8,27,87,1]
numeros_aleatorios
=> [1,27,8,7,8,27,87,1]
numeros_aleatorios.to_set
=> #<Set: {1, 27, 8, 7, 87}>
Tanto las listas como los sets entienden estos mensajes, o dicho de otro
modo, son polimórficos para push, delete, include? y size.

Sin embargo, los siguientes mensajes...


numeros_de_la_suerte = [6, 7, 42]
numeros_de_la_suerte.first
# Nos retorna el primer elemento de la lista
numeros_de_la_suerte.last
# Nos retorna el último de la lista
numeros_de_la_suerte.index 7
# Nos retorna la posición de un elemento en la lista

... no podemos enviárselos a un set porque sus elementos están ordenados.

Hay una diferencia notable entre los primeros dos mensajes (push y delete) y
los otros dos (include? y size):

1. push y delete, al ser evaluados, modifican la colección. Dicho de otra


forma, producen un efecto sobre la lista en sí: agregan o quitan un
elemento del conjunto.
2. include? y size sólo nos retornan información sobre la colección. Son
métodos sin efecto.

Bloques
Los bloques son objetos que representan un mensaje o una secuencia de
envíos de mensajes, sin ejecutar, lista para ser evaluada cuando corresponda.
La palabra con la que se definen los bloques en Ruby es proc. Por ejemplo, en
este caso le asignamos un bloque a incrementador:
un_numero = 7
incrementador = proc { un_numero = un_numero + 1 }

Ahora avancemos un pasito: en este segundo ejemplo, al bloque { otro_numero


= otro_numero * 2 } le enviamos el mensaje call, que le indica que evalúe la
secuencia de envíos de mensajes dentro de él.
otro_numero = 5
duplicador = proc { otro_numero = otro_numero * 2 }.call

O sea el mensaje “call” es el que hace que se ejecute el código dentro del
bloque. Si no lo ponemos, no pasa nada.
Los bloques también pueden recibir argumentos para su aplicación. Por
ejemplo, sumar_a_otros_dos recibe dos argumentos, escritos entre barras
verticales | y separados por comas:
un_numero = 3
sumar_a_otros_dos = proc { |un_sumando, otro_sumando| un_numero = un_numero +
un_sumando + otro_sumando }

Para aplicar el bloque sumar_a_otros_dos, se le pasan los argumentos


deseados al mensaje call:
sumar_a_otros_dos.call(1,2)
=> 6

Select
¿Qué pasa cuando queremos todos aquellos objetos que cumplan con una
condición determinada en una cierta colección? Por ejemplo, si de una lista de
números queremos los mayores a 3.

Lo que usamos es el mensaje select de las colecciones. select recibe un


bloque con un parámetro que representa un elemento de la colección y una
condición booleana como código, y lo que devuelve es una nueva colección
con los elementos que la cumplen.
algunos_numeros = [1, 2, 3, 4, 5]
mayores_a_3 = algunos_numeros.select { |un_numero| un_numero > 3 }

¿Y cuándo se aplica ese bloque que recibe el select? ¡El select es quien decide!
La colección va a aplicarlo con cada uno de los objetos ( un_numero) cuando
corresponda durante el seleccionado (o filtrado) de elementos.
mayores_a_3
=> [4, 5]
¿Y qué pasa con la colección original, como algunos_numeros o juegos? ¿Se
modifica al aplicar select?

¡No, para nada! El select no produce efecto.

Find
¿Y si en vez de todos los elementos que cumplan una condición, sólo
queremos uno? ¡Usamos find!
algunos_numeros = [1, 2, 3, 4, 5]
uno_mayor_a_3 = algunos_numeros.find { |un_numero| un_numero > 3 }

Mientras que select devuelve una colección, find devuelve únicamente un


elemento.
uno_mayor_a_3
=> 4

¿Y si ningún elemento de la colección cumple la condición? Devuelve nil, que,


como aprendiste antes, es un objeto que representa la nada - o en este caso,
que ninguno cumple la condición.

all?
Para saber si todos los elementos de una colección cumplen un cierto criterio
podemos usar el mensaje all?, que también recibe un bloque. Por ejemplo, si
tenemos una colección de alumnos, podemos saber si todos aprobaron de la
siguiente forma:
alumnos.all? { |un_alumno| un_alumno.aprobo? }

any?
De manera muy similar podemos saber si alguno de la colección cumple
cierta condición mediante el mensaje any?. Siguiendo el ejemplo anterior,
ahora queremos saber si por lo menos uno de nuestros alumnos aprobó :
alumnos.any? { |un_alumno| un_alumno.aprobo? }

¿Qué tienen de distinto all? y any? respecto aselect y find?

Mientras que select devuelve una colección y find un elemento o nil, all? y any?
siempre devuelven un valor booleano: true o false.

Map
El mensaje map nos permite, a partir de una colección, obtener otra colección
con cada uno de los resultados que retorna un envío de mensaje a cada
elemento.

En otras palabras, la nueva colección tendrá lo que devuelve el mensaje que


se le envíe a cada uno de los elementos. Por ejemplo, si usamos map para
saber los niveles de energía de una colección de golondrinas:
[Pepita, Norita].map { |una_golondrina| una_golondrina.energia }

=> [77, 52]

Al igual que el resto de los mensajes que vimos hasta ahora, map no modifica
la colección original ni sus elementos, sino que devuelve una nueva colección.

Antes de seguir, un caso particular. Dijimos que map no modifica la colección


original. Pero, ¿qué ocurriría si el mensaje dentro del bloque en el map sí tiene
efecto?

En ese caso se modificaría la colección original, pero sería un mal uso del map
. Lo que nos interesa al mapear es lo que devuelve el mensaje que enviamos,
no provocar un efecto sobre los objetos.

count
Volviendo a nuestra colección de alumnos. Ya preguntamos si todos
aprobaron o si alguno aprobó utilizando all?y any?. ¿Y si queremos saber
cuántos aprobaron? Usamos count:
alumnos.count { |un_alumno| un_alumno.aprobo? }

count nos dice cuántos elementos de una colección cumplen la condición.

sum
Por otro lado, para calcular sumatorias tenemos el mensaje sum. Si queremos
conocer la suma de todas las notas de los alumnos, por ejemplo, podemos
hacer:
alumnos.sum { |un_alumno| un_alumno.nota_en_examen }

each
Hasta ahora, todos los mensajes que vimos de colecciones (con la excepción
de push y delete) no están pensados para producir efectos sobre el sistema.
¿Qué ocurre, entonces, cuando queremos hacer algo con cada elemento? A
diferencia del map, no nos interesan los resultados de enviar el mismo
mensaje a cada objeto, sino mandarle un mensaje a cada uno con la intención
de producir un efecto.

Es en este caso que nos resulta de utilidad el mensaje each.


Por ejemplo, si queremos que de una colección de golondrinas, aquellas con
energía mayor a 100 vuelen a Iruya, podríamos combinar select y each para
hacer:
golondrinas

.select { |una_golondrina| una_golondrina.energia > 100 }

.each { |una_golondrina| una_golondrina.volar_hacia! Iruya }

Clases e Instancias
Si tenemos más de un objeto que se comporta exactamente de la misma
forma, lo que podemos hacer es generalizar ese comportamiento definiendo
una clase. Por ejemplo, si tenemos dos celulares con el mismo saldo y ambos
tienen las mismas funcionalidades, realizar_llamada! y cargar_saldo! :
module CelularDeMaría
@saldo = 25

def self.realizar_llamada!
@saldo -= 5
end

def self.cargar_saldo!(pesos)
@saldo += pesos
end
end

module CelularDeLucrecia
@saldo = 25

def self.realizar_llamada!
@saldo -= 5
end

def self.cargar_saldo!(pesos)
@saldo += pesos
end
end

Podemos generalizarlos en una clase Celular:


class Celular
def initialize
@saldo = 25
end
def realizar_llamada!
@saldo -= 5
end

def cargar_saldo!(pesos)
@saldo += pesos
end
end

Las clases sólo nos sirven para generalizar objetos que tengan el mismo
comportamiento: mismos métodos y mismos atributos.

Definir una clase es muy similar a definir un objeto. Tiene métodos, atributos...
¿cuál es su particularidad, entonces? La clase es un objeto que nos sirve
como molde para crear nuevos objetos.

Momento, ¿cómo es eso? ¿Una clase puede crear nuevos objetos?

¡Así es! Aprovechemos la clase Celular para instanciar los celulares de María y
Lucrecia:

celular_de_maría = Celular.new
celular_de_lucrecia = Celular.new

Celular, al igual que todas las clases, entiende el mensaje new, que crea una
nueva instancia de esa clase.

¿Por qué ahora escribimos bouba en lugar de Bouba? ¿O por qué


celular_de_maría en lugar de CelularDeMaría?

Hasta ahora estuvimos jugando con objetos bien conocidos, como Pepita o
Fito. Esos objetos, al igual que las clases, comienzan en mayúscula. Pero
bouba y celular_de_maría son variables: en particular, son referencias que
apuntan a instancias de Zombi y Celular.

Y como ya aprendiste anteriormente, las variables como saludo, despedida, o


kiki comienzan con minúscula.

initialize
Al trabajar con clases tenemos que inicializar los atributos en algún lugar.
¡Para eso es que existe ese método!

El mensaje initialize nos permite especificar cómo queremos que se inicialice


la instancia de una clase.

Si, por ejemplo, agregamos algunas plantas a un Vivero...


Vivero.agregar_planta! Planta.new
Vivero.agregar_planta! Planta.new
Vivero.agregar_planta! Planta.new

...y el Vivero las guarda en una colección @plantas, luego las podemos regar a
todas...
def regar_todas!
@plantas.each { |planta| planta.regar! }
end

...a pesar de que no tengamos una referencia explícita para cada planta.
¡Puede ocurrir que no necesitemos darle un nombre a cada una!

Es importante tener en cuenta que nuestros objetos también pueden crear


otros objetos, enviando el mensaje new a la clase que corresponda.

Por lo tanto, los casos en los que un objeto puede conocer a otro son:

● Cuando es un objeto bien conocido, como con los que veníamos


trabajando hasta ahora
● Cuando el objeto se pasa por parámetro en un mensaje ( Juliana.atacar
bouba, 4)
● Cuando un objeto crea otro mediante el envío del mensaje new

Afortunadamente el viejo y querido initialize puede recibir parámetros que


especifiquen con qué valores deseamos inicializar los atributos al construir
nuestros objetos.
class Planta
@altura

def initialize(centimetros)
@altura = centimetros
end

def regar!
@altura += 2
end
end

Ahora podemos crear plantas cuyas alturas varíen utilizando una única clase.
Internamente, los parámetros que recibe new se pasan también a initialize:

Los constructores pueden recibir más de un parámetro. Por ejemplo, si de una


Planta no sólo pudiéramos especificar su altura, sino también su especie y si
da o no frutos...
jazmin = Planta.new 70, "Jasminum fruticans", true

Herencia
Una forma de organizar las clases cuando programamos en objetos es definir
una jerarquía. En nuestro caso podemos pensar que Celular y Notebook se
pueden englobar en algo más grande y que las incluye, la idea de Dispositivo.

Muchas veces esa jerarquía se puede visualizar en el mundo real: por ejemplo,
Perro y Gato entran en la categoría Mascota, mientras que Cóndor y Halcón se
pueden clasificar como Ave. Cuando programemos, la jerarquía que utilicemos
dependerá de nuestro modelo y de las abstracciones que utilicemos.
class Ave
def volar!
@energia -= 20
end
end

class Condor < Ave


def dormir!(minutos)
@energia += minutos * 3
end
end

class Halcon < Ave


def dormir!(minutos)
@energia += minutos
end
end

El símbolo < significa "hereda de": por ejemplo, Condor hereda de Ave, que está
más arriba en la jerarquía. Otra manera de decirlo es que cada Condor es una
Ave.

La herencia nos permite que las subclases (Condor y Halcon) posean los
mismos métodos y atributos que la superclase Ave. Es decir, las instancias de
Condor y de Halcon van a saber volar! de la misma forma, pero cuando les
enviemos el mensaje dormir! cada una hará algo diferente.

Para recapitular, cuando dos objetos repiten lógica, creamos una clase con el
comportamiento en común. En el caso que dos clases repitan lógica
deberíamos crear una nueva clase a la cual llamamos superclase. A esta
nueva clase llevaremos los métodos repetidos y haremos que las clases
originales hereden de ella. Estas subclases que heredan de la superclase solo
contendrán su comportamiento particular.
Sabiendo que contamos con las clases Celular y Notebook, ¿alguna vez
instanciaremos un objeto de la clase Dispositivo? ¡Probablemente no! ¿Por qué
querríamos crear algo tan genérico si podemos crear algo más específico?

A este tipo de clases, como Dispositivo o Ave en el ejemplo del ejercicio


anterior, se las llama clases abstractas porque, a diferencia de las clases
concretas (como Celular o Notebook), nunca las instanciamos. En otras
palabras, no creamos objetos con esa clase, solo nos sirven para proveer
comportamiento a sus subclases.

En otros lenguajes del paradigma de objetos hay maneras de explicitar que


una clase es abstracta. Sin embargo, en Ruby no tenemos manera de
diferenciarlas a simple vista. Al no haber una diferencia sintáctica, es decir,
cómo definimos estas clases, es responsabilidad de quien programa no
instanciar aquellas clases que queremos que sean abstractas.

Si tenemos, por ejemplo, la clase colectivo donde entran (entran?) más


personas que en la superclase MediosDeTransporte que ya definimos, lo que
podemos hacer es redefinir el método en Colectivo: si Colectivo define el
método entran? va a evaluar ese código en lugar del de su superclase.

La herencia nos permite heredar el comportamiento de una superclase pero


redefinir aquellas cosas que nuestras subclases hacen distinto. Pero cuidado,
si tenemos que redefinir todo probablemente no necesitemos heredar en
primer lugar.

super
Al utilizar super en el método de una subclase, se evalúa el método con el
mismo nombre de su superclase. Por ejemplo...
class Saludo
def saludar
"Buen día"
end
end

class SaludoDocente < Saludo


def saludar
super + " estudiantes"
end
end
De esta forma, al enviar el mensaje saludar a SaludoDocente, super invoca el
método saludar de su superclase, Saludo.
mi_saludo = SaludoDocente.new
mi_saludo.saludar
=> "Buen día estudiantes"

Clases abstractas vs concretas


En algunos casos tenemos clases abstractas, que no vamos a instanciar,
como Dispositivo pero puede que sí se quieran instanciar las clases, como por ejemplo:
Zombi es una clase concreta

A diferencia de lo que pasaba con la clase abstracta Dispositivo y sus subclases


Celular y Notebook, Zombies una clase concreta ¡y una subclase llamada SuperZombi
puede heredar de ella sin problemas!

Excepciones
Pepita es un ave que vuela en circulos y gasta energía. Pero qué pasa si la
energía llega a 0 y se vuelve negativa? Pepita debería dejar de volar. Para que
Pepita no vuele con energía igual o menor a cero, debemos indicarselo. Pero
hay un problema importante: le estamos diciendo a pepita que vuele en
círculos si su energía es mayor o igual a 20, pero ahora no sabemos
realmente si lo hizo o no.

Esto es lo que se conoce como una falla silenciosa: no hizo lo que debía, ¡pero
no nos avisó que falló!

Que los objetos fallen silenciosamente es malo porque perdemos la confianza


en ellos : no estamos seguros de que el objeto haya cumplido con nuestra
orden.

Esto no parece tan terrible cuando del vuelo de las golondrinas se trata, pero
¿y si estamos haciendo una transferencia bancaria?

¿Qué sucedería si realizamos la transferencia y debitar! no debitara de la cuenta


origen cuando no tiene saldo?
En el ejemplo que acabamos de ver, si la cuenta origen no tiene suficiente
saldo, cuando hagamos transferencia.realizar!, de cuenta_origen no se habrá
debitado nada, pero en la de destino se habrá acreditado dinero. ¡Acabamos
de crear dinero!
Suena divertido, pero el banco estará furioso .

El problema acá surge porque la cuenta origen falló, pero lo hizo en silencio y
nadie se enteró. ¿La solución? ¡Gritar el error fuerte y claro!

raise
Mediante la sentencia raise mensaje se puede lanzar una excepción: provocar un
error explícito que interrumpe el flujo del programa.

Cuando lanzamos una excepción mediante raise mensaje estamos abortando la


evaluación del método: a partir de ese momento todas las sentencias que
faltaba evaluar serán ignoradas. ¡Ni siquiera llega a retornar nada!

Sin embargo, las excepciones hacen más que sólo impedir que el resto del
método se evalúe, sino que, cuando se lanzan, pueden abortar también la
evaluación de todos los métodos de la cadena de envío de mensajes.

Cuando trabajamos con excepciones el orden es importante: lanzar una


excepción interrumpe el flujo de ejecución a partir de ese momento, pero no
descarta cambios realizados anteriormente: lo que pasó, pasó.
Por eso, como regla práctica, siempre que querramos validar alguna situación,
lo haremos siempre antes de realizar las operaciones con efecto . Por
ejemplo:

● si vamos a cocinar, vamos a verificar que contemos con todos los


ingredientes antes de prender las sartenes
● si vamos a bailar, nos aseguraremos de que contemos con el calzado
adecuado antes de entrar en la pista de baile

Moraleja: si

1. ClaseA le envía un mensaje mensaje1 a ClaseB.


2. Dentro del método de mensaje1, ClaseB le envía un mensaje mensaje2 a
ClaseC.
3. mensaje2 lanza una excepción.
4. Se aborta el método m2 y propaga la excepción, es decir, la excepción
continúa a través de la cadena de envío de mensajes que nos llevó
hasta ahí.
5. Se aborta también el método m1.

Esto significa que las excepciones se propagan automáticamente a quien


hayan enviado el mensaje que la provocó. ¡No hay que hacer nada para que
eso suceda!
Mensajes de error
Es bastante evidente que cuando lanzás una excepción tenés que darle un
mensaje. Lo que no es evidente es que:

● Por un lado, el mensaje tiene que ser claro y representativo del error.
Por ejempo, el mensaje "ups" no nos dice mucho, mientras que el
mensaje "el violín no está afinado" nos da una idea mucho más precisa de
qué sucedió;
● y por otro lado, el mensaje está destinado al programador:
probablemente el usuario final que use nuestro sistema de cuentas
bancarias probablemente no vea nuestros mensajes de error, sino
pantallas mucho más bonitas . Por el contrario, quien verá estos
mensajes será el propio programador, cuando haya cometido algún
error.

Por ese motivo, siempre procurá lanzar excepciones con mensajes de error
descriptivos.
Anexo
Apéndice de mumuki

El lenguaje Ruby
Ruby es un lenguaje de Programación Orientada a Objetos gratis y de código
abierto creado en Japón. Su sintaxis amigable lo hace muy popular sobre
todo en el desarrollo web; de hecho una gran parte de la Plataforma Mumuki
está desarrollada en este lenguaje.

Conceptos de programación con objetos


Objeto y ambiente
A partir de la Lección 1: Objetos y mensajes
Los objetos son entes computacionales con los que interactuaremos para
resolver problemas. Estos objetos "viven"en un ambiente:

En este ambiente podemos ver a los objetos Pepita, 90 y 100.

Envío de mensajes
A partir de la Lección 1: Objetos y mensajes
La manera de interactuar con los objetos es a través del envío de mensajes
haciendo objeto.mensaje:
Pepita.volar!
Pepita.comer! 20
En este caso Pepita es el objeto al cual le enviamos:

● el mensaje volar! que no recibe argumentos;


● y el mensaje comer! con el argumento 20.

Definición de objetos
A partir de la Lección 2: Definiendo objetos: métodos y estado
La definición de objetos en Ruby comienza anteponiendo module antes del
nombre y finaliza con end.
module Pepita
end

module Norita
end

Definición de métodos
A partir de la Lección 2: Definiendo objetos: métodos y estado
Para que un objeto entienda un mensaje es necesario crear un método dentro
del mismo. La definición de los métodos comienzan con def y, al igual que en
la declaración de objetos, finaliza con end. En el caso de los métodos creados
dentro de un module es necesario anteponer al nombre self.. En caso que
nuestro método reciba parámetros debemos ponerlos entre paréntesis
separados por coma.
module Pepita
def self.cantar!
end

def self.volar!(distancia)
end

def self.comer!(cantidad, comida)


end
end

Interfaz
A partir de la Lección 1: Objetos y mensajes
Interfaz es el conjunto de mensajes que entiende un objeto. En el ejemplo
anterior, la interfaz de Pepita está compuesta por los mensajes cantar!, volar! y
comer!.
Asignación
A partir de la Lección 2: Definiendo objetos: métodos y estado
Para asignarle un valor a una variable utilizamos =.
numero_favorito = 8
color_favorito = "Violeta"

self
A partir de la Lección 2: Definiendo objetos: métodos y estado
Es la manera que tiene un objeto de enviarse mensajes a sí mismo; en estos
casos self es el objeto receptor del mensaje.
module Gaby
@esta_alegre = false

def self.escuchar_musica!
@esta_alegre = true
self.bailar!
end

def self.bailar!
# No es importante esta definición
end
end

Responsabilidad y delegación
A partir de la Lección 2: Definiendo objetos: métodos y estado
La responsabilidad, en la programación con objetos, está relacionada con qué
objeto debería resolver las determinadas partes de nuestro problema. Si un
objeto no es responsable de hacer algo lo debe delegar en el correspondiente.

Atributos
A partir de la Lección 2: Definiendo objetos: métodos y estado
Los atributos son objetos que nos permiten representar una característica de
otro objeto. Un objeto conoce a todos sus atributos por lo que puede enviarles
mensajes. Los atributos se escriben anteponiendo @ y si bien no es necesario
inicializarlos, hasta que no lo hagamos valdrán nil.
module Pepita
@energia = 100

def self.cantar!
end

def self.ciudad=(una_ciudad)
@ciudad = una_ciudad
end

def self.volar!(distancia)
@energia = @energia - distancia * 2
end

def self.comer!(cantidad, comida)


end
end

En este caso @energia es un atributo de Pepita que:

● @energia tiene un valor inicial de 100;


● cuando Pepita recibe el mensaje volar! disminuye su @energia el doble de
la distancia recorrida.
● @ciudad vale nil hasta que no le enviemos a Pepita el mensaje ciudad=
con una ciudad como argumento.

Estado
A partir de la Lección 2: Definiendo objetos: métodos y estado
El estado de un objeto es el conjunto de atributos que posee. Todos los
atributos son privados, para acceder o modificar los atributos de un objeto es
necesario definir métodos dentro del mismo.

Accessors
A partir de la Lección 3: Polimorfismo y encapsulamiento
Los accessors son métodos que nos permiten acceder o modificar el estado
de un objeto y son conocidos como getters y setters respectivamente.
module Pepita
@energia = 100

def self.energia
@energia
end

def self.energia=(nueva_energia)
@energia = nueva_energia
end
end
Encapsulamiento
A partir de la Lección 3: Polimorfismo y encapsulamiento
El encapsulamiento es la recomendable práctica de minimizar la exposición
del estado de nuestros objetos. Para ello definiremos solo aquellos accessors
que sean indispensables; tengamos en cuenta que no siempre vamos a
querer definir getters y/o setters para todos los atributos de cada objeto.
Veamos un ejemplo:
module AutoDeFabi
@patente = "AAA 111"
@nafta = 200
@color = "rojo"

def self.patente
@patente
end

def self.color=(un_color)
@color = un_color
end

def self.cargar!(cantidad)
@nafta += cantidad
end
end

module Fabi
def self.pintar_auto!(un_color)
AutoDeFabi.color = un_color
end

def self.cargar_nafta!(una_cantidad)
AutoDeFabi.cargar! una_cantidad
end
end

En este caso AutoDeFabi:

● tiene definido un getter para su atributo @patente. Sin embargo, no


define un setter ya que tiene sentido que pueda decir su patente pero
que no se pueda modificar externamente;
● tiene un setter para su atributo @color ya que el objeto Fabi puede
modificarlo directamente;
● no define ningún accessor para su atributo @nafta ya que en caso que
Fabi desee cargar nafta le enviará el mensaje cargar! a AutoDeFabi.
Convenciones para la nominación de métodos
A partir de la Lección 2: Definiendo objetos: métodos y estado
A la hora de ponerle un nombre a los métodos que definimos hay que tener en
cuenta ciertas convenciones de Ruby, es decir, algunos acuerdos entre la
comunidad de personas que programan en este lenguaje:

● Los nombres de métodos que producen un cambio de estado deben


finalizar con !;
● Los nombres de métodos que retornan un valor booleano deben
finalizar con ?;
● Los getters llevan el mismo nombre que el atributo que retornan pero
sin el @.
● Los setters llevan el mismo nombre que el atributo que modifican, pero
sin el @ y con = al final.
module Pepita
@energia = 100

def self.energia
@energia
end

def self.energia=(nueva_energia)
@energia = nueva_energia
end

def self.volar!(distancia)
@energia = @energia - distancia * 2
end

def self.cansada?
@energia < 10
end
end

Si bien nuestro código funcionará correctamente en caso de no respetar estas


convenciones, será menos comprensible para otras personas que lo lean.

Alternativa Condicional
A partir de la Lección 3: Polimorfismo y encapsulamiento
La alternativa condicional en Ruby comienza con if seguido por la condición y
termina con end:
if Pepita.aburrida?
Pepita.volar! 10
end
En caso de contar con un rama de else, end va al final del mismo:
if Norita.hambrienta?
Norita.comer! 10, "alpiste"
else
Norita.volar! 15
end

A diferencia de otros lenguajes, en Ruby podemos hacer elsif en caso de tener


un if dentro de un else:
if Cleo.cansada?
Cleo.descansar!
elsif Cleo.aburrida?
Cleo.leer!
else
Cleo.trabajar!
end

Polimorfismo
A partir de la Lección 3: Polimorfismo y encapsulamiento
El polimorfismo en objetos es la capacidad que tiene un objeto de poder
enviarle el mismo mensaje indistintamente a objetos diferentes. Estos objetos
deben entender este mensaje más allá de cómo este definido el método
asociado al mismo, es decir, dos o más objetos son polimórficos cuando
comparten una interfaz. Para que estemos ante un caso de polimorfismo es
necesaria la presencia de al menos tres objetos: uno que envíe el mensaje y
dos distintos que puedan entenderlo. Veámoslo en un ejemplo:

Supongamos que Agus puede realizar llamadas por celular enviandole un


mensaje llamar! con un parámetro minutos a su atributo @celular:
module Agus
def self.celular=(un_celular)
@celular = un_celular
end

def self.realizar_llamada!(minutos)
@celular.llamar! minutos
end
end

El celular que Agus utiliza puede ser tanto su CelularPersonal como su


CelularLaboral:

module CelularPersonal
@saldo = 200
def self.llamar!(minutos)
@saldo -= minutos
end
end

module CelularLaboral
@minutos_consumidos = 0

def self.llamar!(minutos)
@minutos_consumidos += minutos
end
end

Gracias a que CelularPersonal y CelularLaboral son polimórficos para el mensaje


llamar!, Agus puede realizar llamadas sin tener que verificar qué celular está
utilizando.

Referencias
A partir de la Lección 4: Referencias
Cuando le enviamos un mensaje a un objeto, en realidad no lo conocemos
directamente sino que lo hacemos a través de etiquetas llamadas referencias.
Algunos ejemplos de referencias y envío de mensajes a través de las mismas
son:

● las variables
dia = "domingo"
dia.upcase

● las referencias implícitas


"insomnio".upcase
^
+-- Acá hay una referencia implícita al objeto "insomnio"

● los objetos bien conocidos (los que declaramos con module)


module Pepita
def self.cantar!
end
end

Pepita.cantar!

● los atributos
module Pepita
@ciudad = GeneralLasHeras

def self.coordenadas
@ciudad.coordenadas
end
end

● los parámetros
module Guille
@paginas_leidas

def self.leer!(libro)
@paginas_leidas = @paginas_leidas + libro.cantidad_de_paginas
end
end

Colecciones
A partir de la Lección 5: Colecciones
Las colecciones son objetos que contienen referencias a otros objetos. Un
tipo de colección son las listas, las cuales se escriben entre corchetes ([]) y
permiten tener objetos repetidos con un orden determinado dentro de ellas:
libros = [Fundacion, Socorro, Elevacion, Kriptonita, Socorro]

Otro tipo de colecciones muy común son los sets, los cuales a diferencia de
las listas no pueden tener elementos repetidos y sus elementos no tienen un
orden determinado:
numeros_aleatorios = [1,27,8,7,8,27,87,1]
numeros_aleatorios
=> [1,27,8,7,8,27,87,1]
numeros_aleatorios.to_set
=> #<Set: {1, 27, 8, 7, 87}>

Bloques de código
A partir de la Lección 5: Colecciones
Los bloques son objetos que representan un mensaje o una secuencia de
envíos de mensajes, sin ejecutar, lista para ser evaluada cuando corresponda.
anio_actual = 2021
anio_nuevo = proc { anio_actual = anio_actual + 1 }

Estos bloques de código pueden recibir parámetros escritos entre ||


separados por comas.
saludador = proc { |saludo, nombre| saludo + " " + nombre + ", que lindo día para programar, ¿no?"
}

Dentro de cada bloque podemos usar y enviarle mensajes tanto a los


parámetros del bloque (saludo y nombre) como a las variables declaradas fuera
del mismo (anio_actual).

Por último, para ejecutar el código dentro del bloque debemos enviarle el
mensaje call con los argumentos correspondientes.
anio_nuevo.call
=> 2022

saludador.call("Hola", "Jor")
=> "Hola Jor, que lindo día para programar, ¿no?"

Clases e instancias
A partir de la Lección 6: Clases e Instancias
Las clases son objetos que sirven de moldes para crear nuevos objetos que
tienen el mismo comportamiento.

Por ejemplo, si tuvieramos dos perros representados con los objetos Firulais y
Stimpy:

class Firulais
@energia = 200

def self.jugar!(un_tiempo)
@energia -= un_tiempo
end

def recibir_duenio!
@energia += 100
end
end

class Stimpy
@energia = 300

def self.jugar!(un_tiempo)
@energia -= un_tiempo
end

def recibir_duenio!
@energia += 100
end
end
Podemos ver que tienen el mismo comportamiento. Para poder solucionar
esta repetición podríamos crear la clase Perro:
class Perro
def initialize(energia)
@energia = energia
end

def jugar!(un_tiempo)
@energia -= un_tiempo
end

def recibir_duenio!
@energia += 100
end
end

El método initialize de las clases permite especificar cómo se inicializan las


instancias de una clase. En este método declararemos los valores iniciales de
los atributos. Por último para crear nuestros objetos debemos hacer:
firulais = Perro.new 200
stimpy = Perro.new 300

Estos nuevos objetos creados a partir de una clase (firulais y stimpy) son
instancias de la misma. Es importante tener en cuenta que:

● Todo instancia pertenece a una y sólo una clase.


● No se puede cambiar la clase de una instancia en tiempo de ejecución.

Herencia
A partir de la Lección 7: Herencia
Cuando dos objetos repiten lógica, creamos una clase con el comportamiento
en común. En el caso que dos clases repitan lógica deberíamos crear una
nueva clase a la cual llamamos superclase. A esta nueva clase llevaremos los
métodos repetidos y haremos que las clases originales hereden de ella. Estas
subclases que heredan de la superclase solo contendrán su comportamiento
particular.

Por ejemplo si tuvieramos:


class Gato
def initialize(energia)
@energia = energia
end

def jugar!(un_tiempo)
@energia -= un_tiempo
end

def recibir_duenio!
@energia -= 10
end
end

class Perro
def initialize(energia)
@energia = energia
end

def jugar!(un_tiempo)
@energia -= un_tiempo
end

def recibir_duenio!
@energia += 100
end
end

Podríamos crear la clase Mascota:


class Mascota
def initialize(energia)
@energia = energia
end

def jugar!(un_tiempo)
@energia -= un_tiempo
end
end

Por último es necesario hacer que las clases Gato y Perro hereden de Mascota
utilizando <:
class Gato < Mascota
def recibir_duenio!
@energia -= 10
end
end

class Perro < Mascota


def recibir_duenio!
@energia += 100
end
end
En nuestra nueva jerarquía Mascota es una superclase de la cual heredan las
subclases Gato y Perro.

Redefinición
A partir de la Lección 7: Herencia
La redefinición de métodos de una superclase nos permite modificar en las
subclases el comportamiento definidio originalmente. Por ejemplo si en una
subclase Gallina de Mascota quisieramos redefinir el método jugar! lo haríamos
de esta forma:
class Gallina < Mascota
def jugar!(un_tiempo)
@energia -= 5
end

def recibir_duenio!
@energia *= 2
end
end

Clases abstractas
A partir de la Lección 7: Herencia
Las clases abstractas son clases que no se desea instanciar. Sirven para
abstraer la lógica repetida de otras clases pero no las usaremos como molde
de otros objetos. En contraposición, aquellas que sí instanciaremos son las
llamadas clases concretas. En el ejemplo anterior Mascota es una clase
abstracta mientras que Gato y Perro son clases concretas.
super
A partir de la Lección 7: Herencia
super nos permite redefinir un método pero sólo agregar una parte nueva a la
funcionalidad, reutilizando la lógica común que está definida en la superclase.
Al utilizar super en el método de una subclase, se evalúa el método con el
mismo nombre de su superclase.

Por ejemplo:
class Pichicho < Perro
def recibir_duenio!
super
self.ladrar!
end
end

Excepciones
A partir de la Lección 8: Excepciones
Una excepción es una indicación de que algo salió mal. Cuando lanzamos una
excepción provocamos un error explícito que interrumpe el flujo de nuestro
programa. La excepción no solo aborta el método en el cual la lanzamos sino
también la ejecución de todos los métodos de la cadena de envío de
mensajes pero no descarta los cambios realizados anteriormente; es por este
motivo que es importante saber en qué momento debemos hacerlo. Por
último, para lanzar excepciones utilizaremos raise con un mensaje expresivo
para entender por qué se interrumpió el flujo.
raise "No se puede realizar esta acción"
No se puede realizar esta acción (RuntimeError)

Operadores
Operadores matemáticos
A partir de la Lección 1: Objetos y mensajes
8+7
32 - 9
2*3
4/2

Operadores lógicos
A partir de la Lección 1: Objetos y mensajes
true && false
true || false
! false

Comparaciones
A partir de la Lección 1: Objetos y mensajes
Pepita == Norita
"ser" != "estar"
7 >= 5
7>5
7 <= 5
7<5

Metodos usuales
A lo largo del capítulo "Programación con Objetos" utilizamos algunos
métodos en la resolución de ejercicios. A continuación te los listamos en el
orden que aparecen.
numero.abs
A partir de la Lección 2: Definiendo objetos: métodos y estado
Permite obtener el valor absoluto de un número.
8.abs
=> 8

(-8).abs
=> 8

(3 - 7).abs
=> 4

numero.times bloque
A partir de la Lección 3: Polimorfismo y encapsulamiento
Ejecuta el código del bloque tantas veces como diga numero.
Pepita.energia = 5

3.times { Pepita.energia = Pepita.energia * 2 }

Pepita.energia
=> 40

string.upcase
A partir de la Lección 4: Referencias
Retorna un nuevo string con todos los caracteres de string en mayúsculas.
"libro".upcase
=> "LIBRO"

string.size
A partir de la Lección 4: Referencias
Retorna la cantidad de caracteres de string.
"camino".size
=> 6

numero.even?
Lección 4: Referencias
Nos permite saber si numero es par.
8.even?
=> true

7.even?
=> false

objeto.equal? otro_objeto
A partir de la Lección 4: Referencias
Nos permite saber si objeto y otro_objeto son referencias que apuntan a
exactamente el mismo objeto.
un_string = "lamparita"
otro_string = un_string

un_string.equal? "lamparita"
=> false

un_string.equal? otro_string
=> true

coleccion.push elemento
A partir de la Lección 5: Colecciones
Agrega elemento a coleccion.
numeros_de_la_suerte = [8, 7, 42]

numeros_de_la_suerte.push 9

numeros_de_la_suerte
=> [8, 7, 42, 9]

coleccion.delete elemento
A partir de la Lección 5: Colecciones
Remueve elemento de coleccion.
numeros_de_la_suerte = [8, 7, 42]

numeros_de_la_suerte.delete 7

numeros_de_la_suerte
=> [8, 42]

coleccion.include? elemento
A partir de la Lección 5: Colecciones
Nos permite saber si elemento pertenece a coleccion.
[25, 87, 776].include? 8
=> true

[25, 87, 776].include? 9


=> false

coleccion.size
A partir de la Lección 5: Colecciones
Retorna la cantidad de elementos dentro de coleccion.
["hola", "todo", "bien", "por", "acá"].size
=> 5

coleccion.select bloque_con_condicion
A partir de la Lección 5: Colecciones
Retorna una nueva colección con los elementos de coleccion que cumplan con
la condición de bloque_con_condicion. Este método no tiene efecto sobre
coleccion.
[1, 2, 3, 5, 7, 11, 13].select { |un_numero| un_numero > 5 }
=> [7, 11, 13]

coleccion.find bloque_con_condicion
A partir de la Lección 5: Colecciones
Retorna el primer el elemento de coleccion que cumpla con la condición de
bloque_con_condicion. Si ningún elemento cumple la condición nos devuelve nil.
[1, 2, 3, 5, 7, 11, 13].find { |un_numero| un_numero > 15 }
=> nil

coleccion.all? bloque_con_condicion
A partir de la Lección 5: Colecciones
Nos permite saber si todos los elementos de coleccion cumplen con la
condición de bloque_con_condicion.
[1, 2, 3, 5, 7, 11, 13].all? { |un_numero| un_numero > 5 }
=> false

[1, 2, 3, 5, 7, 11, 13].all? { |un_numero| un_numero < 20 }


=> true

coleccion.map bloque
A partir de la Lección 5: Colecciones
Retorna una nueva colección con el resultado de ejecutar el código de bloque
por cada elemento de coleccion.
[1, 2, 3, 4, 5].map { |un_numero| un_numero * 2 }
=> [2, 4, 6, 8, 10]

coleccion.count bloque_con_condicion
A partir de la Lección 5: Colecciones
Retorna cuántos elementos de coleccion cumplen con la condición de
bloque_con_condicion.
[1, 2, 3, 5, 7, 11, 13].count { |un_numero| un_numero > 3 }
=> 4
coleccion.sum bloque
A partir de la Lección 5: Colecciones
Retorna la suma de los valores obtenidos al ejecutar el código de bloque en
cada elemento de coleccion.
juegos_de_mesa = [Ajedrez, Damas, Ludo]

juegos_de_mesa.sum { |un_juego| un_juego.cantidad_de_piezas }


=> 60 # 32 del ajedrez + 24 de las damas + 4 del ludo

coleccion.each bloque
A partir de la Lección 5: Colecciones
Ejecuta el código de bloque por cada elemento de coleccion. El método each no
retorna una nueva colección sino que tiene efecto sobre la original.
golondrinas = [Pepita, Norita, Mercedes]

golondrinas.each { |una_golondrina| una_golondrina.comer_lombriz! } # Hace que cada


golondrina de la colección coma lombriz.

Clase.new
A partir de la Lección 6: Clases e Instancias
Crea y retorna una nueva instancia de Clase.
guitarra = Instrumento.new
piano = Instrumento.new

También podría gustarte

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy