Saltar a contenido

Programación Orientada a Objetos en Kotlin

Kotlin nos permite utilizar la metodología de programación orientada a objetos (POO). Esta metodología introduce los conceptos de objeto, clase, propiedad, método, constructor, herencia, encapsulamiento, etc.

¿Qué es un objeto?

Un objeto es una entidad independiente con sus propios datos y comportamientos.
Los datos se representan mediante propiedades y el comportamiento mediante métodos.

El encapsulamiento es la unión entre los datos y las funciones que los manipulan.

class Persona {
    var nombre: String = ""
    var edad: Int = 0

    fun inicializar(nombre: String, edad: Int) {
        this.nombre = nombre
        this.edad = edad
    }

    fun imprimir() {
        println("Nombre: $nombre — Edad: $edad")
    }

    fun esMayorEdad() {
        if (edad >= 18)
            println("$nombre es mayor de edad")
        else
            println("$nombre es menor de edad")
    }
}

fun main() {
    val persona1 = Persona()
    persona1.inicializar("Lucía", 21)
    persona1.imprimir()
    persona1.esMayorEdad()

    val persona2 = Persona()
    persona2.inicializar("Carlos", 16)
    persona2.imprimir()
    persona2.esMayorEdad()
}

El constructor

En Kotlin podemos definir un método que se ejecute inicialmente y en forma automática. Este método se lo llama constructor. El constructor tiene las siguientes características:

  • Es el primer método que se ejecuta.
  • Se ejecuta en forma automática.
  • No puede retornar datos.
  • Se ejecuta una única vez.
  • Un constructor tiene por objetivo inicializar atributos.
  • Una clase puede tener varios constructores pero solo uno es el principal.
class Persona constructor (val nombre: String, val edad: Int) {
    fun imprimir() {
        println("Nombre: $nombre — Edad: $edad")
    }
}
fun main() {
    val persona = Persona("Ginés", 35)
    persona.imprimir()
}

Opciones

La palabra constructor es opcional, con lo que si escribimos de manera concisa, se puede obviar. Veremos en conceptos futuros que es obligatorio la palabra constructor cuando agregamos un modificador de acceso (private, protected, public, internal) previo al constructor o una anotación.

Bloque init

Si en algunas situaciones queremos ejecutar un algoritmo inmediatamente después del constructor debemos implementar un bloque llamado init. En este bloque podemos por ejemplo validar los datos que llegan al constructor e inicializar otras propiedades de la clase.

class Estudiante(val nombre: String, var nota: Double) {
    init {
        if (nota < 0 || nota > 10) {
            println("Nota inválida, se ajusta a 0.")
            nota = 0.0
        }
    }

    fun mostrar() = println("$nombre tiene una nota de $nota")
}

Segundo constructor

En ocasiones, nos interesará el hecho de poder definir un segundo constructor. Cuando se define otro constructor aparte del principal de la clase debe implementarse obligatoriamente con la palabra clave constructor, sus parámetros y la llamada obligatoria al constructor principal de la clase. En un bloque de llaves desarrollamos el algoritmo del mismo.

class Alumno(val nombre: String, val edad: Int) {

    constructor(nombre: String) : this(nombre, 0) {
        println("Se ha creado un alumno sin edad definida.")
    }

    fun mostrar() = println("Alumno: $nombre — Edad: $edad")
}

Llamada de métodos dentro de la clase

class Operaciones {
    fun sumar(a: Int, b: Int): Int = a + b

    fun imprimirSuma(a: Int, b: Int) {
        val resultado = this.sumar(a, b)
        println("La suma de $a + $b = $resultado")
    }
}

Colaboración entre clases

class Motor {
    fun arrancar() = println("Motor en marcha")
}

class Auto(val marca: String) {
    private val motor = Motor()
    fun encender() {
        println("Encendiendo $marca...")
        motor.arrancar()
    }
}

Modificadores de acceso

Uno de los principios fundamentales de la programación orientada a objetos es el encapsulamiento, esto se logra agrupando una serie de métodos y propiedades dentro de una clase.

En Kotlin cuando implementamos una clase por defecto todas las propiedades y métodos son de tipo public. Un método o propiedad public se puede acceder desde donde definimos un objeto de dicha clase.

