En la Programación Orientada a Objetos (POO), las clases son los bloques fundamentales que representan entidades o conceptos del mundo real. Sin embargo, estas clases no existen de forma aislada. Para construir sistemas complejos y funcionales, las clases deben relacionarse entre sí. Las relaciones entre clases definen la forma en que interactúan y colaboran para construir aplicaciones modulares y reutilizables. Comprender estos conceptos es clave para diseñar software coherente, bien estructurado, eficiente y escalable.
Como herramienta para generar y visualizar los diagramas de clases usaremos PlantUML. Esta herramienta puede instalarse en prácticamente cualquier entorno de desarrollo (IDE).
¿Qué son las Relaciones entre Clases?
Las relaciones entre clases describen cómo una clase se conecta con otra, cómo interactúan las clases y se asocian entre sí. Estas conexiones pueden ser de diferentes tipos, dependiendo de la naturaleza de la interacción. Las relaciones más comunes son:
- Herencia: Una clase hereda atributos y métodos de otra.
- Dependencia: Una clase usa temporalmente a otra.
- Asociación: Una clase tiene una relación más permanente con otra.
1. Herencia
La herencia es una relación de tipo «es-un». Las clases que integran una herencia juegan uno de estos papeles:
- Subclase, también llamada clase hija o clase derivada. Esta clase hereda atributos y métodos de la otra clase.
- Superclase, también llamada ancestro o clase base). Esta clase propaga sus atributos y métodos a la otra clase.
La herencia permite reutilizar código y crear jerarquías de clases.
@startuml
class Animal {
#nombre : String
+void comer()
}
class Perro {
+void ladrar()
}
Animal <|-- Perro
@enduml
Ejemplo en Java
// Superclase
class Animal {
protected String nombre;
public Animal(String nombre) {
this.nombre = nombre;
}
void comer() {
System.out.println("El animal " + nombre + " está comiendo.");
}
}
// Subclase
class Perro extends Animal {
void ladrar() {
System.out.println("El perro " + nombre + " está ladrando.");
}
}
public class Main {
public static void main(String[] args) {
Perro miPerro = new Perro("Jacinto");
miPerro.comer(); // Método heredado
miPerro.ladrar(); // Método propio
}
}
Explicación:
- La clase
Perro
hereda deAnimal
, por lo que tiene unnombre
y puede usar el métodocomer()
. - La flecha con triángulo (
<|--
) indica herencia.
Puedes ver más información concreta sobre la herencia en Herencia en Java: la magia de la reutilización. Si tienes dudas sobre los modificadores de acceso te recomiendo que consultes Comprendiendo la Visibilidad de Atributos y Métodos en Java.
2. Dependencia
La dependencia es una relación temporal donde una clase usa a otra, generalmente para realizar alguna acción específica, pero no la contiene como parte de su estructura. Por ejemplo, un método de una clase puede recibir un objeto de otra clase como parámetro.
Se suele describir como una relación «usa a»: un Conductor
usa un Coche
, una Impresora
imprime un Documento
.
@startuml
class Coche {
+encender() : void
}
class Conductor {
+conducir(coche : Coche) : void
}
Conductor ..> Coche : usa
@enduml
Ejemplo en Java
class Coche {
void encender() {
System.out.println("El coche está encendido.");
}
}
class Conductor {
void conducir(Coche coche) {
coche.encender();
System.out.println("El conductor está conduciendo.");
}
}
public class Main {
public static void main(String[] args) {
Coche miCoche = new Coche();
Conductor miConductor = new Conductor();
miConductor.conducir(miCoche);
}
}
Explicación:
- La clase
Conductor
depende deCoche
porque usa un objeto de tipoCoche
en su métodoconducir()
. - La flecha discontinua (
-->
) indica dependencia.
3. Asociación
La asociación es una relación estructural, más permanente entre dos clases. Indica que una clase está conectada a otra porque colaboran o se comunican entre sí. Se da cuando una clase tiene una referencia a otra como parte de su estructura.
La relación puede ser unidireccional, en la que solo una de las clases mantiene referencia a la otra, o bidireccional, en las que ambas clases se referencian mutuamente.
Asociación Unidireccional
En una asociación unidireccional, solo una clase tiene una referencia a la otra. La clase que tiene la referencia puede acceder a los métodos y atributos de la otra, pero no al revés.
Supongamos que tenemos una clase Persona
y una clase Coche
. Un Coche
pertenece a una Persona
, pero la Persona
no tiene una referencia directa a su Coche
.
@startuml
class Persona {
-nombre : String
+getNombre() : String
}
class Coche {
-propietario : Persona
+mostrarPropietario() : void
}
Persona "1" --> "*" Coche : posee
@enduml
class Persona {
String nombre;
public Persona(String nombre) {
this.nombre = nombre;
}
public String getNombre() {
return nombre;
}
}
class Coche {
private Persona propietario;
public Coche(Persona propietario) {
this.propietario = propietario;
}
public void mostrarPropietario() {
System.out.println("Propietario: " + propietario.getNombre());
}
}
public class Main {
public static void main(String[] args) {
Persona juan = new Persona("Juan");
Coche cocheDeJuan = new Coche(juan);
cocheDeJuan.mostrarPropietario(); // Salida: Propietario: Juan
}
}
Asociación Bidireccional
En una asociación bidireccional, ambas clases tienen una referencia entre sí. Esto permite que ambas clases interactúen directamente.
Supongamos que tenemos una clase Autor
y una clase Libro
. Un Autor
puede escribir varios Libros
, y un Libro
tiene un Autor
.
@startuml
class Autor {
-nombre : String
-libros : List<Libro>
+agregarLibro(Libro libro) : void
+mostrarLibros() : void
}
class Libro {
-titulo : String
-autor : Autor
+setAutor(Autor autor) : void
+getTitulo() : String
+mostrarAutor() : void
}
Autor "1" -- "0..*" Libro : escribe
@enduml
import java.util.ArrayList;
import java.util.List;
class Autor {
private String nombre;
private List<Libro> libros;
Autor(String nombre) {
this.nombre = nombre;
this.libros = new ArrayList<>();
}
void agregarLibro(Libro libro) {
libros.add(libro);
libro.setAutor(this);
}
void mostrarLibros() {
System.out.println("Libros escritos por " + nombre + ":");
for (Libro libro : libros) {
System.out.println("- " + libro.getTitulo());
}
}
}
class Libro {
private String titulo;
private Autor autor;
Libro(String titulo) {
this.titulo = titulo;
}
void setAutor(Autor autor) {
this.autor = autor;
}
String getTitulo() {
return titulo;
}
void mostrarAutor() {
System.out.println("El libro '" + titulo + "' fue escrito por " + autor.getNombre());
}
}
public class Main {
public static void main(String[] args) {
Autor autor = new Autor("Gabriel García Márquez");
Libro libro1 = new Libro("Cien años de soledad");
Libro libro2 = new Libro("El amor en los tiempos del cólera");
autor.agregarLibro(libro1);
autor.agregarLibro(libro2);
autor.mostrarLibros();
libro1.mostrarAutor();
}
}
Explicación:
- La clase
Autor
tiene una lista deLibros
, y la claseLibro
tiene una referencia a unAutor
. - Las flechas (
-->
) en ambas direcciones indican una asociación bidireccional. - La multiplicidad
1
y0..*
indica que unAutor
puede escribir muchosLibros
, pero cadaLibro
tiene un soloAutor
.
Importancia de Comprender estas Relaciones
Entender y aplicar correctamente las relaciones entre clases permite:
- Diseño coherente: Facilita la creación de sistemas donde los componentes interactúan de manera lógica y estructurada.
- Mantenimiento simplificado: Un diseño claro reduce la complejidad al actualizar o modificar el código.
- Reutilización de código: Promueve la creación de componentes modulares que pueden ser reutilizados en diferentes partes del sistema o en proyectos futuros.
Conclusión
Las relaciones entre clases son fundamentales en la programación orientada a objetos. En esta primera parte hemos visto las relaciones básicas entre clases en POO. Estas permiten estructurar el código de manera clara y modular:
Relación | Descripción | Símbolo en PlantUML |
---|---|---|
Herencia | Una clase hereda de otra atributos y comportamientos. | <|– o –|> |
Dependencia | Una clase usa temporalmente a otra sin que una haya relación permanente. | <.. o ..> |
Asociación | Una o ambas clases tienen una referencia a la otra para interactuar. | <-- o --> (unidireccional) –(bidireccional) |
También hemos visto cómo con PlantUML, podemos visualizar estas relaciones de manera clara y sencilla.
En la siguiente parte exploraremos relaciones más complejas como realización/implementación, agregación y composición.
Deja una respuesta