Programación Con Objetos - Resumen Mumuki
Programación Con Objetos - Resumen Mumuki
Programación Con Objetos - Resumen Mumuki
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.
Pepita==Norita
Ya entendimos que en un ambiente hay objetos, y que cada uno de ellos tiene
identidad: sabe que es diferente de otro.
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:
nil es la forma que tenemos en Ruby de representar a "la nada" (que, casualmente, ¡también
es un objeto!).
Como convención, a los mensajes con efecto (es decir, que hacen algo) les
pondremos un signo de exclamación ! al final.
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.
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
Pepita.comer_alpiste! 6, Norita
# wrong number of arguments (2 for 1) (ArgumentError)
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?
● el objeto receptor es 2;
● el mensaje es +;
● el argumento es 3.
ej:
5.+ 6
= 11
3.< 27
=true
Pepita.== Norita
=false
Fitito.cargar_nafta!(120 * 4)
module Pepita
def self.cantar!
end
end
● 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
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?
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)
def energia
@energia
end
end
#...etc...
end
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
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?
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
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.
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.
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.
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.
● 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.
● 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"
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
¡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
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"
despedida = otro_saludo
=> false
=> 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
=> true
"hola" == "adiós"
=> false
"hola".equal? "hola"
=> false
¿Y qué hay de los objetos que veníamos definiendo hasta ahora? Por ejemplo
module Fito
@felicidad = 100
def self.comer!(calorias)
end
def self.felicidad
@felicidad
end
end
A objetos como Fito se los conocen como objetos bien conocidos: cuando los
¡Adiviná! Esas etiquetas también son referencias . Y son globales, es decir que
Además de los que ya vimos, hay más tipos de referencias: los atributos.
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
siguiente:
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
def volar_en_circulos!
@energia = @energia - 10
end
actual, @energia, a ese valor menos 10. Por ejemplo, pasa de 100 a 90.
referenciar al 90:
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:
● y una_ciudad es un parámetro;
Colecciones
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.
Otro tipo muy común de colecciones son los sets (conjuntos), los cuales
tienen algunas diferencias con las listas:
Hay una diferencia notable entre los primeros dos mensajes (push y delete) y
los otros dos (include? y size):
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 }
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 }
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.
¿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?
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 }
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? }
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.
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.
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? }
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.
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
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.
¡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.
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.
initialize
Al trabajar con clases tenemos que inicializar los atributos en algún lugar.
¡Para eso es que existe ese método!
...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!
Por lo tanto, los casos en los que un objeto puede conocer a otro son:
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:
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
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?
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
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ó!
Esto no parece tan terrible cuando del vuelo de las golondrinas se trata, pero
¿y si estamos haciendo una transferencia bancaria?
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.
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.
Moraleja: si
● 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.
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:
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
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
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
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
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
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:
def self.realizar_llamada!(minutos)
@celular.llamar! minutos
end
end
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
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
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 }
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
Estos nuevos objetos creados a partir de una clase (firulais y stimpy) son
instancias de la misma. Es importante tener en cuenta que:
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.
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
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
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
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
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
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]
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]
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