Instancias de objeto y clases en Kotlin

1. Antes de comenzar

En los codelabs de esta ruta de aprendizaje, compilarás una app de Dice Roller para Android. Cuando el usuario "lance el dado", se generará un resultado aleatorio. Ese resultado tendrá en cuenta la cantidad de caras que tiene el dado. Por ejemplo, para un dado de 6 caras, solo saldrán valores del 1 al 6.

Así se verá la app final:

8299aaca25c93863.png

Para que puedas concentrarte en los nuevos conceptos de programación relacionados con esta app, usarás la herramienta de programación de Kotlin basada en el navegador para crear la funcionalidad principal de la app. El programa enviará los resultados a la consola. Más adelante, implementarás la interfaz de usuario en Android Studio.

En este primer codelab, crearás un programa en Kotlin que simule un lanzamiento de dados y muestre un número al azar, como ocurriría con un dado real.

Requisitos previos

  • Conocimientos para abrir, editar y ejecutar código en https://developer.android.com/training/kotlinplayground
  • Habilidad para crear y ejecutar un programa en Kotlin que use variables y funciones, y que imprima un resultado en la consola
  • Capacidad de dar formato a números dentro del texto mediante una plantilla de cadenas con la notación ${variable}

Qué aprenderás

  • Cómo generar números al azar de manera programática para simular los lanzamientos de dados
  • Cómo estructurar tu código mediante la creación de una clase Dice con una variable y un método
  • Cómo crear una instancia de objeto de una clase, modificar sus variables y llamar a sus métodos

Qué compilarás

  • Compilarás un programa en la herramienta de programación de Kotlin basada en el navegador que pueda realizar un lanzamiento de dados aleatorio.

Requisitos

  • Una computadora con conexión a Internet

2. Lanza números al azar

A menudo, los juegos cuentan con un elemento azaroso. Puedes ganar un premio aleatorio o avanzar una cantidad de posiciones al azar en el tablero de juego. En tu vida cotidiana, puedes usar números y letras al azar para generar contraseñas más seguras.

En lugar de usar dados reales, puedes escribir un programa que simule un lanzamiento de dados para ti. Cada vez que lances el dado, el resultado puede ser cualquier número dentro del rango de valores posibles. Afortunadamente, no tienes que compilar tu propio generador de números al azar para tener un programa de este tipo. La mayoría de los lenguajes de programación, incluido Kotlin, tienen una manera integrada de modo que puedas generar números al azar. En esta tarea, usarás el código de Kotlin para lograrlo.

Configura tu código de partida

  1. En tu navegador, abre el sitio web https://developer.android.com/training/kotlinplayground.
  2. En el editor de código, borra todo el código existente y reemplázalo con el que se muestra a continuación. Esta es la función main() con la que trabajaste en codelabs anteriores.
fun main() {

}

Usa la función de aleatorización

A los efectos de lanzar un dado, necesitas una forma de representar todos los resultados válidos posibles. Para un dado común de 6 caras, los valores aceptables son 1, 2, 3, 4, 5 y 6.

Con anterioridad, aprendiste que existen tipos de datos, como Int para números enteros y String para texto. IntRange es otro tipo de datos y representa un rango de números enteros desde un valor de partida hasta el último. IntRange es un tipo de datos adecuado si quieres representar los valores posibles que puede arrojar un lanzamiento de dados.

  1. Dentro de la función main(), define una variable como un val llamado diceRange. Asígnala a un IntRange del 1 al 6, que represente el rango de números enteros que puede obtenerse cuando lanzas un dado de 6 caras.
val diceRange = 1..6

Podrás ver que 1..6 es un rango en Kotlin porque tiene un número de inicio, dos puntos y un número final (sin espacios entre ellos). Otros ejemplos de rangos de números enteros son 2..5 para los números 2 a 5 y 100..200 para los números 100 a 200.

