Composición vs. herencia: el verdadero significado de «no uses herencia»
Al estudiar programación orientada a objetos, hay una frase con la que tarde o temprano te encuentras: «Prefiere la composición a la herencia (Favor composition over inheritance)».
La conclusión es que esta frase no significa «no uses herencia jamás», sino «no abuses de la herencia para reutilizar código». La herencia sigue siendo una herramienta válida, pero en la mayoría de los casos que requieren reutilización, la composición es una opción más segura.
En este artículo explicaré, a partir de mi experiencia, cómo se diferencian ambas opciones en código real y cuándo conviene elegir cada una.
¿En qué se diferencian la herencia y la composición?
Empecemos por una distinción muy sencilla.
La herencia representa una relación «es un (is-a)». Un perro es un animal, por ejemplo. La clase hija hereda directamente las funciones de la clase padre.
La composición representa una relación «tiene un (has-a)», como un coche tiene un motor. Contiene un objeto con la funcionalidad necesaria y delega el trabajo en él.
Se entiende mejor con código. Esto es heredar funcionalidad mediante herencia.
// Herencia: Stackrecibe NSMutableArraytodos los métodos
class Stack: NSMutableArray {
func push(_ o: Any) { add(o) }
func pop() -> Any {
let o = lastObject!
removeLastObject()
return o
}
}
El problema es que Stack también expone al exterior métodos no deseados como insert(_:at:) y removeObject(at:).
Así se ve la misma implementación transformada mediante composición.
// Composición: seleccionar solo la funcionalidad necesaria y delegarla
class Stack {
private var list: [Any] = []
func push(_ o: Any) { list.append(o) }
func pop() -> Any { list.removeLast() }
}
Mantenemos el array dentro y solo exponemos las operaciones necesarias. Stack conserva únicamente lo que realmente debe hacer.
El verdadero significado de «no uses herencia»
Esta recomendación surge de dos debilidades representativas de la herencia.
Primero, se rompe la encapsulación. La clase hija pasa a depender de la implementación interna de la clase padre. Si cambia el código del padre, la hija puede romperse de repente aunque antes funcionara correctamente.
Segundo, el acoplamiento aumenta demasiado. Padre e hija quedan fuertemente ligados en tiempo de compilación, por lo que después resulta difícil cambiar su relación.
La herencia no es una herramienta para reutilizar código, sino una herramienta para definir tipos.
Esta frase es la clave. En cuanto usas herencia solo porque quieres reutilizar código, la relación empieza a complicarse.
El cuadrado y el rectángulo son un ejemplo clásico. Matemáticamente, un cuadrado es un rectángulo, así que parece lógico usar herencia. Sin embargo, al heredar la capacidad del rectángulo de «cambiar por separado el ancho y el alto», el cuadrado deja de ser un cuadrado.
Aunque parezca una relación is-a, si no puedes sustituir completamente también el comportamiento, la herencia se convierte en una trampa.
Entonces, ¿cuándo está bien usar herencia?
La herencia no es siempre mala. Si cumples todas las condiciones siguientes, puede resultar incluso más limpia.
- ¿Es una relación is-a real?: ¿la hija es siempre un tipo de la padre?
- ¿Respeta el principio de sustitución de Liskov?: ¿puedes colocar la hija donde esperas al padre sin problemas?
- ¿El padre fue diseñado pensando en la herencia?: ¿está documentado y abierto a la extensión?
Si las tres respuestas son afirmativas, puedes usar herencia. Extender clases de framework como UIViewController es un caso representativo.
Por el contrario, si una sola resulta dudosa, piensa primero en la composición.
Lo he comparado brevemente en una tabla.
| Situación | Recomendación |
|---|---|
| Relación is-a pura y sustitución completa | Herencia |
| Cuando solo quieres reutilizar código | Composición |
| Cuando quieres cambiar el comportamiento en tiempo de ejecución | Composición |
| Cuando necesitas combinar varias funcionalidades | Composición |
Como puedes ver, la mayoría de las situaciones prácticas se inclinan por la composición. El consejo «favor composition» no apareció por casualidad.
Así lo decido en la práctica
Cuando creo una clase nueva, suelo preguntarme primero: «¿Es un tipo de su padre o solo quiero tomar prestada su funcionalidad?»
Si solo quiero tomarla prestada, casi siempre elijo composición.
Los patrones de diseño también dejan clara la dirección. El patrón Strategy, el patrón Decorator y otros se basan en la composición.
En especial, Strategy separa el comportamiento en un objeto y lo intercambia en tiempo de ejecución. Es una flexibilidad difícil de imitar con herencia.
Por supuesto, debes aceptar que el código se alargue un poco, porque tienes que escribir los métodos delegados manualmente. Aun así, después resulta mucho más fácil cambiar la estructura.
«No uses herencia» no significa prohibirla, sino advertir contra su abuso con fines de reutilización. Si el is-a es claro, usa herencia; si solo quieres tomar prestada una función, usa composición. Con este único criterio, tu código será mucho más sólido. ¡Espero que hoy también diseñes bien!
Artículos recomendados
- ¿La abstracción (Abstraction) y la encapsulación (Encapsulation) en OOP te confunden? Resuélvelo con este artículo
- La verdadera razón para usar la palabra clave final de Swift en una clase (resumen completo de rendimiento y diseño)
- La esencia de la programación orientada a objetos son los mensajes: el verdadero OOP de Alan Kay

