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
Regla de Effective Java (Bloch): favorece la composición sobre la herencia a menos que la clase esté diseñada explícitamente para ser extendida. La herencia es apropiada cuando la relación "es un" es genuina, estable y la superclase ha sido diseñada para ese fin.

Siguiente apartado → Control de visibilidad: modificadores de acceso y paquetes

Índice de la sección

Índice del curso