De manera similar a como la llamada a println() le indica al sistema que imprima el texto proporcionado, puedes usar una función llamada random() para generar y mostrar un número al azar perteneciente a un rango determinado. Como antes, puedes almacenar el resultado en una variable.

  1. Dentro de main(), define una variable como un val llamado randomNumber.
  2. Haz que randomNumber tenga el valor del resultado de la llamada a random() en el rango diceRange, como se muestra a continuación.
 val randomNumber = diceRange.random()

Observa que llamas a random() en diceRange mediante un punto entre la variable y la llamada a función. Puedes leer esto como "generando un número al azar del diceRange". El resultado se almacenará en la variable randomNumber.

  1. Si quieres ver el número generado de manera aleatoria, usa la notación de formato de cadenas (también llamada "plantilla de cadenas") ${randomNumber} para imprimirlo, como se muestra a continuación.
println("Random number: ${randomNumber}")

El código finalizado debería verse de la siguiente manera:

fun main() {
    val diceRange = 1..6
    val randomNumber = diceRange.random()
    println("Random number: ${randomNumber}")
}
  1. Ejecuta el código varias veces. Deberías obtener un resultado como el siguiente, con diferentes números al azar cada vez.
Random number: 4

3. Crear una clase Dice

Cuando lanzas dados, estos son objetos reales en tus manos. Si bien el código que acabas de escribir funciona a la perfección, es difícil imaginar que se trata de un dado de verdad. Cuando organizas un programa de modo que se asemeje más a las cosas que representa, resulta más fácil entenderlo. Por eso, sería genial tener dados programáticos que puedas lanzar.

En esencia, todos los dados funcionan de la misma manera. Tienen las mismas propiedades, como tener caras, y tienen el mismo comportamiento, como el hecho de que se pueden lanzar. En Kotlin, puedes crear un plano programático de un dado que establezca que este tiene caras y que, cuando lo lances, generará un número al azar. Este plano se denomina clase.

A partir de esa clase, puedes crear objetos de dados llamados instancias de objeto. Por ejemplo, puedes crear un dado de 12 caras o uno de 4.

Cómo definir una clase Dice

En los siguientes pasos, definirás una nueva clase llamada Dice para representar un dado que se puede lanzar.

  1. Para comenzar de cero, borra el código de la función main() de modo que tengas el código tal como se muestra a continuación.
fun main() {

}
  1. Debajo de esta función main(), agrega una línea en blanco y, luego, agrega el código para crear la clase Dice. Como se muestra más abajo, comienza con la palabra clave class, seguida del nombre de la clase y una llave de apertura y cierre. Si quieres incluir el código correspondiente a la clase, deja espacio entre las llaves.
class Dice {

}

Dentro de una definición de clase, puedes usar variables para especificar una propiedad de la clase o más. Los dados reales tienen una cantidad de caras, un color o un peso. En esta tarea, te concentrarás en la propiedad asociada con la cantidad de caras.

  1. Dentro de la clase Dice, agrega una var llamada sides para la cantidad de caras que tendrá tu dado. Establece sides en 6.
class Dice {
    var sides = 6
}

Se ve así. Ahora tienes una clase muy simple que representa el dado.

Crea una instancia de la clase Dice

Con esta clase Dice, tienes un plano del dado. Para tener un dado real en tu programa, deberás crear una instancia de objeto Dice. (Y, si necesitaras tener tres dados, crearías tres instancias de objeto).

ba2038022410942c.jpeg

  1. Para crear una instancia de objeto de Dice, en la función main(), crea un val llamado myFirstDice e inicialízalo como una instancia de la clase Dice. Observa los paréntesis después del nombre de la clase, lo que indica que estás creando una instancia de objeto nueva desde la clase.
fun main() {
    val myFirstDice = Dice()
}

Ahora que tienes un objeto myFirstDice, algo creado a partir del plano, puedes acceder a sus propiedades. La única propiedad de Dice es su sides. Puedes acceder a una propiedad mediante la "notación de puntos". Por lo tanto, para acceder a la propiedad sides de myFirstDice, llama a myFirstDice.sides, que se pronuncia "myFirstDice punto sides".

  1. Debajo de la declaración de myFirstDice, agrega una sentencia println() para obtener el número de sides de myFirstDice.
