Skip to content
IRC-CodingIRC-Coding
relaciones de clasesasociaciónagregacióncomposiciónherencia

Relaciones de Clases OOP: Asociación, Agregación, Composición

Aprende las relaciones entre clases OOP: asociación, agregación, composición y herencia con ejemplos prácticos y notación UML.

S

schutzgeist

7 min read
Relaciones de Clases OOP: Asociación, Agregación, Composición

Relaciones de clases en OOP: Asociación, Agregación, Composición y Herencia

Este artículo es una explicación completa de las relaciones de clases en OOP, incluyendo asociación, agregación, composición y herencia con ejemplos prácticos.

En resumen

Las relaciones de clases describen cómo los objetos interactúan y colaboran entre sí para formar sistemas complejos. Las cuatro tipos principales de relaciones son la asociación, agregación, composición y herencia.

Descripción técnica concisa

Las relaciones de clases definen la forma en que las clases y sus objetos se conectan entre sí. Son fundamentales para estructurar sistemas orientados a objetos.

Cuatro tipos principales de relaciones:

1. Asociación (tiene-un)

  • Descripción: Dos clases están conectadas entre sí
  • Ciclo de vida: Independientes entre sí
  • Ejemplo: Cliente tiene Pedidos
  • UML: Línea simple entre clases

2. Agregación (se-compone-de)

  • Descripción: Relación “tiene-un”, las partes pueden existir de forma independiente
  • Ciclo de vida: La parte puede existir sin el todo
  • Ejemplo: Auto tiene Ruedas
  • UML: Línea con rombo vacío en el todo

3. Composición (es-parte-de)

  • Descripción: Relación “es-parte-de”, las partes solo existen con el todo
  • Ciclo de vida: La parte no puede existir sin el todo
  • Ejemplo: Auto tiene Motor
  • UML: Línea con rombo lleno en el todo

4. Herencia (es-un)

  • Descripción: Relación “es-un”, especialización
  • Ciclo de vida: La subclase hereda de la superclase
  • Ejemplo: Auto es un Vehículo
  • UML: Línea con flecha vacía hacia la superclase

Puntos clave para estudio

  • Asociación: Relación simple entre clases
  • Agregación: Relación “tiene-un” con partes independientes
  • Composición: Relación “es-parte-de” con partes dependientes
  • Herencia: Relación “es-un” con especialización
  • Multiplicidades: 1, , 0..1, 1.., 0..*
  • Notación UML: Diferentes flechas y símbolos de rombo
  • Ciclo de vida: Dependencia de los objetos entre sí
  • Relevancia profesional: Importante para arquitectura y diseño de software

Componentes clave

  1. Asociación: Relación bidireccional o unidireccional
  2. Agregación: Relación “tiene-un” débil
  3. Composición: Relación “es-parte-de” fuerte
  4. Herencia: Especialización y reutilización
  5. Multiplicidad: Número de instancias de la relación
  6. Roles: Designación del rol de la relación
  7. Navegabilidad: Dirección de la relación
  8. Cualificadores: Información adicional de la relación

Ejemplos prácticos

1. Asociación (Cliente - Pedido)

// Asociación bidireccional
public class Cliente {
    private String clienteId;
    private String nombre;
    private List<Pedido> pedidos = new ArrayList<>();
    
    public void addPedido(Pedido pedido) {
        pedidos.add(pedido);
        pedido.setCliente(this); // Referencia inversa
    }
    
    public List<Pedido> getPedidos() {
        return new ArrayList<>(pedidos);
    }
}

public class Pedido {
    private String pedidoId;
    private Date fechaPedido;
    private Cliente cliente; // Referencia inversa al cliente
    
    public void setCliente(Cliente cliente) {
        this.cliente = cliente;
    }
    
    public Cliente getCliente() {
        return cliente;
    }
}

// Uso
Cliente meier = new Cliente("1", "Meier");
Pedido p1 = new Pedido("P001", new Date());
Pedido p2 = new Pedido("P002", new Date());

meier.addPedido(p1);
meier.addPedido(p2);

2. Agregación (Auto - Ruedas)

// Agregación: Auto tiene ruedas, las ruedas pueden existir sin el auto
public class Auto {
    private String modelo;
    private List<Rueda> ruedas = new ArrayList<>();
    
    public Auto(String modelo) {
        this.modelo = modelo;
    }
    
