certmundo.
es‑mx

6 min de lectura

¿Cómo funcionan las clases y la programación orientada a objetos en Kotlin?

Las clases en Kotlin son plantillas que definen las propiedades y comportamientos de los objetos que usarás en tu app Android.

Con la programación orientada a objetos (POO) puedes modelar entidades del mundo real como productos, usuarios o pedidos. Kotlin hace esto de forma más concisa que Java.

Qué es una clase y cómo se declara

Una clase es un molde. A partir de ese molde creas objetos (también llamados instancias).

Esta es la estructura básica:

class NombreClase(
    val propiedad1: Tipo,
    var propiedad2: Tipo
) {
    fun nombreFuncion(): TipoRetorno {
        // lógica
    }
}

Las propiedades declaradas en el paréntesis principal se llaman constructor primario. Usa val para datos que no cambian y var para los que sí.

Ejemplo 1 — Producto de Liverpool

class Producto(
    val id: Int,
    val nombre: String,
    var precio: Double,
    var disponible: Boolean = true
) {
    fun mostrarInfo(): String {
        return "$nombre — $$precio"
    }
}

val tenis = Producto(id = 1, nombre = "Tenis Nike", precio = 1899.0)
println(tenis.mostrarInfo())  // Tenis Nike — $1,899
println(tenis.disponible)     // true

precio es var porque puede cambiar en una oferta. id y nombre son val porque no deben modificarse.

Data classes: el estándar para modelos de datos

Una data class es una clase especial optimizada para guardar datos. Kotlin genera automáticamente equals(), hashCode(), toString() y copy().

data class NombreModelo(
    val campo1: Tipo,
    val campo2: Tipo
)

Esta es la estructura que más usarás cuando consumas APIs o manejes listas en Android.

Ejemplo 2 — Repartidor de FEMSA

data class Repartidor(
    val id: Int,
    val nombre: String,
    val zona: String,
    val salarioMensual: Double
)

val rep1 = Repartidor(101, "Carlos Vega", "Monterrey Norte", 18500.0)
val rep2 = rep1.copy(zona = "Monterrey Sur")

println(rep1)
// Repartidor(id=101, nombre=Carlos Vega, zona=Monterrey Norte, salarioMensual=18500.0)
println(rep2.zona)  // Monterrey Sur

copy() crea un nuevo objeto con los mismos valores, excepto los que indiques. Es muy útil para actualizar estados en apps Android sin mutar el objeto original.

Ejemplo 3 — Comparación entre data class y clase normal

// Clase normal
class UsuarioNormal(val nombre: String)
val a = UsuarioNormal("Ana")
val b = UsuarioNormal("Ana")
println(a == b)  // false — compara referencias

// Data class
data class UsuarioData(val nombre: String)
val c = UsuarioData("Ana")
val d = UsuarioData("Ana")
println(c == d)  // true — compara contenido

En apps Android esto importa mucho. Por ejemplo, cuando RecyclerView necesita detectar cambios en una lista.

Herencia: reutilizar comportamiento entre clases

La herencia permite que una clase hija tome las propiedades y funciones de una clase padre. En Kotlin, las clases son final por defecto. Debes marcar la clase padre con open para permitir herencia.

open class ClasePadre(val prop: Tipo) {
    open fun funcion(): String = "padre"
}

class ClaseHija(prop: Tipo, val extra: Tipo) : ClasePadre(prop) {
    override fun funcion(): String = "hija"
}

Ejemplo 4 — Empleados de Bimbo

open class Empleado(
    val nombre: String,
    val salarioBase: Double
) {
    open fun calcularPago(): Double = salarioBase
    fun mostrarPago() {
        println("Pago de $nombre: $${calcularPago()}")
    }
}

class Vendedor(
    nombre: String,
    salarioBase: Double,
    val comision: Double
) : Empleado(nombre, salarioBase) {
    override fun calcularPago(): Double = salarioBase + comision
}

class Operador(
    nombre: String,
    salarioBase: Double
) : Empleado(nombre, salarioBase)

val v = Vendedor("Luis Mora", 12000.0, 3500.0)
val o = Operador("Sofía Ríos", 10500.0)

v.mostrarPago()  // Pago de Luis Mora: $15,500
o.mostrarPago()  // Pago de Sofía Ríos: $10,500

Vendedor sobreescribe calcularPago() para sumar la comisión. Operador hereda el comportamiento sin cambios.

Interfaces: definir contratos de comportamiento