println(myFirstDice.sides)

Tu código debería verse de la siguiente manera:

fun main() {
    val myFirstDice = Dice()
    println(myFirstDice.sides)
}

class Dice {
    var sides = 6
}
  1. Ejecuta el programa. Debería mostrar el número de sides definido en la clase Dice.
6

Ahora tienes una clase Dice y un dado real myFirstDice con 6 sides.

¡Lancemos el dado!

Lanza el dado

Anteriormente, usaste una función para realizar la acción de imprimir capas de pastel. Lanzar un dado también es una acción que se puede implementar como una función. Y, como todos los dados se pueden lanzar, puedes agregar una función para ello dentro de la clase Dice. Una función que se define dentro de una clase también se denomina método.

  1. En la clase Dice, debajo de la variable sides, inserta una línea en blanco y, luego, crea una nueva función para lanzar el dado. Comienza con la palabra clave fun en Kotlin, seguida del nombre del método, de los paréntesis () y, por último, de las llaves de apertura y cierre {}. Puedes dejar una línea en blanco entre llaves para escribir más código, como se muestra más abajo. Tu clase debería verse así:
class Dice {
    var sides = 6

    fun roll() {

    }
}

Cuando lanzas un dado de seis caras, genera un número al azar entre 1 y 6.

  1. Dentro del método roll(), crea un val randomNumber. Asígnale un número al azar que pertenezca al rango 1..6. Usa la notación de puntos para llamar a random() en el rango.
val randomNumber = (1..6).random()
  1. Después de generar el número al azar, imprímelo en la consola. El método roll() terminado debería tener el siguiente aspecto:
fun roll() {
     val randomNumber = (1..6).random()
     println(randomNumber)
}
  1. Para implementar myFirstDice, en main(), llama al método roll() en myFirstDice. La forma de llamar a un método es mediante la "notación de puntos". Por lo tanto, para llamar al método roll() de myFirstDice, escribe myFirstDice.roll(), que se pronuncia "myFirstDice punto roll()".
myFirstDice.roll()

El código terminado debería verse de la siguiente manera:

fun main() {
    val myFirstDice = Dice()
    println(myFirstDice.sides)
    myFirstDice.roll()
}

class Dice {
    var sides = 6

    fun roll() {
        val randomNumber = (1..6).random()
        println(randomNumber)
    }
}
  1. Ejecuta tu código. Deberías ver el resultado de un lanzamiento aleatorio del dado debajo del número de caras. Ejecuta el código varias veces y observa que la cantidad de caras siga siendo la misma y que el valor del dado cambie.
6
4

¡Felicitaciones! Definiste una clase Dice con una variable sides y una función roll(). En la función main(), creaste una nueva instancia de objeto Dice y, luego, llamaste al método roll() en ella para producir un número al azar.

4. Muestra el resultado del lanzamiento

Por el momento, imprimes el valor de randomNumber en tu función roll() y eso funciona muy bien. Pero, a veces, resulta más útil devolver el resultado de una función a lo que sea que la haya llamado. Por ejemplo, podrías asignar el resultado del método roll() a una variable y, luego, mover un jugador esa cantidad de posiciones. Veamos cómo hacerlo.

  1. En main(), modifica la línea que dice myFirstDice.roll(). Crea un val llamado diceRoll. Establécelo en el valor que muestra el método roll().
val diceRoll = myFirstDice.roll()

De momento no hará nada, porque roll() aún no muestra nada. Para que este código funcione según lo previsto, roll() debe mostrar algo.

En los codelabs anteriores, aprendiste que debes especificar un tipo de datos para los argumentos de entrada de las funciones. De la misma manera, debes especificar un tipo de datos para los datos que muestra una función.

  1. Cambia la función roll() para que especifique el tipo de datos que se mostrará. En este caso, el número al azar es un Int, por lo que el tipo de datos que se muestra es Int. La sintaxis a los efectos de especificar dicho tipo de datos es: después del nombre de la función, después de los paréntesis, agrega dos puntos, espacio y la palabra clave Int correspondiente al tipo de datos de la función. La definición de la función debería ser similar al siguiente código:
