Diseño de software

Polimorfismo en OOP: de overriding a protocolos

¿Alguna vez te has confundido al intentar entender de una vez el polimorfismo en OOP, desde el overriding hasta los protocolos?

4 min de lectura
Imagen de portada de Polimorfismo en OOP: de overriding a protocolos

¿Alguna vez te has confundido al intentar entender de una vez el polimorfismo en OOP, desde el overriding hasta los protocolos?

El overloading, el overriding y los protocolos pueden parecerse, así que es fácil confundirlos.

Empecemos por la conclusión: el polimorfismo significa que «una llamada con el mismo nombre se comporta de forma distinta según su objetivo». El overriding lo implementa mediante herencia, mientras que los protocolos lo hacen mediante un contrato.

En este artículo veremos paso a paso cómo se relacionan estos tres conceptos.

¿Qué es exactamente el polimorfismo?

Polimorfismo (Polymorphism) viene del griego y significa «muchas (poly) formas (morph)».

En pocas palabras, es la capacidad de distintos objetos para responder de forma diferente al mismo mensaje.

Por ejemplo, aunque les pidas lo mismo, un perro ladra y un gato maúlla cuando les dices «haz un sonido».

El código que llama solo tiene que invocar 소리내(). Cada objeto decide qué debe producir.

La clave del polimorfismo es que «quien llama no necesita saberlo».

Así puedes tratar cualquier objeto con el mismo código.

Esto hace que el código sea mucho más flexible. Añadir un animal nuevo no exige modificar el código que realiza la llamada.


Overloading y overriding: ¿cuál es la diferencia?

Sus nombres parecidos son la principal fuente de confusión.

Overloading consiste en crear varias funciones con el mismo nombre, cambiando solo los parámetros.

Overriding consiste en que una clase hija redefine y sobrescribe una función creada por su clase padre.

Como explicarlo solo con palabras puede ser difícil, veámoslo con código.

class Animal {
    func sound() { print("...") }
}

class Dog: Animal {
    override func sound() { print("Guau") }  // Overriding
}

let a: Animal = Dog()
a.sound()
// Salida: Guau

Lo interesante es que, aunque el tipo de a sea Animal, produce el sonido de Dog.

Este es el polimorfismo que se decide en tiempo de ejecución al observar el objeto real.

Una clase padre cuyos hijos redefinen el comportamiento de distintas maneras
Una clase padre cuyos hijos redefinen el comportamiento de distintas maneras

El overloading es ligeramente distinto. La función que se llamará se decide en tiempo de compilación.

func add(_ a: Int, _ b: Int) -> Int { a + b }
func add(_ a: String, _ b: String) -> String { a + b }

print(add(1, 2))       // Salida: 3
print(add("a", "b"))  // Salida: ab

Por eso también se llama polimorfismo estático al overloading y polimorfismo dinámico al overriding.


¿Por qué necesitamos protocolos?

La herencia ya permite el polimorfismo, así que ¿por qué aparecen los protocolos?

La herencia representa una relación «is-a», por lo que solo puede haber una clase padre. Es difícil que un gato sea un animal y pertenezca al mismo tiempo a otra jerarquía.

Un protocolo es mucho más flexible: cualquier tipo puede participar si cumple el contrato.

protocol Soundable {
    func sound()
}

struct Cat: Soundable {
    func sound() { print("Miau") }
}

struct Car: Soundable {
    func sound() { print("Bocinazo") }
}

let things: [Soundable] = [Cat(), Car()]
things.forEach { $0.sound() }
// Salida: Miau
// Salida: Bocinazo

Un gato y un coche no tienen ninguna relación de herencia. Aun así, puedes guardarlos en el mismo array y tratarlos exactamente igual.

Obtener polimorfismo sin una genealogía de herencia: ese es el poder de los protocolos.

También es la razón por la que lenguajes como Swift priorizan los protocolos sobre la herencia.

Probé el código directamente y entendí mejor protocol y override
Probé el código directamente y entendí mejor protocol y override

Cuándo usar herencia y cuándo usar protocolos

He resumido en una tabla los criterios que uso en la práctica.

Situación Decisión
Quiero heredar código común Herencia (overriding)
No hay relación, pero quiero garantizar el mismo comportamiento Protocolo
Quiero asignar varias capacidades a la vez Protocolo (se pueden adoptar varios)
Quiero dar polimorfismo a un tipo por valor (struct) Protocolo

Solo tienes que recordar algunos principios sencillos.

  • Usa herencia si vas a reutilizar gran parte de la implementación del padre.
  • Usa un protocolo si quieres definir una «capacidad».
  • Si te preocupa la extensibilidad, considera primero los protocolos.

Así lo preguntan en una entrevista

P. Explica la diferencia entre overloading y overriding.

El overloading define distintos parámetros con el mismo nombre y se decide en tiempo de compilación. El overriding consiste en que una clase hija redefine un método heredado y se decide en tiempo de ejecución según el objeto real. Por eso el primero se denomina polimorfismo estático y el segundo, polimorfismo dinámico.

P. ¿Qué ventajas tiene usar protocolos en lugar de herencia?

La herencia está limitada a una clase padre y no puede usarse con tipos por valor, mientras que un tipo puede adoptar varios protocolos y también pueden aplicarse a structs. Incluso los tipos sin relación pueden agruparse y tratarse mediante el mismo contrato, lo que facilita la extensión.


El polimorfismo puede parecer abstracto al principio, pero si recuerdas una sola frase —«quien llama no necesita saberlo»—, lo demás empieza a encajar.

Prueba en código el overriding y los protocolos que has aprendido hoy. Así se te quedarán mucho mejor. ¡Ánimo!

Seguir leyendo