Skip to content

Relaciones entre Clases: Asociación, Agregación y Composición

¡Muy buenas, arquitectos del código! Hasta ahora, hemos aprendido a crear clases, que son como los planos para fabricar los "ladrillos" de nuestro software: los objetos. Pero una casa no se construye con un solo ladrillo, ¿verdad? El verdadero poder surge cuando empezamos a conectar esos ladrillos, creando estructuras complejas y funcionales.

En este tema, vamos a explorar cómo los objetos se relacionan entre sí. Piénsalo como si fuéramos ingenieros de LEGO. No solo fabricamos piezas, sino que aprendemos cómo encajarlas. Algunas uniones son simples, como poner un ladrillo encima de otro (Asociación). Otras son más específicas, como cuando montas un coche y sabes que "tiene" ruedas (Agregación). Y otras son tan fuertes que si rompes la pieza principal, las piezas más pequeñas que la forman se van con ella, como la cabina de una nave espacial (Composición).

Dominar estas relaciones es fundamental, especialmente cuando queremos traducir un diseño, como un diagrama Entidad-Relación (E/R) de una base de datos, a un sistema de clases funcional en Java. ¡Vamos a aprender a construir estructuras robustas y con sentido!

Asociación, Agregación y Composición

En POO, las relaciones entre clases definen cómo los objetos interactúan. La asociación es el concepto más general y describe cualquier tipo de relación entre dos clases. Agregación y Composición son dos formas especializadas y más fuertes de asociación.

Definición: Asociación

Es una relación "usa un/a" entre dos clases independientes. Indica que los objetos de una clase se relacionan con los de otra, pero sus ciclos de vida no están ligados. La relación puede tener distinta cardinalidad (uno a uno, uno a muchos, etc.).

Definición: Agregación

Es una relación "tiene un/a" (Has-A). Es un tipo de asociación donde una clase (el "todo") contiene o es un contenedor de otras clases (las "partes"). Sin embargo, las partes pueden existir de forma independiente al todo. Si el todo se destruye, las partes no necesariamente lo hacen. Es una asociación débil.

Definición: Composición

Es una relación "es parte de" (Part-Of). Es la forma más fuerte de agregación. El ciclo de vida de la parte está completamente ligado al del todo. Si el todo se destruye, las partes se destruyen con él. La parte no puede existir sin el todo. Es una asociación fuerte.

Imagina una biblioteca:
* Una Biblioteca tiene Libros -> Agregación. Si la biblioteca cierra, los libros todavía existen y pueden ir a otra parte.
* Una Casa está compuesta de Habitaciones -> Composición. Si demueles la casa, las habitaciones desaparecen con ella.

graph TD

    subgraph "Tipos de Relación"
        direction LR
        A(Asociación) ----> B(Agregación) ----> C(Composición);
    end



    style A fill:#cde4ff,stroke:#68aaff,stroke-width:2px
    style B fill:#9ac7ff,stroke:#68aaff,stroke-width:2px
    style C fill:#68aaff,stroke:#3a86ff,stroke-width:2px


De Diagramas Entidad-Relación a Clases Java

Una habilidad clave es saber traducir un modelo de datos (como un diagrama E/R) a un modelo de objetos en Java. La cardinalidad de la relación nos da la pista principal.

Relaciones 1:1 (Uno a Uno)

En una relación unívoca, cada instancia de una entidad se relaciona con, como máximo, una instancia de la otra. Para implementarlo, la clase "principal" o la que tiene más sentido que conozca a la otra, tendrá un atributo del tipo de la clase secundaria.

erDiagram
    PLANETA ||--|| ORBITA : tiene

En este caso, una ORBITA depende de un PLANETA para existir. Por lo tanto, Planeta es la entidad principal que "contiene" la referencia.

public class Orbita {
    private final double radioEnKm;

    public Orbita(double radioEnKm) {
        this.radioEnKm = radioEnKm;
    }

    public double getRadioEnKm() {
        return radioEnKm;
    }
    // ... otros métodos, toString(), etc.
}

public class Planeta {
    private final String nombre;
    private final Orbita orbita; // Relación 1:1

    public Planeta(String nombre, Orbita orbita) {
        this.nombre = nombre;
        this.orbita = orbita;
    }
    // ... getters y otros métodos
}

Relaciones 1:N (Uno a Muchos)

Aquí, una instancia de la entidad "1" se relaciona con muchas instancias de la entidad "N". * La clase del lado "1" tendrá una colección (normalmente una List o un Set) de objetos del lado "N". * La clase del lado "N" tendrá una única referencia a un objeto del lado "1".