fun roll(): Int {
  1. Ejecuta este código. Verás un error en Problems View. Dice lo siguiente:
A ‘return' expression required in a function with a block body (‘{...}')

Cambiaste la definición de la función para que muestre un objeto Int, pero el sistema indica que el código no muestra un Int. "Block body" (cuerpo del bloque) o "function body" (cuerpo de la función) hacen referencia al código que se encuentra entre las llaves de una función. Para corregir este error, puedes mostrar un valor de una función con una sentencia return al final del cuerpo de la función.

  1. En roll(), quita la sentencia println() y reemplázala por una sentencia return para randomNumber. Tu función roll() debería ser similar al siguiente código:
fun roll(): Int {
     val randomNumber = (1..6).random()
     return randomNumber
}
  1. En main(), quita la sentencia de impresión de las caras del dado.
  2. Agrega una sentencia para imprimir el valor de sides y diceRoll en una oración informativa. La función main() terminada debería tener el aspecto del código que se indica a continuación:
fun main() {
    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}
  1. Ejecuta el código. El resultado debería ser similar al siguiente:
Your 6 sided dice rolled 4!

Este es el código que escribiste hasta ahora.

fun main() {
    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}

class Dice {
    var sides = 6

    fun roll(): Int {
        val randomNumber = (1..6).random()
        return randomNumber
    }
}

5. Cambia la cantidad de caras de tu dado

No todos los dados tienen 6 caras. Los dados pueden ser de diversas formas y tamaños: 4 caras, 8 caras y hasta 120 caras.

  1. En tu clase Dice, dentro del método roll(), cambia el 1..6 hard-coded y, en su lugar, usa sides, de modo que el rango y, por lo tanto, el número obtenido al azar, siempre sean los adecuados para la cantidad de caras.
val randomNumber = (1..sides).random()
  1. En la función main() que aparece a continuación, después de imprimir el resultado del lanzamiento, cambia sides de myFirstDice y establécelo en 20.
myFirstDice.sides = 20
  1. Copia y pega la sentencia de impresión existente que aparece a continuación debajo del lugar donde cambiaste la cantidad de caras.
  2. Reemplaza la impresión de diceRoll por la correspondiente al resultado de llamar al método roll() en myFirstDice.
println("Your ${myFirstDice.sides} sided dice rolled ${myFirstDice.roll()}!")

El programa debería verse así:

fun main() {

    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")

    myFirstDice.sides = 20
    println("Your ${myFirstDice.sides} sided dice rolled ${myFirstDice.roll()}!")
}

class Dice {
    var sides = 6

    fun roll(): Int {
        val randomNumber = (1..sides).random()
        return randomNumber
    }
}
  1. Ejecuta el programa. Deberías ver un mensaje para el dado de 6 caras y un segundo mensaje para el dado de 20 caras.
Your 6 sided dice rolled 3!
Your 20 sided dice rolled 15!

6. Personaliza tu dado

La idea de una clase es representar algo, a menudo algo físico en el mundo real. En este caso, una clase Dice representa un dado físico. En el mundo real, los dados no pueden cambiar su cantidad de caras. Si quieres una cantidad diferente de caras, deberás conseguir un dado diferente. De manera programática, esto significa que, en lugar de cambiar la propiedad de caras de una instancia de objeto Dice existente, deberás crear una instancia de objeto para el dado nuevo con la cantidad de caras que necesites.

En esta tarea, modificarás la clase Dice de modo que puedas especificar el número de caras cuando crees una instancia nueva. Cambia la definición de la clase Dice para proporcionar el número de caras. Esto es similar a la forma en que una función puede aceptar argumentos de entrada.

  1. Modifica la definición de la clase Dice para que acepte un número entero llamado numSides. El código de la clase no cambiará.
