Diseño de software

Programación orientada a protocolos (POP): superar los límites de OOP

La programación orientada a protocolos (POP) diseña los tipos según lo que pueden hacer, no según lo que heredan. Este artículo resume con ejemplos de Swift dónde falla la herencia, cómo resolverlo combinando protocolos y cuándo evitar este enfoque.

4 min de lectura
Imagen de portada de Programación orientada a protocolos (POP): superar los límites de OOP

Al ampliar funcionalidades mediante herencia de clases, todos hemos visto cómo la clase padre se vuelve cada vez más grande. Si seguimos añadiendo métodos comunes a la superclase, acabamos con una clase padre enorme que da miedo tocar.

La programación orientada a protocolos (POP) supera aquí los límites de OOP. POP diseña los tipos según «lo que pueden hacer», no según «lo que heredan». En lugar de una jerarquía vertical, combina las capacidades necesarias mediante protocolos.

En este artículo veremos qué limitaciones de OOP dieron lugar a POP, qué cambia en el código Swift real y cuándo conviene usarlo o evitarlo.


¿Dónde se atasca la herencia de OOP?

La herencia orientada a objetos es potente, pero suele encontrar obstáculos en tres puntos.

Primero, la limitación de la herencia única. Las clases de Swift solo pueden tener un padre. Para crear un tipo que haga red y use caché, la herencia por sí sola no basta.

Segundo, el problema de la clase base obesa. Al concentrar la funcionalidad común en el padre, los hijos heredan incluso métodos que no utilizan.

Tercero, su compatibilidad con los tipos por valor. Los struct y enum de Swift no admiten herencia, mientras que gran parte de la biblioteca estándar de Swift usa tipos por valor.

Por tanto, un diseño centrado solo en la herencia dificulta aprovechar las ventajas de los tipos por valor que Swift promueve.


¿Qué es la programación orientada a protocolos (POP)?

POP se hizo popular en 2015, cuando Apple declaró en la WWDC que «Swift es un lenguaje orientado a protocolos».

La clave es que los protocolos pueden incluir implementaciones predeterminadas. Con las extensiones de protocolo, no solo se define la interfaz: también se distribuye el comportamiento real.

Por ejemplo:

protocol Greetable {
    var name: String { get }
}

extension Greetable {
    func greet() -> String {
        return "Hola, \(name)soy"
    }
}

struct Person: Greetable {
    let name: String
}

print(Person(name: "Jihun").greet())
// Salida: Hola, soy Jihun

Con adoptar Greetable, greet() viene incluido. Sin herencia, también hemos añadido la funcionalidad a un struct.

También se pueden adoptar varios protocolos a la vez. Si definimos por separado las capacidades de red y caché, cada tipo recibe solo las capacidades que necesita.

Diagrama de clases del tipo Person adoptando los protocolos Greetable y Cacheable
Un protocolo, una capacidad: añade solo lo que necesitas

Herencia de OOP frente a POP: ¿qué cambia?

La diferencia puede resumirse en esta tabla.

Elemento Herencia de OOP POP
Reutilización del código Se hereda de la clase padre Se combina mediante extensiones de protocolo
Relación entre tipos Vertical (is-a) Horizontal (can-do)
Adopción múltiple Solo herencia única Adopción simultánea de varios protocolos
Compatibilidad con tipos por valor No disponible para struct/enum Disponible para struct/enum
Acoplamiento Padre e hijo están fuertemente vinculados Separación flexible por capacidades

En una frase:

La herencia dice «qué eres» y el protocolo dice «qué puedes hacer».

Pantalla de Xcode con una tabla comparativa de OOP y POP, junto a un escritorio con café
En estos casos, pienso primero «¿puedo dividirlo con protocolos?» y no «empecemos por la herencia»

¿Cuándo usar POP y cuándo evitarlo?

POP no sirve para todo; hay que elegir según la situación.

Situación Evaluación
Quiero compartir funcionalidad entre tipos por valor (struct/enum) POP es la respuesta
Necesito combinar capacidades distintas POP ofrece ventajas
Ya existe una jerarquía clara (animal-mamífero-perro) La herencia también funciona bien
Objeto donde compartir referencias es esencial (p. ej., un controlador de vista) La herencia de clases es natural
Los protocolos están tan fragmentados que cuesta seguirlos Abstracción excesiva; hay que revisarla

El criterio es sencillo: POP para tipos por valor y combinaciones de capacidades; herencia para jerarquías claras y referencias compartidas. No son rivales: son herramientas que pueden usarse juntas.

Así pueden preguntarlo en una entrevista

P. ¿Qué limitaciones de OOP resuelve POP?

Resuelve la herencia única y el problema de las clases base obesas. Permite combinar varios protocolos para añadir solo las capacidades necesarias y proporcionar implementaciones predeterminadas mediante extensiones de protocolo a tipos por valor como struct/enum, que no admiten herencia.

P. ¿Cuál es la diferencia entre una extensión de protocolo y la herencia de clases?

La herencia establece una relación vertical is-a y permite un solo padre; los protocolos establecen una relación horizontal can-do y pueden adoptarse varios a la vez. Además, la herencia solo se aplica a clases, que son tipos por referencia, mientras que los protocolos también se aplican a tipos por valor.


No se trata de que la herencia sea mala y los protocolos buenos. Pero al desarrollar con Swift, conviene pensar primero «¿puedo dividir esto con protocolos?» en lugar de «empecemos por la herencia». Ese hábito conduce a un código más flexible.

Seguir leyendo