    public void addRueda(Rueda rueda) {
        if (ruedas.size() < 4) {
            this.ruedas.add(rueda);
        }
    }
    
    public void removeRueda(Rueda rueda) {
        this.ruedas.remove(rueda);
        // La rueda continúa existiendo y puede asignarse a otro auto
    }
}

public class Rueda {
    private String fabricante;
    private int tamaño;
    
    public Rueda(String fabricante, int tamaño) {
        this.fabricante = fabricante;
        this.tamaño = tamaño;
    }
    
    // La rueda puede existir independientemente del auto
    public void montar() {
        System.out.println("Rueda se monta");
    }
}

// Uso
Auto golf = new Auto("Golf");
Rueda michelin1 = new Rueda("Michelin", 195);
Rueda michelin2 = new Rueda("Michelin", 195);

golf.addRueda(michelin1);
golf.addRueda(michelin2);

// Las ruedas pueden removerse y reutilizarse
golf.removeRueda(michelin1);

3. Composición (Auto - Motor)

// Composición: El motor solo existe con el auto
public class Auto {
    private String modelo;
    private Motor motor; // El motor no puede existir sin el auto
    
    public Auto(String modelo, int potencia) {
        this.modelo = modelo;
        this.motor = new Motor(potencia); // El motor se crea internamente
    }
    
    public void iniciar() {
        motor.iniciar();
        System.out.println(modelo + " se inicia");
    }
    
    public void apagar() {
        motor.apagar();
        System.out.println(modelo + " se apaga");
    }
    
    // El motor se destruye con el auto
    protected void finalize() {
        // El motor se limpia automáticamente
    }
}

public class Motor {
    private int potencia;
    private boolean funcionando;
    
    // Constructor protegido - solo Auto puede crear un motor
    protected Motor(int potencia) {
        this.potencia = potencia;
        this.funcionando = false;
    }
    
    protected void iniciar() {
        this.funcionando = true;
        System.out.println("Motor con " + potencia + " CV se inicia");
    }
    
    protected void apagar() {
        this.funcionando = false;
        System.out.println("Motor se apaga");
    }
}

// Uso
Auto bmw = new Auto("BMW", 200);
bmw.iniciar(); // El motor se inicia internamente
bmw.apagar();

// El motor no se puede crear de forma independiente:
// Motor motor = new Motor(150); // Error: Constructor es protegido

4. Herencia (Vehículo - Auto)

// Superclase
public abstract class Vehiculo {
    protected String marca;
    protected int anio;
    protected int velocidadActual = 0;
    
    public Vehiculo(String marca, int anio) {
        this.marca = marca;
        this.anio = anio;
    }
    
    // Métodos comunes
    public void acelerar(int kmh) {
        this.velocidadActual += kmh;
        System.out.println(marca + " acelera a " + velocidadActual + " km/h");
    }
    
    public void frenar(int kmh) {
        if (velocidadActual >= kmh) {
            this.velocidadActual -= kmh;
            System.out.println(marca + " frena a " + velocidadActual + " km/h");
        }
    }
    
    // Método abstracto - debe implementarse en subclases
    public abstract void tocar();
    
    // Método concreto - puede ser sobrescrito
    public void mostrar() {
        System.out.println("Vehículo: " + marca + ", Año: " + anio + 
                          ", Velocidad: " + velocidadActual + " km/h");
    }
}

// Subclase
public class Auto extends Vehiculo {
    private int numerosPuertas;
    private boolean aireAcondicionado;
    
    public Auto(String marca, int anio, int numerosPuertas) {
        super(marca, anio); // Llamar al constructor de la superclase
        this.numerosPuertas = numerosPuertas;
        this.aireAcondicionado = false;
    }
    
    // Implementación del método abstracto
    @Override
    public void tocar() {
        System.out.println("Auto toca la bocina: Tut Tut!");
    }
    
    // Método adicional solo para Auto
    public void encenderAireAcondicionado() {
        aireAcondicionado = true;
        System.out.println("Aire acondicionado encendido");
    }
    
    // Sobrescribir el método de la superclase
    @Override
    public void mostrar() {
        super.mostrar(); // Llamar al método de la superclase
        System.out.println("  Tipo: Auto, Puertas: " + numerosPuertas + 
                          ", Aire acondicionado: " + (aireAcondicionado ? "sí" : "no"));
    }
}

// Otra subclase
public class Motocicleta extends Vehiculo {
    private boolean tieneSidecar;
    