class Dice(val numSides: Int) {
   // Code inside does not change.
}
  1. Dentro de la clase Dice, borra la variable sides, ya que ahora puedes usar numSides.
  2. Además, corrige el rango para usar numSides.

Tu clase Dice debería verse así:

class Dice (val numSides: Int) {

    fun roll(): Int {
        val randomNumber = (1..numSides).random()
        return randomNumber
    }
}

Si ejecutas este código, verás muchos errores, porque deberás actualizar main() de modo que funcione con los cambios en la clase Dice.

  1. En main(), para crear myFirstDice con 6 caras, deberás proporcionar la cantidad de caras como un argumento de la clase Dice, como se muestra a continuación.
    val myFirstDice = Dice(6)
  1. En la sentencia de impresión, cambia sides a numSides.
  2. Debajo de eso, borra el código que cambia sides a 20, porque esa variable ya no existe.
  3. Borra también la sentencia println que se encuentra debajo.

La función main() debería ser similar al siguiente código, y si la ejecutas, no debería arrojar errores.

fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
}
  1. Después de imprimir el primer resultado del lanzamiento de dados, agrega código para crear e imprimir un segundo objeto Dice llamado mySecondDice con 20 caras.
val mySecondDice = Dice(20)
  1. Agrega una sentencia de impresión que imprima el valor que resulte del lanzamiento.
println("Your ${mySecondDice.numSides} sided dice rolled  ${mySecondDice.roll()}!")
  1. La función main() terminada debería verse de la siguiente manera:
fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")

    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        val randomNumber = (1..numSides).random()
        return randomNumber
    }
}
  1. Ejecuta tu programa terminado. El resultado debería verse así:
Your 6 sided dice rolled 5!
Your 20 sided dice rolled 7!

7. Implementa las prácticas recomendadas de codificación

Cuando escribes código, es aconsejable la brevedad. Puedes deshacerte de la variable randomNumber y mostrar el número al azar directamente.

  1. Cambia la sentencia return de modo que muestre el número al azar de forma directa.
fun roll(): Int {
    return (1..numSides).random()
}

En la segunda sentencia de impresión, coloca la llamada para obtener el número al azar en la plantilla de cadenas. Puedes deshacerte de la variable diceRoll haciendo lo mismo en la primera sentencia de impresión.

  1. Llama a myFirstDice.roll() en la plantilla de cadenas y borra la variable diceRoll. Las primeras dos líneas de tu código main() ahora deberían tener este aspecto:
val myFirstDice = Dice(6)
println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")
  1. Ejecuta tu código. No debería haber diferencias en el resultado.

Este es el código final luego de refactorizarlo.

fun main() {
    val myFirstDice = Dice(6)
    println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")

    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        return (1..numSides).random()
    }
}

8. Código de solución

fun main() {
    val myFirstDice = Dice(6)
    println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")

    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        return (1..numSides).random()
    }
}

9. Resumen

  • Llama a la función random() en un IntRange de modo que generes un número al azar: (1..6).random().
  • Las clases son como el plano de un objeto. Pueden tener propiedades y comportamientos, implementados como variables y funciones.
  • Una instancia de una clase representa un objeto, a menudo un objeto físico, como un dado. Puedes llamar a las acciones en el objeto y cambiar sus atributos.
  • Puedes suministrar valores a una clase cuando creas una instancia. Por ejemplo: class Dice(val numSides: Int) y, luego, crear una instancia con Dice(6).
  • Las funciones pueden mostrar un resultado. Especifica en la definición de la función el tipo de datos que se mostrará y usa una sentencia return en el cuerpo de la función para que muestre un resultado. Por ejemplo: fun example(): Int { return 5 }.

10. Más información

11. Practica por tu cuenta

Haz lo siguiente:

  • Dale a tu clase Dice otro atributo de color y crea varias instancias de dados de diferentes colores y cantidades de caras.
  • Crea una clase Coin, dale la capacidad de girar, crea una instancia de la clase y haz girar algunas monedas. ¿Cómo usarías la función random() mediante un rango a los efectos de lograr el giro de una moneda?