- Personalizar comportamiento al asignar u obtener atributos de una clase
- Utilizar modificadores de accceso para restringir los atributos de la clase
- Haber cursado los temas anteriores en este módulo
Cuando hablamos de atributos en una clase, caeremos en cuenta de que algunas requieren algún mayor nivel de seguridad que otras. Por lo tanto, en kotlin como en la mayoría de los lenguajes de programación existen los modificadores de acceso, que determinan desde dónde podemos acceder a nuestras variables y métodos. Kotlin cuenta con los siguientes modificadores de acceso:
- private: Que es accesible solo dentro de la clase que lo contiene.
- protected: Sólo se tiene accceso dentro de la clase y por medio de las clases que heredan de este.
- internal: Accesible entre módulos (una serie de archivos compilados en conjunto).
- public: Como su nombre lo indica, da acceso a toda entidad que desee llamarlo.
Cabe señalar que los modificadores no se limitan a Programación orientada objetos, sino que aplican a archivos, funciones, variables, etc. Sin embargo, es un tema que va de la mano con POO.
Public es el modificador por defecto, eso hace que declararlo sea redundante. Hemos visto previamente el uso de valores públicos en el Reto 1, cuando consultamos desde main.kt el estado del coche.
println("El coche está prendido? ${miVehiculo.encendido}")
miVehiculo.encender()
println("El coche está prendido? ${miVehiculo.encendido}")
Podemos acceder sin ningún problema al atributo.
Ahora imaginemos un ejemplo de uso para private. En Super Mario Bros, Mario tiene diferentes efectos cuando colisiona con un Super Mushroom, con un goomba, con una Fire Flower .
Objeto a colisionar | Efecto |
---|---|
Super Mushroom | Super Mario |
Fire Flower | Fire Mario |
Goomba | Quitar vida |
Definiremos dos atributos:
- state: definirá el estado de mario (mario pequeño, super mario, mario fuego)
- lives: la cantidad de vidas
estos atributos deben ser privados, de lo contrario cualquiera podrá ponerse vidas infinitas y volverse fire Mario.
crearemos un método para restar una vida cuando mueres, y tampoco puede ser manipulado por otra entidad porque uno no controla la muerte directamente, sino los eventos que lo provocan, por eso debe ser también privado.
Nota: Si die() debiera ser pública, y queremos especificar su tipo, la IDE nos va a marcar que la declaración del modificador es redundante:
Por último, crearemos un colisionador para detectar que en cuanto toques un objeto, tenga el efecto de la tabla.
class Mario(var vidas: Int =3){ //vamos a dejar setear el número de vidas al iniciar el objeto Mario
init {
println("It's a me! Mario!") //vamos a hacer que Mario se presente al construirlo!
}
private var state = "small" //mario es pequeño al iniciar el juego
private var lives = 3 //uno empieza el juego con tres vidas
//resta una vida al jugador
private fun die(){
lives--
println("Haz perdido una vida!")
}
//el modificador public es redundante
//en función del objeto colisionante, se ejecuta un evento
public fun collision(collisionObj: String){
when(collisionObj){
"Goomba" -> die()
"Super Mushroom" -> state = "Super mario"
"Fire flower" -> state = "Fire mario"
else -> println("Objeto desconocido ¡no pasa nada!")
}
}
}
¡Muy bien! ahora vamos a instanciar nuestra clase en la función main y hacemos que mario colisione con un objeto no definido
import clases.Mario
fun main(){
var mario = Mario()
mario.collision("Pipe")
}
Objeto desconocido ¡no pasa nada!
Ahora, agregamos otra colisión con un Goomba:
mario.collision("Goomba")
Haz perdido una vida!
Los atributos de una clase pueden ser leídos y escritos. Como mencionamos, el modificador private impide que fuera de la clase se pueda interactuar con un método o atributo, pero si un agente externo requiere poder sumar algún valor a un número sin tener acceso al valor actual o a modificarlo directamente, necesitamos usar un Setter. En el ejemplo anterior aislamos algunas de sus propiedades, pero, si están aisladas sus propiedades cómo accedemos a ellas, o cómo es que podemos modificarlas. Es ahí donde entran los Getters y Setters. En kotlin, podremos declarar Getters y Setters por dos formas:
- Provistos por el programador
- desde kotlin
Provistos por el programador
En el ejemplo de Mario tenemos un ejemplo de un setter:
private fun die(){
lives--
println("Haz perdido una vida!")
}
Con eso deducimos que un setter no sólo da acceso a una variable, sino que restringe la forma en la que se puede modificar y cualquier otro paso adicional (en este caso, imprimir el mensaje "Haz perdido una vida").
si queremos saber el número de vidas necesitaremos crear un Getter:
fun getLives(): String{
return "$lives vidas"
}
En este caso, tampoco estamos devolviendo el número de vidas en sí, sino estamos regresando un String que especifica que son vidas la cantidad enviada. Este tipo de transformaciones pueden reducir código al ejecutar una tarea repetida en kotlin.
Utilizando el Getter después de tocar a un Goomba queda:
println("Te quedan ${mario.getLives()}")
Te quedan 2 vidas
Get y Set desde kotlin
Todas las variables declaradas llevan implícitas un método Get y Set, y al ser leídas o escritas, estas se ejecutan. Esto sería equivalente a declarar:
private var state = "small" //mario es pequeño al iniciar el juego
set(value){
field = value
}
get() = field
veremos una línea sobre el getter y setter que indican redundancia en el código, y es lógico porque son los valores por defecto de estos y no tienen por lo tanto qué ser declarados.
Utilizaremos la variable lives para crear un Setter desde Kotlin. Al parecer no existe restricción en que el número de vidas sea mínimo de una vida, y podríamos llevarlo a vidas negativas. Vamos a matar a mario 5 veces.
for(i in 1..5){ //matando a mario 5 veces
mario.collision("Goomba")
println("Te quedan ${mario.getLives()}")
}
ejecutamos ese código y tendremos este resultado:
Haz perdido una vida!
Te quedan 2 vidas
Haz perdido una vida!
Te quedan 1 vidas
Haz perdido una vida!
Te quedan 0 vidas
Haz perdido una vida!
Te quedan -1 vidas
Haz perdido una vida!
Te quedan -2 vidas
Este código lógicamente no está bien, requerimos ponerle un alto cuando mario tenga ya sólo una vida, porque al llegar a cero, se pierde automáticamente. Para eso utilizaremos el método set:
set(value){
if(field == 1){ //si teníamos una vida, se termina el juego
field = 0
gameOver()
} else if(field==0){ //si ya teníamos 0 vidas, no haz reiniciado el juego
println("Necesitas volver a jugar")
}
else{
field=value //podemos asignar el valor correctamente
}
}
La función die() ahora no es inutil, por lo tanto la eliminamos y al colisionar con un Goomba, pondremos directamente:
...
"Goomba" -> lives--
...
y reproducimos:
It's a me! Mario!
Te quedan 2 vidas
Te quedan 1 vidas
JUEGO TERMINADO
Te quedan 0 vidas
Necesitas volver a jugar
Te quedan 0 vidas
Necesitas volver a jugar
Te quedan 0 vidas