Clases Anidadas En Kotlin

En este tutorial te mostraremos la definición y declaración de clases anidadas en Kotlin a través de ejemplos, con el fin de ubicar clases dentro de otras.

Clases Anidadas

Una clase anidada es una clase cuya declaración se encuentra al interior de otra. Esto quiere decir que tanto su cabecera, como todo el bloque de código para el cuerpo, se escriben dentro de otra clase.

class ClaseExterna {
    fun metodo() = 4
    class ClaseAnidada {
        val propiedad = "Propiedad"
    }
}

En el ejemplo anterior la clase contenedora es ClaseExterna y la clase anidada es ClaseAnidada.

Acceso A Clases Anidadas

Las clases anidadas pasan a ser miembros de las clases externas, por lo que puedes acceder a la creación de instancias de la forma Externa.Anidada(), por ejemplo:


fun main() {
    val instanciaClaseAnidada = ClaseExterna.ClaseAnidada()
    println(instanciaClaseAnidada.propiedad)
}

Este mecanismo es de gran utilidad si deseas agrupar clases que cumplen responsabilidades en un solo lugar, lo que aumenta la legibilidad del código y permite aumentar las restricciones de visibilidad.

Restricción De Acceso A Miembros

Las clases anidadas no tienen acceso a los miembros de su clase externa. Si intentas accederlos el compilador te indicará que no reconoce la referencia, ya que no es visible para el alcance:

class ClaseExterna {
    fun metodo() = 4
    class ClaseAnidada {
        val propiedad = "Propiedad"

        fun metodoInterno(){
            metodo() // Unresolve reference: metodo
        }
    }
}

Más adelante verás que es posible acceder a la referencia si usas el modificador inner en la clase anidada.

Ejemplo De Clases Anidadas En Kotlin

Tomemos como referencia el modelado de una factura junto a las líneas que la componen.

class Invoice(
    val cliente: String,
    private var state: State = State.DRAFT,
    private val lines: List<Line>
) {

    fun calculateTotal(): Double {
        return lines.sumOf { it.calculateTotal() }
    }

    fun markAsPaid() {
        println("$cliente pagó la factura")
        state = State.PAID
    }

    class Line(
        private val unitCost: Double,
        private val quantity: Int
    ) {
        fun calculateTotal() = unitCost * quantity
    }

    enum class State {
        DRAFT, DELETED, OPEN, PAID, VOID
    }
}

Para representar al estado de la factura usamos una clase enumerada anidada llamada State con las instancias: Borrador, Eliminada, Abierta, Pagada y Anulada.

Las líneas de la factura son definidas por la clase anidada Line, la cual tiene propiedades del costo del ítem y la cantidad pedida. Esto permite que su método calculateTotal() genere el total de cada línea.

Con ello podemos simular como un cliente hipotético paga una factura que tenía abierta de la siguiente forma:

fun main() {
    val invoice = Invoice(
        "Carlos Rentería",
        Invoice.State.OPEN,
        listOf(
            Invoice.Line(30.5, 5),
            Invoice.Line(10.0, 2)
        )
    )

    println("${invoice.cliente} debe $${invoice.calculateTotal()}")
    // Cliente paga...
    invoice.markAsPaid()
}

Salida:

Carlos Rentería debe $172.5
Carlos Rentería pagó la factura

Clases Internas

Las clases internas son clases anidadas que se les permite acceder a la instancia de la clase externa a través del modificador inner. Lo que te permite hacer uso de los miembros, por ejemplo:

class Externa{
    val propiedad = "Propiedad"

    inner class Interna{
        fun metodo() = println(this@Externa.propiedad)
    }
}

Para hacer la referencia a la instancia, usa la expresión this junto al nombre de la clase externa como etiqueta (this@Externa). En el caso anterior es redundante, por lo que puedes omitirlo al acceder a propiedad.

inner class Interna{

    fun metodo() = println(propiedad)
}

Debido a que la clase interna requiere la instancia de la externa, es necesario instancear a la clase externa.


fun main() {
    Externa().Interna().metodo()
}

Ejemplo De Clases Internas En Kotlin

Imaginemos que diseñaremos la simulación del abordaje de varios soldados a un helicóptero en un videojuego, donde es necesario llevar la cuenta de las unidades.

class Helicopter(val name: String) {
    var charge = 0
        private set

    inner class Soldier(val id: String) {
        fun board(): String {
            charge++
            return "Soldado $id abordando al $name"
        }
    }
}

La clase Helicopter se identificará por su propiedad name y poseerá un contador charge con la carga de los personajes abordo.

La clase interna Soldier accederá al contador de Helicopter, para realizar el incremento respectivo y luego retornará un mensaje donde avisa que ya está en la aeronave.

Ahora llenemos un helicóptero con 10 soldados y comprobemos cada subida y la carga final.

fun main() {
    val halcon1 = Helicopter("Halcón 1")

    repeat(10) {
        val soldier = halcon1.Soldier("Soldado ${it + 1}")
        println(soldier.board())
    }

    println("Carga actual: ${halcon1.charge}")
}

Salida:

Soldado Soldado 1 abordando al Halcón 1
Soldado Soldado 2 abordando al Halcón 1
Soldado Soldado 3 abordando al Halcón 1
Soldado Soldado 4 abordando al Halcón 1
Soldado Soldado 5 abordando al Halcón 1
Soldado Soldado 6 abordando al Halcón 1
Soldado Soldado 7 abordando al Halcón 1
Soldado Soldado 8 abordando al Halcón 1
Soldado Soldado 9 abordando al Halcón 1
Soldado Soldado 10 abordando al Halcón 1
Carga actual: 10

Únete Al Discord De Develou

Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!