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:
ClientetienePedidos - 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:
AutotieneRuedas - 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:
AutotieneMotor - 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:
Autoes unVehí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
- Asociación: Relación bidireccional o unidireccional
- Agregación: Relación “tiene-un” débil
- Composición: Relación “es-parte-de” fuerte
- Herencia: Especialización y reutilización
- Multiplicidad: Número de instancias de la relación
- Roles: Designación del rol de la relación
- Navegabilidad: Dirección de la relación
- 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ímbolo | Significado | Ejemplo |
|---|---|---|
| 1 | Exactamente uno | 1 Motor |
| 0..1 | Cero o uno | 0..1 Licencia |
| * | Cero o más | * Neumáticos |
| 1..* | Al menos uno | 1..* Puertas |
| 2..4 | Entre 2 y 4 | 2..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
-
¿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.
-
¡Explica la multiplicidad 1..*! Al menos uno, se pueden conectar tantos objetos como sea necesario.
-
¿Cuándo usas herencia en lugar de composición? Cuando existe una relación “es-un” y deseas reutilizar código.
-
¿Qué significa una asociación bidireccional? Ambas clases se conocen mutuamente y pueden acceder la una a la otra.
Recursos principales
- https://de.wikipedia.org/wiki/Assoziation_(UML)
- https://refactoring.guru/design-patterns/composition-over-inheritance
- 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.