En el caso que necesitemos definir métodos y propiedades que solo se puedan acceder desde dentro de la clase las debemos definir con el modificador private.

El uso de estos modificadores de acceso puede ser la diferencia entre una fuga de datos o no. De la manera en la que gestionemos el acceso, será la manera en la que haremos seguro nuestro código y nuestras aplicaciones.

class CuentaBancaria(private var saldo: Double) {
    fun depositar(cantidad: Double) {
        if (cantidad > 0) saldo += cantidad
    }
    fun consultarSaldo() = println("Saldo disponible: $saldo €")
}

Propiedades con get/set personalizados

class Producto {
    var precio: Double = 0.0
        set(value) {
            field = if (value >= 0) value else 0.0
        }

    val conIVA: Double
        get() = precio * 1.21
}

data class

En muchas situaciones queremos almacenar un conjunto de datos sin necesidad de implementar funcionalidades, en estos casos el lenguaje Kotlin nos provee de una estructura llamada: data class.

data class Persona(val nombre: String, val edad: Int)

Desestructuración

val (nombre, edad) = Persona("Carlos", 30)
println("Nombre: $nombre, Edad: $edad")

enum class

En Kotlin tenemos otro tipo especial de clase que se las declara con las palabras claves enum class. Se utiliza para definir un conjunto de constantes.

Igual que las clases comunes las clases enum class pueden tener propiedades definidas en el constructor. Luego debemos indicar un valor para cada una de esas propiedades en las constantes definidas.

enum class Dia(val laboral: Boolean) {
    LUNES(true), MARTES(true), MIERCOLES(true),
    JUEVES(true), VIERNES(true), SABADO(false), DOMINGO(false)
}

Objetos nombrados y anónimos

Otra característica del lenguaje Kotlin es poder definir un objeto en forma inmediata sin tener que declarar una clase.

Objeto nombrado

object Configuracion {
    val version = "1.0.0"
    fun mostrar() = println("Versión actual: $version")
}

Objeto anónimo

val rectangulo = object {
    val base = 10
    val altura = 5
    fun area() = base * altura
}

Uso en Jetpack Compose

data class Alumno(val nombre: String, val edad: Int) {
    fun esMayorEdad() = edad >= 18
}

@Composable
fun FichaAlumno(alumno: Alumno) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(8.dp),
        shape = RoundedCornerShape(12.dp),
        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text("${alumno.nombre}", style = MaterialTheme.typography.titleMedium)
            Text("Edad: ${alumno.edad}", style = MaterialTheme.typography.bodyLarge)
            Text(
                if (alumno.esMayorEdad()) "Mayor de edad" else "Menor de edad"
            )
        }
    }
}

@Composable
fun ListaAlumnos() {
    val alumnos = listOf(
        Alumno("Lucía", 19),
        Alumno("Carlos", 17),
        Alumno("Ginés", 35)
    )

    LazyColumn(modifier = Modifier.fillMaxSize()) {
        items(alumnos) { FichaAlumno(it) }
    }
}

Actividades

  • AC 416 (RA2 / CE2a / IC1 / 1p). Implementa una clase Curso con las propiedades nombre:String y horas:Int, y un método descripcion() que devuelva un texto formateado. Crea dos objetos y muéstralos en una interfaz Compose usando una tarjeta (Card) para cada uno. La IU debe mostrar nombre, horas y la descripción generada por el método.

  • AP 417 (RA2 / CE2a / IC1 / 1p). Diseña una clase Rectangulo con un constructor primario que reciba base y altura. Implementa un método area() y otro esCuadrado(). En Compose, crea una pantalla con dos OutlinedTextField para capturar base y altura, genera un objeto Rectangulo y muestra dinámicamente:

    • el área
    • si es cuadrado o no

    Asegúrate de validar entradas y usar funciones puras para la lógica.

  • AR 418 (RA2 / CE2a / IC1 / 1p). Crea un data class llamado Alumno(val nombre: String, val nota: Double) y un método dentro de la clase que indique si está APTO/NO APTO (nota ≥ 5). Implementa una lista de varios alumnos y muéstralos en una interfaz Compose con LazyColumn.
    Cada tarjeta debe mostrar:

    • Nombre
    • Nota
    • Resultado del método (APTO / NO APTO)

    Debes usar desestructuración (val (n, _) = alumno) al menos una vez en el código.