Saltar a contenido

5.B Polimorfismo y clases abstractas

Polimorfismo

Polimorfismo significa «muchas formas»: una misma llamada se comporta de forma distinta según el objeto real sobre el que se hace. En el ejemplo de la herencia, a.presentarse() imprimía algo diferente para un perro, un gato y un animal genérico.

Para entenderlo hay que distinguir dos tipos:

  • El tipo declarado de la variable, que es lo que ve el compilador (Animal).
  • El tipo real del objeto, que se decide al crearlo (Perro, Gato...).

Al llamar a un método sobrescrito, se ejecuta la versión del tipo real. Gracias a eso se puede escribir código que trabaja con el tipo general y funciona con todos los casos presentes y futuros: si mañana se añade Loro, el bucle que recorre los animales no cambia.

Clases abstractas

A veces una clase base solo tiene sentido como molde: un «instrumento» genérico no suena a nada, pero sí una guitarra o un tambor. Una clase abstracta es una clase que no se puede instanciar y que obliga a sus subclases a completar lo que falta.

Se marca la clase con abstract y cada método sin cuerpo con abstract (abstract fun sonido(): String). No hace falta open: lo abstracto ya está pensado para heredarse. No se puede crear un objeto de una clase abstracta.

abstract class Instrumento(val nombre: String) {
    abstract fun sonido(): String // abstracto: cada subclase lo define

    fun tocar() = "$nombre: ${sonido()}" // concreto: usa el abstracto
}

class Guitarra : Instrumento("Guitarra") {
    override fun sonido() = "rasgueo de cuerdas"
}

class Tambor : Instrumento("Tambor") {
    override fun sonido() = "¡pum pum!"
}

class Piano : Instrumento("Piano") {
    override fun sonido() = "notas de teclado"
}

fun main() {
    val orquesta = listOf<Instrumento>(Guitarra(), Tambor(), Piano())
    for (instrumento in orquesta) {
        println(instrumento.tocar())
    }
    println("en la orquesta hay ${orquesta.size} instrumentos")
}

Salida:

Guitarra: rasgueo de cuerdas
Tambor: ¡pum pum!
Piano: notas de teclado
en la orquesta hay 3 instrumentos

Fíjate en el diseño:

  • sonido() es abstracto: la clase base sabe que todo instrumento suena, pero no cómo, así que lo deja sin escribir.
  • tocar() es concreto y ya está escrito, pero usa sonido(). Es una idea muy útil: la base fija el esquema y cada subclase rellena el hueco. Se la conoce como método plantilla.
  • El código que recorre la orquesta solo conoce Instrumento; no le importa cuántos tipos hay.

¿Cuándo hacer una clase abstracta?

Cuando la clase base comparte código o datos (aquí, el nombre y tocar) y además obliga a las hijas a definir algo. Si solo hay que fijar un contrato, sin código común, una interfaz es más flexible.

Errores frecuentes

Error Cómo evitarlo
Intentar crear un objeto de una clase abstracta Crear un objeto de una subclase concreta
Que una subclase olvide implementar un método abstracto El compilador (o Python al crear el objeto) lo avisa: hay que implementarlo todo
Poner demasiado en la clase abstracta Solo lo que es común a todas las hijas
Usar herencia solo para compartir código Valorar composición o funciones auxiliares

Para practicar

Haz los ejercicios de clases abstractas y polimorfismo de U5.1 (los primeros). Y para ver cómo se escribe en otro lenguaje: Pasar de uno a otro.