Objetos
User Objects
Además de los objetos predefinidos recientemente visto, cualquier lenguaje orientado a objetos debe permitir que el desarrollador puede crear sus propios objetos.
Esto puede hacerse de dos maneras ligeramente diferentes:
- Objetos autodefinidos
- Objetos anónimos
Objetos autodefinidos
Los objetos autodefinidos permiten hacer definiciones custom con estado y comportamiento particular. Otros nombres con los que pueden encontrarse en diferente bibliografía son Named Objects, “Well-Known Objects” (wko) o “singleton”.
Para definir un objeto utilizamos esta construcción:
object miObjeto { // aquí va el código del objeto miObjeto}
console.println(miObjeto)
A partir de aquí podemos acceder en cualquier parte del programa a la referencia “miObjeto”.
Objetos anonimos
Otra opción es utilizar objetos literales sin explícitamente darle un nombre. Esto permite usarlos en el momento sin utilizar una referencia o asignarlo a una variable de alcance más limitado que el global.
console.println( object { /* código aquí*/ })
El objeto creado sirve a modo de ejemplo didáctico, ya que es un objeto vacío. En la consola sólo se verá
anObject{}
Metodos
Dentro de las llaves de un objeto se puede definir su comportamiento mediante la implementación de métodos. Por ejemplo representaremos un pájaro:
object pepita{ method estasFeliz() { return true } method saluda(nombre) { return "hola " + nombre }
}
console.println(pepita.estasFeliz()) // trueconsole.println(pepita.saluda("pepona") // "hola pepona"
Los métodos se definen mediante la palabra method. Pueden recibir de 0 a n parámetros y opcionalmente devolver un valor. Los tipos de los parámetros no hay que definirlos. En caso que tenga, cada parámetro es una referencia que estará presente en el contexto del método a evaluar.
En el caso de que el método devuelva un valor es obligatorio escribir una sentencia return, a excepción de los simple return method explicados en la siguiente sección.
Simple Return Method
Hay un shortcut para definir métodos simples de una línea que solo devuelven valores.
object pepita { method estaFeliz() = true method saluda(nombre) = "hola " + nombre}
El lector podrá notar que no es necesario escribir return, ni tampoco las llaves que encierren el cuerpo del método. Las llaves se reemplazan por el símbolo =, que indica que la evaluación de la parte derecha del método será el valor devuelto.
Atributos
Hasta el momento pepita no hace cosas muy interesantes, retorna siempre lo mismo o solo dependiendo de los parámetros que recibe en los mensajes. Así que le agregaremos “estado” al objeto, mediante atributos, también llamados variables de instancia, en los cuales va a guardar valores, recordar información y en definitiva hacer referencia a otros objetos. Algunos métodos harán que el estado del objeto cambie, asignándole nuevos valores a los atributos. A su vez, el mismo estado del objeto influirá en la respuesta de otros métodos.
object pepita { var energia = 100 method volar(metros) { energia = energia - 2 + metros } method comer(gramos) { energia = energia + gramos } method estaFeliz() { return energia > 50 }}
pepita.volar(23)pepita.comer(10)pepita.estaFeliz()
El atributo energia inicialmente tiene un valor de 100, cada vez que se le envía el mensaje volar() o comer() a pepita el valor al que hace referencia energía va cambiando y cuando se le pregunta por estaFeliz(), la respuesta depende del valor de energía del momento.
Las referencias de la instancia se declaran en un objeto, justo antes del primer método. Como hemos visto, las referencias pueden ser var o const.
Todas las referencias de instancia son sólo visibles dentro del mismo objeto, se pueden acceder desde cualquiera de sus métodos.
Estas expresiones no son válidas.
pepita.energiapepita.energia = 200pepita.energia() //sería válido si se definiese un método llamado energia()
Mensajes
Uno de los conceptos más importantes de la programación orientada a objetos son los mensajes. En Wollok, (casi) todo lo que uno hace es enviar mensajes a objetos.
Al enviar el mensaje a un objeto se ejecuta la definición del método.
Para enviar un mensaje la sintaxis es:
objeto.mensaje(param1, param2, ...)
Estas variantes no son válidas:
mensaje(param1, param2) // falta el objeto receptorobjeto.mensaje // faltan paréntesis
Self
¿Qué pasa cuando estoy en un objeto y quiero enviarme un mensaje a mí mismo para aprovechar un método existente?
En ese caso utilizamos self que es una referencia que apunta al objeto donde estamos escribiendo el código.
object pepita { var energia = 100 method volar(metros) { energia = energia - 2 + metros } method comer(gramos) { energia = energia + gramos } method estaFeliz() { return energia > 50 }
// NUEVO METODO method volarYComer(metrosAVolar, gramosAComer) { self.volar(metrosAVolar) self.comer(gramosAComer) }}
pepita.volarYComer(23, 10)
Polimorfismo
El polimorfismo es la capacidad de un objeto de ser intercambiable con otro, sin que un tercero que los usa se vea afectado.
Wollok comparte algunas características con los lenguages dinámicamente tipados, ya que tiene un Pluggable Type System. Esto significa que si dos objetos entienden los mismos mensajes, entonces no necesitamos nada más para que un tercero los use en forma polimórfica.
Por ejemplo, cambiaremos la forma de comer de pepita. En vez de directamente decirle la cantidad de gramos que come, se le indica qué cosa comer, de manera que la cantidad de gramos va a depender de la comida que se le pase por parámetro.
object pepita { var energia = 100 method volar(metros) { energia = energia - 2 + metros } method comer(comida) { energia = energia + comida.energia() // una "comida" es algo que provee "energia" } method estaFeliz() { return energia > 50 }}
El método comer() puede recibir en el parámetro comida cualquier objeto que entienda el mensaje energia() y devuelva un número (la cantidad de energía que provee).
Por ejemplo, se puede tener diferentes objetos que representen cosas que pepita pueda comer:
object alpiste{ var peso = 2 method energia() { return peso * 5 }}
object arroz { method energia() { return 2 }}
pepita.comer(alpiste)pepita.comer(arroz)pepita.comer(object{method energia() = 1000}) // También puede ser un objeto anónimo
Aquí tanto alpiste como arroz son polimórficos respecto a pepita en el mensaje comer().
If
La expresión if permite evaluar una condición booleana y realizar diferentes acciones para el caso verdadero y falso respectivamente.
Por ejemplo:
if (self.estaLloviendo()) { self.irACasaEn(self.auto())}else self.irACasaEn(self.bicicleta())
En Wollok el “if” no es una sentencia (que controla el flujo) sino más bien una expresión. Esto implica que devuelve un valor, lo que permite cambiar el ejemplo a esta otra forma:
const transporte = if (self.estaLloviendo()) self.auto() else self.bicicleta()
self.irACasaEn(transporte)
Propiedades
Una facilidad que ofrece Wollok es definir los atributos como propiedades, lo que asume automáticamente la existencia de métodos de acceso, sin tener que explicitarlos en el código (tarea que suele ser repetitiva para escribir y molesta para leer). Se declaran con la palabra property antes del nombre de la referencia. En el caso de las variables incluye getters y setters, en el caso de las constantes, solo los getters
El siguiente ejemplo…
object pepita { var energia = 100 const rendimiento = 5
method energia() { return energia } method energia(nuevaEnergia){ energia = nuevaEnergia } method rendimiento() { return rendimiento }}
… sin perder funcionalidad, puede reescribirse así:
object pepita { var property energia = 100 const property rendimiento = 5}
En ambos casos es equivalente consultar:
pepita.energia()pepita.rendimiento()