erDiagram
    EDITORIAL ||--|{ LIBRO : publica

Una EDITORIAL publica muchos LIBROs. Un LIBRO es publicado por una EDITORIAL.

// Lado "1" de la relación
public class Editorial {
    private String nombre;
    // Una editorial tiene una colección de libros
    private List<Libro> catalogo;

    public Editorial(String nombre) {
        this.nombre = nombre;
        // ¡Importante! Siempre inicializar la colección.
        this.catalogo = new ArrayList<>();
    }

    // Método para gestionar la relación
    public void anadirLibroAlCatalogo(Libro libro) {
        this.catalogo.add(libro);
    }

    public List<Libro> getCatalogo() {
        return Collections.unmodifiableList(catalogo); // Buena práctica: devolver una vista de solo lectura
    }
    // ... getters, setters, etc.
}

// Lado "N" de la relación
public class Libro {
    private String titulo;
    // Un libro tiene una sola editorial
    private Editorial editorial;

    public Libro(String titulo, Editorial editorial) {
        this.titulo = titulo;
        this.editorial = editorial;
    }
    // ... getters, setters, etc.
}

Devolver Copias o Vistas Inmutables

Cuando un método get devuelve una colección, es una buena práctica devolver una copia o una vista inmutable (Collections.unmodifiableList(...)) de la lista. Esto evita que el código externo pueda modificar la lista interna de tu objeto sin pasar por tus métodos de control (como addLibro), manteniendo así el encapsulamiento.

Relaciones N:M (Muchos a Muchos)

En una relación de muchos a muchos, ambas clases contendrán una colección de objetos de la otra clase.

erDiagram
    LIBRO }|--|{ AUTOR : escrito_por

Un LIBRO puede tener varios AUTORes, y un AUTOR puede tener varios LIBROs.

public class Libro {
    private String titulo;
    private List<Autor> autores;

    public Libro(String titulo) {
        this.titulo = titulo;
        this.autores = new ArrayList<>();
    }

    // Método para añadir un autor y mantener la consistencia
    public void addAutor(Autor autor) {
        this.autores.add(autor);
        // Opcional pero recomendado: mantener la relación bidireccional
        if (!autor.getLibros().contains(this)) {
            autor.addLibro(this);
        }
    }
    // ...
}

public class Autor {
    private String nombre;
    private List<Libro> libros;

    public Autor(String nombre) {
        this.nombre = nombre;
        this.libros = new ArrayList<>();
    }

    // Método para añadir un libro y mantener la consistencia
    public void addLibro(Libro libro) {
        this.libros.add(libro);
        if (!libro.getAutores().contains(this)) {
            libro.addAutor(this);
        }
    }
    public List<Libro> getLibros(){
        return this.libros;
    }
    // ...
}

Relaciones N:M con Atributos

Si la relación N:M tiene sus propios atributos (por ejemplo, la fecha en que un autor publicó un libro), el modelo se complica. Se necesita una tercera clase, a menudo llamada "clase de enlace" o "clase de asociación", para representar la relación en sí.

erDiagram
    LIBRO ||--o{ PUBLICACION : "tiene"
    AUTOR ||--o{ PUBLICACION : "realiza"
    PUBLICACION {
        Date fechaPublicacion
        String rol "rol del autor (e.g., 'co-autor')"
    }
En este caso, la clase Publicacion une Libro y Autor y contiene los datos específicos de esa unión.

public class Publicacion {
    private final Libro libro;
    private final Autor autor;
    private final Date fechaPublicacion;
    private final String rol;

    public Publicacion(Libro libro, Autor autor, Date fecha, String rol) {
        this.libro = libro;
        this.autor = autor;
        this.fechaPublicacion = fecha;
        this.rol = rol;
    }
    // ... getters ...
}

public class Libro {
    private String titulo;
    private List<Publicacion> publicaciones;
    // ...
}

public class Autor {
    private String nombre;
    private List<Publicacion> publicaciones;
    // ...
}

Reflexiona

  1. Modela la relación entre Conductor y Vehiculo. ¿Es 1:1, 1:N o N:M? ¿Cómo lo implementarías en Java?
  2. Piensa en una red social. Un Usuario puede seguir a muchos Usuarios, y ser seguido por muchos. ¿Qué tipo de relación es? ¿Cómo la modelarías?
  3. En el ejemplo de Editorial y Libro (1:N), ¿quién debería ser responsable de crear la relación? ¿El Libro en su constructor, o la Editorial a través de un método publicarLibro()? Debate las ventajas de cada enfoque.



Cuadro Comparativo: Asociación vs. Agregación vs. Composición

Característica Asociación Agregación Composición
Definición Relación genérica entre dos clases independientes. Forma especial de asociación ("tiene un/a") donde las partes pueden existir independientemente del todo. Forma fuerte de asociación ("es parte de") donde las partes no pueden existir sin el todo.
Dependencia Las clases están relacionadas, pero pueden existir de forma independiente. Los objetos contenidos pueden existir independientemente del contenedor. Los objetos contenidos no pueden existir sin su contenedor.
Ciclo de Vida Ciclos de vida independientes. Ciclos de vida independientes. Ciclo de vida dependiente. Si el contenedor se destruye, las partes también.
Propiedad No implica propiedad. Propiedad compartida o no exclusiva. Propiedad exclusiva. El contenedor es el único dueño de sus partes.
Fuerza Débil Moderada Fuerte
Cardinalidad 1:1, 1:N, N:1, N:M 1:1, 1:N, N:1, N:M 1:1, 1:N
Ejemplo Un Profesor enseña a Estudiantes. Una Biblioteca tiene Libros. Un Coche tiene un Motor específico (el motor no puede estar en dos coches a la vez).
Representación class Profesor { private Estudiante estudiante; } class Biblioteca { private List<Libro> libros; } class Coche { private final Motor motor; }