Composición frente a herencia
Asociación, Agregación y Composición
Asociación
La relación más débil. Dos clases se conocen (una tiene una referencia a la otra), pero sus ciclos de vida son completamente independientes. Ninguna es responsable de crear o destruir a la otra.
// Asociación: dos clases que se conocen pero tienen ciclos de vida independientes
public class Profesor {
private String nombre;
public Profesor(String nombre) { this.nombre = nombre; }
public String getNombre() { return nombre; }
}
public class Curso {
private String titulo;
private Profesor profesor; // referencia, no posesión
public Curso(String titulo, Profesor profesor) {
this.titulo = titulo;
this.profesor = profesor;
}
}
// El Profesor existe independientemente del Curso
Profesor p = new Profesor("Ana García");
Curso c1 = new Curso("Java 8", p);
Curso c2 = new Curso("Spring Boot", p); // mismo profesor, dos cursos Agregación
Relación "tiene un" débil. El contenedor referencia a sus partes, pero estas pueden existir fuera del contenedor y pertenecer a otros. Si el contenedor desaparece, las partes siguen existiendo.
// Agregación: "tiene un" — el contenedor no controla el ciclo de vida del contenido
public class Departamento {
private String nombre;
private List<Empleado> empleados; // el departamento "tiene" empleados
public Departamento(String nombre) {
this.nombre = nombre;
this.empleados = new ArrayList<>();
}
public void addEmpleado(Empleado e) { empleados.add(e); }
public void removeEmpleado(Empleado e) { empleados.remove(e); }
}
// Si el Departamento se elimina, los Empleados siguen existiendo
// Pueden pertenecer a otro Departamento Composición
Relación "está compuesto de" fuerte. El contenedor crea sus partes y es responsable de su ciclo de vida. Las
partes no existen sin el contenedor. Se modela con campos final
inicializados en el constructor.
// Composición: "está compuesto de" — el contenedor controla el ciclo de vida
public class Motor {
private int cilindros;
public Motor(int cilindros) { this.cilindros = cilindros; }
}
public class Coche {
private final Motor motor; // el Motor es parte del Coche
public Coche(int cilindros) {
this.motor = new Motor(cilindros); // el Coche crea su Motor
}
// El Motor no existe sin el Coche
} Por qué preferir composición sobre herencia
La herencia acopla fuertemente la subclase a la superclase: cualquier cambio en la superclase puede romper la subclase. Además, los métodos heredados que no tienen sentido en la subclase quedan expuestos de todas formas.
// Herencia: "es un" — problema cuando se abusa de ella
public class Pila extends ArrayList<String> { // MAL: Pila "es un" ArrayList?
public void push(String s) { add(s); }
public String pop() { return remove(size() - 1); }
// Problema: se hereda add(), remove(), set()... que rompen la abstracción de pila
}
// Correcto con composición:
public class Pila {
private final ArrayList<String> datos = new ArrayList<>(); // "tiene" una lista
public void push(String s) { datos.add(s); }
public String pop() { return datos.remove(datos.size() - 1); }
public int size() { return datos.size(); }
// Solo se expone la interfaz de pila, nada más
} Regla práctica
// Regla: herencia solo cuando la relación "es un" es genuina y estable
// Composición cuando necesitas reutilizar comportamiento sin garantizar esa relación
// Correcto: un Perro ES UN Animal
class Perro extends Animal { ... }
// Correcto con composición: un Coche TIENE UN Motor
class Coche {
private final Motor motor;
}
// Anti-patrón clásico: HttpServlet extendido solo para meter lógica de negocio
// → mejor inyectar los servicios por composición