Composición vs Herencia


La comparación entre composición y herencia es un tema habitual en entrevistas técnicas. Seguro que también has escuchado la frase "favorece la composición sobre la herencia", pero ¿qué implica realmente?
Definiciones
- Composición: Es una técnica de diseño orientada a objetos que establece una relación "tiene-un" entre objetos, utilizando instancias de otras clases como variables. Por ejemplo, una persona que tiene un trabajo se puede implementar como:
public class Job {
// variables, métodos, etc.
}
public class Person {
// relación "tiene-un"
private Job job;
// otros variables, métodos, constructores, etc.
}
- Herencia: Permite establecer una relación "es-un" entre clases mediante la palabra clave
extends
. Por ejemplo, un gato es un animal:
public class Animal {
// variables, métodos, etc.
}
public class Cat extends Animal {
// más funcionalidad específica del gato
}
¿Por qué Composición sobre Herencia?
Aunque ambas técnicas promueven la reutilización de código, aquí algunas razones para priorizar composición:
- Acoplamiento bajo vs alto: La herencia crea un acoplamiento estrecho entre clases, lo que puede generar problemas si la superclase cambia. Por ejemplo, si
ClassA
añade un métodobar()
que choca con el de su subclaseClassB
, esto causa errores de compilación. Con composición, esto no ocurre porque podemos delegar los métodos necesarios:
class ClassB {
private ClassA classA = new ClassA();
public void bar() {
classA.foo();
}
}
- Control de acceso: La herencia expone todos los métodos públicos de la superclase a la subclase, lo que puede incluir métodos innecesarios o con problemas de seguridad. Con composición, puedes seleccionar cuidadosamente qué métodos exponer.
class ClassB {
private ClassA classA = new ClassA();
public void foo() {
classA.foo(); // Exponiendo solo lo necesario
}
}
- Flexibilidad con múltiples subclases: La herencia puede complicar las jerarquías si hay muchas subclases. La composición, en cambio, permite manejar dinámicamente diferentes comportamientos:
abstract class Abs {
abstract void foo();
}
public class ClassA extends Abs {
public void foo() {
// Implementación específica
}
}
public class Test {
private Abs obj;
public Test(Abs obj) {
this.obj = obj;
}
public void foo() {
obj.foo(); // Flexible para cualquier subclase de Abs
}
}
- Facilidad en las pruebas: Las pruebas unitarias son más simples con composición porque sabes exactamente qué métodos estás utilizando. Con herencia, debes probar toda la superclase, incluso métodos que tu subclase no usa.
Conclusión
En Java, decidir entre composición y herencia es esencial para crear sistemas sólidos y sencillos de mantener. Aunque la herencia facilita la reutilización de comportamiento, puede llevar a estructuras rígidas y propensas a fallos. Por otro lado, la composición destaca por su flexibilidad y bajo acoplamiento, siendo ideal para sistemas complejos. Elegir correctamente entre ambas alternativas permite desarrollar aplicaciones más versátiles y escalables.
Subscribe to my newsletter
Read articles from Cristian F. Martin directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Cristian F. Martin
Cristian F. Martin
Desarrollador apasionado por convertir bugs en funcionalidades (y café en productividad). Especialista en sistemas distribuidos, microservicios y todo lo que hace que la tecnología funcione… o finja que lo hace. Amante del código limpio, aunque mi historial de commits podría decir otra cosa. Siempre aprendiendo, siempre depurando, y ocasionalmente negociando con mi teclado para que coopere.