    public Motocicleta(String marca, int anio, boolean tieneSidecar) {
        super(marca, anio);
        this.tieneSidecar = tieneSidecar;
    }
    
    @Override
    public void tocar() {
        System.out.println("Motocicleta toca la bocina: Iii Iii!");
    }
    
    public void caballito() {
        System.out.println("Motocicleta hace caballito!");
    }
    
    @Override
    public void mostrar() {
        super.mostrar();
        System.out.println("  Tipo: Motocicleta, Sidecar: " + 
                          (tieneSidecar ? "sí" : "no"));
    }
}

// Uso
Vehiculo golf = new Auto("Volkswagen", 2023, 5);
Vehiculo harley = new Motocicleta("Harley-Davidson", 2022, false);

golf.tocar();      // Auto toca la bocina: Tut Tut!
harley.tocar();   // Motocicleta toca la bocina: Iii Iii!

golf.acelerar(50);
harley.acelerar(80);

golf.mostrar();
harley.mostrar();

// Casting a subclase para métodos específicos
if (golf instanceof Auto) {
    Auto autoGolf = (Auto) golf;
    autoGolf.encenderAireAcondicionado();
}

if (harley instanceof Motocicleta) {
    Motocicleta motoHarley = (Motocicleta) harley;
    motoHarley.caballito();
}

Notación UML para relaciones

Asociación

Cliente 1..* --* Pedido

Agregación

Auto 1 --* Neumático

Composición

Auto 1 --* Motor

Herencia

Vehículo <|-- Auto
Vehículo <|-- Motocicleta

Multiplicidades

SímboloSignificadoEjemplo
1Exactamente uno1 Motor
0..1Cero o uno0..1 Licencia
*Cero o más* Neumáticos
1..*Al menos uno1..* Puertas
2..4Entre 2 y 42..4 Ruedas

Guía para elegir el tipo de relación

¿Cuándo usar cada relación?

Usa asociación cuando:

  • Dos clases interactúan pero no tienen dependencia entre ellas
  • La relación es temporal u opcional
  • Los objetos existen de forma independiente

Usa agregación cuando:

  • Hay una relación “tiene-un”
  • Las partes pueden existir sin el todo
  • Las partes pueden compartirse entre diferentes todos

Usa composición cuando:

  • Hay una relación “es-parte-de”
  • Las partes existen solo con el todo
  • El ciclo de vida de la parte está vinculado al del todo

Usa herencia cuando:

  • Hay una relación “es-un”
  • La subclase es una forma especializada de la superclase
  • Buscas reutilización de código y polimorfismo

Ventajas y desventajas

Ventajas de las relaciones entre clases

  • Estructura: Arquitectura clara del sistema
  • Reutilización: Funcionalidad compartida
  • Flexibilidad: Fácil de extender
  • Comprensión: Refleja el mundo real
  • Mantenibilidad: Cambios dirigidos y precisos

Desventajas

  • Complejidad: Muchas relaciones pueden volverse confusas
  • Acoplamiento: Las dependencias fuertes pueden causar problemas
  • Rendimiento: Demasiadas conexiones entre objetos pueden ser lentas
  • Testabilidad: Las relaciones complejas son difíciles de probar

Preguntas frecuentes de examen

  1. ¿Cuál es la diferencia entre agregación y composición? Agregación: Las partes pueden existir independientemente. Composición: Las partes existen solo con el todo.

  2. ¡Explica la multiplicidad 1..*! Al menos uno, se pueden conectar tantos objetos como sea necesario.

  3. ¿Cuándo usas herencia en lugar de composición? Cuando existe una relación “es-un” y deseas reutilizar código.

  4. ¿Qué significa una asociación bidireccional? Ambas clases se conocen mutuamente y pueden acceder la una a la otra.

Recursos principales

  1. https://de.wikipedia.org/wiki/Assoziation_(UML)
  2. https://refactoring.guru/design-patterns/composition-over-inheritance
  3. https://www.uml-diagrams.org/class-diagram-relationships.html

Continúa en la ruta de aprendizaje OOP

El siguiente artículo en la ruta de aprendizaje OOP trata sobre Herencia OOP: Fundamentos, Inheritance y Polimorfismo, explicando cómo la herencia permite reutilizar código.

Volver al blog
Share:

Nächster Artikel in Desarrollo de Software

Weiterlesen
Lenguajes de Programación: Guía Completa 2026

Entradas relacionadas