Una interfaz define funciones que una clase debe implementar. No tiene estado propio (no guarda datos). Es útil cuando varias clases deben compartir el mismo "contrato".

interface NombreInterfaz {
    fun accion(): TipoRetorno
}

class MiClase : NombreInterfaz {
    override fun accion(): TipoRetorno {
        // implementación
    }
}

Ejemplo 5 — App de pagos de Mercado Libre

interface MetodoPago {
    fun procesarPago(monto: Double): String
}

class TarjetaCredito(val ultimos4: String) : MetodoPago {
    override fun procesarPago(monto: Double): String {
        return "Cobrado $$monto con tarjeta terminada en $ultimos4"
    }
}

class MercadoPago(val usuario: String) : MetodoPago {
    override fun procesarPago(monto: Double): String {
        return "Pago de $$monto procesado por MercadoPago para $usuario"
    }
}

fun cobrar(metodo: MetodoPago, monto: Double) {
    println(metodo.procesarPago(monto))
}

cobrar(TarjetaCredito("4321"), 850.0)
// Cobrado $850 con tarjeta terminada en 4321

cobrar(MercadoPago("jgarcia@mail.com"), 850.0)
// Pago de $850 procesado por MercadoPago para jgarcia@mail.com

La función cobrar() acepta cualquier objeto que implemente MetodoPago. Esto se llama polimorfismo.

Companion objects: miembros de clase sin instancia

Un companion object permite tener funciones o constantes ligadas a la clase, no a un objeto específico. Es el equivalente de static en Java.

class Factura(
    val folio: Int,
    val monto: Double
) {
    companion object {
        const val IVA = 0.16

        fun calcularConIva(subtotal: Double): Double {
            return subtotal * (1 + IVA)
        }
    }
}

println(Factura.IVA)                     // 0.16
println(Factura.calcularConIva(5000.0))  // 5800.0

No necesitas crear una instancia de Factura para usar calcularConIva(). Esto es útil para utilidades relacionadas con la clase.

Tabla comparativa: clase vs data class vs interface

Característica class data class interface
Guarda estado
equals() automático
copy() disponible
Permite herencia ✅ (con open) ✅ (implementación)
Uso principal Lógica y comportamiento Modelos de datos Contratos de comportamiento

Errores comunes

1. Olvidar open en la clase padre

class Animal(val nombre: String)  // Error: no se puede heredar
class Perro : Animal("Perro")      // ❌ Compilación falla

Solución: agrega open antes de class Animal.

2. Usar class cuando necesitas data class

Si guardas datos y necesitas comparar objetos por contenido (listas, estados de UI), usa data class. Una class normal compara por referencia en memoria, lo que causa bugs difíciles de detectar en Android.

3. Poner lógica de negocio en una data class

Las data class son para datos, no para lógica compleja. Si tu clase calcula impuestos, valida RFC o llama a una API, usa una class normal o un servicio separado.

4. Confundir herencia con interfaces

Usa herencia cuando una clase "es un tipo de" otra (un Vendedor es un Empleado). Usa interfaces cuando una clase "puede hacer" algo (un TarjetaCredito puede procesar pagos). Kotlin no permite herencia múltiple de clases, pero sí implementar múltiples interfaces.

Resumen rápido

  • class — para objetos con lógica y comportamiento
  • data class — para modelos de datos (usuarios, productos, pedidos)
  • open class + override — para herencia
  • interface — para definir contratos compartidos
  • companion object — para constantes y funciones de clase

Puntos clave

  • Usa `data class` para modelar datos en tu app (usuarios, productos, pedidos): Kotlin genera automáticamente `equals()`, `toString()` y `copy()` sin que escribas código extra.
  • Marca una clase con `open` para permitir herencia; sin esa palabra clave, Kotlin bloquea la extensión por defecto para evitar errores involuntarios.
  • Las interfaces definen contratos de comportamiento que varias clases pueden implementar, lo que permite el polimorfismo: una función puede aceptar cualquier objeto que cumpla el contrato.
  • El `companion object` reemplaza a `static` de Java: te permite acceder a constantes y funciones directamente desde el nombre de la clase, sin crear una instancia.
  • Separa responsabilidades: usa `data class` solo para datos, `class` para lógica, e `interface` para comportamientos compartidos entre clases no relacionadas.

Comparte esta lección:

¿Cómo funcionan las clases y la programación orientada a objetos en Kotlin? | Kotlin para Android: Curso Práctico | Certmundo