Cuando empezamos a programar, es muy tentador abrir el IDE, crear unas cuantas clases y ponerse a escribir código directamente. En ejercicios pequeños puede parecer suficiente, pero en cuanto el proyecto crece un poco aparecen los problemas: clases que hacen demasiadas cosas, relaciones poco claras, código duplicado, responsabilidades mezcladas y una sensación constante de “esto funciona, pero no sé si está bien diseñado”.
Ahí es donde entra el diagrama UML de clases.
Un diagrama UML de clases sirve para pensar la estructura de una aplicación antes de programarla. No se trata de hacer dibujos bonitos ni de rellenar cajas por obligación. Se trata de representar qué clases tendrá el sistema, qué datos guardará cada una, qué operaciones realizará y cómo se relacionarán entre ellas.
En mi caso, después de trabajar en desarrollo software, consultoría tecnológica y docencia en FP Informática, cada vez tengo más claro que un diagrama UML de clases bien planteado ahorra muchos problemas. No porque sustituya al código, sino porque obliga a razonar antes de lanzarse a escribirlo. Y eso, en programación, marca bastante la diferencia.
Un buen diagrama UML de clases ayuda a responder preguntas como estas:
- ¿Qué entidades importantes tiene mi aplicación?
- ¿Qué atributos necesita cada clase?
- ¿Qué métodos son realmente relevantes?
- ¿Qué clases se relacionan entre sí?
- ¿Una relación es simple, de herencia, de agregación o de composición?
- ¿Cuántos objetos pueden participar en cada relación?
- ¿El diseño se entiende sin tener que explicarlo todo verbalmente?
Dicho de otra forma: el diagrama UML de clases es una especie de plano del software. Igual que no tendría sentido construir una casa colocando ladrillos al azar, tampoco conviene construir una aplicación creando clases sin pensar primero su estructura.
Qué es un diagrama UML de clases
Un diagrama UML de clases es una representación visual de la estructura de un sistema orientado a objetos. Muestra las clases principales, sus atributos, sus métodos y las relaciones que existen entre ellas.
Aunque suele explicarse dentro del contexto de UML, lo importante para este artículo no es estudiar todos los tipos de diagramas, sino entender bien el diagrama UML de clases. UML es el lenguaje visual que nos da una notación común, pero el objetivo práctico es diseñar mejor nuestras clases.
Un diagrama UML de clases representa la parte estática de una aplicación. Es decir, no muestra paso a paso cómo ocurre un proceso en el tiempo, sino qué elementos forman el sistema y cómo están conectados.
Por ejemplo, si queremos crear una aplicación para una biblioteca, antes de programar podríamos detectar clases como:
- Libro
- Usuario
- Prestamo
Después, pensaríamos qué datos tiene cada una:
- Un libro puede tener título, autor, ISBN y disponibilidad.
- Un usuario puede tener nombre, DNI y correo electrónico.
- Un préstamo puede tener fecha de inicio, fecha prevista de devolución y estado.
Y, por último, representaríamos sus relaciones:
- Un usuario puede realizar varios préstamos.
- Cada préstamo pertenece a un usuario.
- Cada préstamo corresponde a un libro.
Eso ya empieza a parecerse a un diagrama UML de clases útil.
Lo interesante es que el diagrama UML de clases conecta directamente con el código. Una clase del diagrama suele acabar convirtiéndose en una clase Java. Un atributo del diagrama puede acabar siendo una variable de instancia. Un método del diagrama puede acabar siendo una operación dentro de la clase. Una relación entre clases puede convertirse en una referencia, una lista, una herencia o una dependencia.
Por eso el diagrama UML de clases no es solo documentación. También es una herramienta de diseño.
Para qué sirve un diagrama UML de clases antes de programar
El diagrama UML de clases sirve para reducir improvisación. Y en programación, improvisar demasiado suele salir caro.
Cuando no hacemos ningún tipo de modelado, las clases aparecen según las vamos necesitando. Al principio parece cómodo, pero poco a poco el diseño se vuelve más difícil de mantener. Una clase empieza gestionando usuarios, luego también pedidos, más tarde envía emails, después calcula facturas y, cuando queremos darnos cuenta, tenemos una clase enorme que nadie quiere tocar.
Con un diagrama UML de clases, la idea es justo la contraria: pensar primero y programar después.
Antes de escribir código, nos preguntamos:
- Qué clases necesita realmente el sistema.
- Qué responsabilidad tiene cada clase.
- Qué atributos pertenecen a cada clase.
- Qué métodos tienen sentido.
- Qué relaciones existen entre las clases.
- Qué multiplicidades hay en esas relaciones.
- Qué partes del diseño podrían simplificarse.
En mi experiencia docente, el diagrama UML de clases funciona especialmente bien cuando se explica con ejemplos cercanos. Biblioteca, tienda online, gestión de tareas, academia de formación… En cuanto el alumnado ve que una clase no es una caja abstracta, sino una entidad del problema, empieza a entender mejor por qué merece la pena modelar.
Además, un diagrama UML de clases permite detectar errores antes de que lleguen al código. Si en el diagrama ya vemos que una clase tiene demasiadas responsabilidades, que faltan relaciones o que una multiplicidad no tiene sentido, estamos a tiempo de corregirlo de forma barata.
Corregir un diseño en un diagrama cuesta poco. Corregirlo cuando ya hay cientos de líneas de código, pruebas, dependencias y pantallas conectadas cuesta bastante más.
Conceptos básicos para entender un diagrama UML de clases
Antes de crear un diagrama UML de clases, conviene tener claros algunos conceptos de programación orientada a objetos. No hace falta ser experto, pero sí entender las piezas básicas.
Clase y objeto
Una clase es una plantilla. Define cómo serán ciertos objetos, qué datos tendrán y qué operaciones podrán realizar.
Por ejemplo, en Java podríamos tener una clase Libro:
public class Libro {
private String titulo;
private String autor;
public void prestar() {
System.out.println("Libro prestado");
}
}
La clase Libro no es un libro concreto. Es el molde que define cómo serán los libros dentro del programa.
Un objeto, en cambio, es una instancia concreta de una clase:
Libro libro1 = new Libro();
Libro libro2 = new Libro();
libro1 y libro2 son objetos distintos creados a partir de la misma clase. Cada uno puede tener valores diferentes en sus atributos.
En un diagrama UML de clases normalmente representamos las clases, no los objetos concretos. Es decir, dibujamos Libro, Usuario o Pedido, no “libro1”, “usuarioPedro” o “pedido245”.
Atributos
Un atributo es un dato o característica de una clase. En Java suele declararse como una variable dentro de la clase.
Por ejemplo:
private String titulo;
private String autor;
private int numeroPaginas;
En un diagrama UML de clases, los atributos se colocan normalmente en la parte central del rectángulo de la clase.
Una clase Libro podría tener estos atributos:
- titulo: String
- autor: String
- isbn: String
- disponible: boolean
El símbolo – indica que el atributo es privado. Esto es habitual porque los atributos suelen protegerse para evitar modificaciones incorrectas desde fuera de la clase.
Métodos
Un método representa una operación o comportamiento de una clase. En Java se implementa como una función dentro de la clase.
Por ejemplo:
public void prestar() {
System.out.println("Libro prestado");
}
public boolean estaDisponible() {
return true;
}
En un diagrama UML de clases, los métodos se escriben normalmente en la parte inferior del rectángulo.
Para la clase Libro, podríamos representar:
+ prestar(): void
+ devolver(): void
+ estaDisponible(): boolean
El símbolo + indica que el método es público. Es decir, puede utilizarse desde otras clases.
Constructor
Un constructor es un método especial que se ejecuta cuando se crea un objeto. Sirve para inicializar sus valores.
Por ejemplo:
public Libro(String titulo, String autor) {
this.titulo = titulo;
this.autor = autor;
}
En un diagrama UML de clases, el constructor puede representarse como una operación con el mismo nombre de la clase. No siempre es obligatorio mostrarlo, pero puede ser útil cuando queremos dejar clara la forma en que se crean los objetos.
Visibilidad
La visibilidad indica desde dónde se puede acceder a un atributo o método. En un diagrama UML de clases se suele representar con símbolos.
| Símbolo | Equivalente habitual en Java | Significado |
|---|---|---|
| + | public | Accesible desde cualquier clase |
| – | private | Accesible solo desde la propia clase |
| # | protected | Accesible desde la clase y sus subclases |
| ~ | package | Accesible dentro del mismo paquete |
En general, una buena práctica es declarar los atributos como privados y ofrecer métodos públicos cuando sea necesario acceder a ellos o modificarlos.
Encapsulación
La encapsulación consiste en ocultar los detalles internos de una clase y permitir el acceso controlado mediante métodos.
Por ejemplo:
public class CuentaBancaria {
private double saldo;
public void ingresar(double cantidad) {
if (cantidad > 0) {
saldo += cantidad;
}
}
public double getSaldo() {
return saldo;
}
}
En este caso, no se permite modificar el saldo directamente desde fuera. La operación ingresar controla que la cantidad sea válida antes de cambiar el valor.
Esto también debería reflejarse en el diagrama UML de clases: los atributos importantes suelen ser privados y los métodos que forman parte de la interfaz pública suelen aparecer como públicos.
Cuando he trabajado con código real, esta diferencia se nota muchísimo. Una clase con atributos públicos y lógica desordenada puede parecer rápida al principio, pero se vuelve frágil. Una clase bien encapsulada, en cambio, comunica mejor su intención y reduce errores.
Cómo se representa una clase en un diagrama UML de clases
Una clase se representa normalmente con un rectángulo dividido en tres partes:
- Nombre de la clase.
- Atributos.
- Métodos.
Por ejemplo:
+-----------------------------+
| Libro |
+-----------------------------+
| - titulo: String |
| - autor: String |
| - isbn: String |
| - disponible: boolean |
+-----------------------------+
| + prestar(): void |
| + devolver(): void |
| + estaDisponible(): boolean |
+-----------------------------+
La primera sección contiene el nombre de la clase. La segunda contiene los atributos. La tercera contiene los métodos.
Este formato ayuda a leer rápidamente qué representa una clase. Si vemos Libro, sabemos que es una entidad del dominio. También, si vemos sus atributos, entendemos qué información almacena. Además, si vemos sus métodos, sabemos qué operaciones relevantes puede realizar.
Sintaxis habitual de atributos
La sintaxis más habitual para escribir atributos en un diagrama UML de clases es:
visibilidad nombre: Tipo
Ejemplos:
- nombre: String
- edad: int
- precio: double
- activo: boolean
En Java, estos tipos se entienden fácilmente:
| Tipo en el diagrama | Tipo aproximado en Java | Ejemplo |
|---|---|---|
| String | String | nombre: String |
| int | int | edad: int |
| double | double | precio: double |
| boolean | boolean | activo: boolean |
| LocalDate | LocalDate | fechaNacimiento: LocalDate |
| List<T> | List<T> / ArrayList<T> | libros: List<Libro> |
Lo importante es que los tipos sean coherentes con el lenguaje que se usará después. Si vamos a programar en Java, tiene sentido usar tipos cercanos a Java.
Sintaxis habitual de métodos
La sintaxis más habitual para escribir métodos en un diagrama UML de clases es:
visibilidad nombreMetodo(parametro: Tipo): TipoRetorno
Ejemplos:
+ calcularTotal(): double
+ cambiarEmail(nuevoEmail: String): void
+ buscarPorId(id: int): Usuario
+ esMayorDeEdad(): boolean
No hace falta incluir todos los métodos imaginables. Un error muy común es llenar el diagrama UML de clases con operaciones secundarias que no aportan demasiado. El diagrama debe mostrar lo importante para entender el diseño, no convertirse en una copia exacta de todo el código.
Relaciones en un diagrama UML de clases
Las relaciones son una de las partes más importantes de un diagrama UML de clases. No basta con dibujar clases sueltas. Hay que indicar cómo colaboran, cómo se contienen, cómo se heredan o cómo dependen unas de otras.
Un diagrama UML de clases sin relaciones suele quedarse a medias, porque no muestra la estructura real del sistema.
Las relaciones más habituales son:
- Asociación.
- Multiplicidad.
- Herencia.
- Agregación.
- Composición.
- Dependencia.
- Realización de interfaces.
Asociación
La asociación indica que dos clases están relacionadas de alguna forma. Es una relación general y suele representar que un objeto conoce, utiliza o se comunica con otro.
Por ejemplo:
Usuario ---------------- Prestamo
Esto puede leerse como:
- Un usuario realiza préstamos.
- Un préstamo pertenece a un usuario.
La clave está en no mirar solo la línea. Hay que preguntarse qué significa esa relación en lenguaje natural.
Si no podemos explicar la relación con una frase clara, probablemente el diagrama UML de clases no está suficientemente pensado.
Multiplicidad
La multiplicidad indica cuántos objetos de una clase pueden relacionarse con objetos de otra clase.
Algunos valores habituales son:
| Multiplicidad | Significado | Ejemplo |
|---|---|---|
| 1 | Exactamente uno | Un préstamo corresponde a un usuario |
| 0..1 | Cero o uno | Un usuario puede tener o no una tarjeta activa |
| * | Cero o muchos | Un usuario puede realizar muchos préstamos |
| 0..* | Cero o muchos | Un cliente puede no haber hecho pedidos o haber hecho varios |
| 1..* | Uno o muchos | Un pedido debe tener al menos una línea de pedido |
| 2..5 | Entre dos y cinco | Una actividad puede tener entre dos y cinco participantes |
Por ejemplo:
Usuario 1 ---------------- 0..* Prestamo
Esta relación se lee así: un usuario puede tener cero o muchos préstamos, y cada préstamo pertenece a un único usuario.
La multiplicidad es fundamental en un diagrama UML de clases porque evita ambigüedades. No es lo mismo decir “Cliente se relaciona con Pedido” que decir “un cliente puede tener cero o muchos pedidos, y cada pedido pertenece a un único cliente”.
La segunda explicación es mucho más útil para programar.
Herencia
La herencia indica que una clase especializada hereda atributos y métodos de una clase más general.
Por ejemplo:
Empleado --------▷ Persona
Esto se lee así: Empleado hereda de Persona. Persona es la clase general y Empleado es una clase especializada.
En Java podría representarse así:
public class Persona {
protected String nombre;
protected String dni;
}
public class Empleado extends Persona {
private double salario;
}
La herencia debe usarse cuando existe una relación clara de tipo “es un”.
Por ejemplo:
- Un empleado es una persona.
- Un alumno es una persona.
- Un coche es un vehículo.
Pero no conviene usar herencia solo para reutilizar código. Ese es uno de los errores más frecuentes. Si la relación no es realmente “es un”, muchas veces será mejor usar composición.
En un diagrama UML de clases, abusar de la herencia suele generar diseños rígidos y difíciles de mantener.
Agregación
La agregación representa una relación todo-parte débil. Una clase agrupa objetos de otra, pero esas partes pueden existir independientemente del todo.
Por ejemplo:
Equipo ◇---------------- Jugador
Un equipo agrupa jugadores, pero un jugador puede existir aunque el equipo desaparezca o puede cambiar de equipo.
La pregunta útil para detectar una agregación es:
¿La parte puede vivir sin el todo?
Si la respuesta es sí, probablemente estemos ante una agregación.
Composición
La composición representa una relación todo-parte fuerte. Las partes dependen del ciclo de vida del todo.
Por ejemplo:
Pedido ◆---------------- LineaPedido
Una línea de pedido tiene sentido dentro de un pedido. Si se elimina el pedido, normalmente se eliminan también sus líneas.
La pregunta útil para detectar composición es:
¿La parte depende totalmente del todo?
Si la respuesta es sí, probablemente estemos ante una composición.
La diferencia entre agregación y composición suele generar dudas, así que conviene recordarla así:
| Relación | Pregunta clave | Ejemplo |
|---|---|---|
| Agregación | ¿La parte puede existir sin el todo? | Equipo y Jugador |
| Composición | ¿La parte depende del todo? | Pedido y LineaPedido |
En el aula, esta diferencia se entiende mejor cuando dejamos de mirar el símbolo y pensamos en la vida real. Un jugador puede seguir existiendo aunque desaparezca el equipo. Una línea de pedido, en cambio, no tiene demasiado sentido si desaparece el pedido al que pertenece.
Dependencia
La dependencia indica que una clase utiliza a otra de forma puntual. No necesariamente la guarda como atributo.
Por ejemplo:
InformeService - - - - > PdfGenerator
Esto puede leerse así: InformeService usa PdfGenerator para generar un documento PDF.
En un diagrama UML de clases, una dependencia suele representar un uso más débil que una asociación. La clase depende de otra para una operación concreta, pero no necesariamente mantiene una relación permanente con ella.
Interfaces
Una interfaz define un contrato: operaciones que una clase debe implementar.
Por ejemplo:
public interface NotificationService {
void enviar(String mensaje);
}
public class EmailNotificationService implements NotificationService {
public void enviar(String mensaje) {
System.out.println("Enviando email: " + mensaje);
}
}
En un diagrama UML de clases, la realización de interfaces puede representarse con una línea discontinua y un triángulo vacío apuntando a la interfaz.
Esto indica que una clase se compromete a implementar las operaciones definidas por la interfaz.
Cómo hacer un diagrama UML de clases paso a paso
Crear un diagrama UML de clases no consiste en dibujar cajas al azar. Hay que leer, pensar, decidir y revisar.
Una forma ordenada de hacerlo es seguir estos pasos.
1. Leer el enunciado completo
Antes de dibujar nada, lee el enunciado completo. Parece obvio, pero es uno de los pasos que más se saltan.
Si empiezas a dibujar demasiado pronto, es fácil que conviertas en clase algo que luego era solo un atributo, o que olvides una relación importante que aparece al final del enunciado.
Primero entiende el problema. Después modela.
2. Subrayar sustantivos importantes
Los sustantivos suelen sugerir posibles clases o atributos.
Por ejemplo, en un enunciado sobre una biblioteca podrían aparecer:
- Biblioteca.
- Libro.
- Usuario.
- Préstamo.
- Título.
- Autor.
- ISBN.
- Correo electrónico.
- Fecha de devolución.
Pero cuidado: no todos los sustantivos deben convertirse en clases.
Título, autor e ISBN seguramente serán atributos de Libro. No tendría sentido crear una clase Titulo o una clase ISBN en un ejercicio básico de biblioteca.
3. Subrayar verbos importantes
Los verbos suelen sugerir métodos o relaciones.
Por ejemplo:
- Un usuario realiza préstamos.
- Un libro se presta.
- Un préstamo se finaliza.
- Un cliente realiza pedidos.
- Un pedido calcula su total.
De aquí pueden salir métodos como:
+ prestar(): void
+ finalizar(): void
+ calcularTotal(): double
También pueden salir relaciones. Si el enunciado dice “un usuario realiza préstamos”, probablemente habrá una relación entre Usuario y Prestamo.
4. Identificar las clases principales
Las clases principales suelen representar entidades del dominio con datos propios, comportamiento propio o ciclo de vida propio.
Una buena pregunta es:
¿Este elemento tiene información propia y participa de forma importante en el sistema?
Si la respuesta es sí, puede ser una clase.
Por ejemplo, en una biblioteca:
- Libro tiene datos propios y participa en préstamos.
- Usuario tiene datos propios y realiza préstamos.
- Prestamo conecta a Usuario con Libro y guarda fechas.
Por tanto, Libro, Usuario y Prestamo son buenas clases candidatas.
5. Separar clases de atributos
Este paso es crucial. Uno de los errores más típicos al hacer un diagrama UML de clases es convertir demasiadas palabras en clases.
Para decidir si algo es clase o atributo, puedes usar esta tabla:
| Pregunta | Si la respuesta es sí… | Ejemplo |
|---|---|---|
| ¿Tiene datos propios? | Puede ser una clase | Libro |
| ¿Tiene comportamiento propio? | Puede ser una clase | Prestamo |
| ¿Se relaciona con varias entidades? | Puede ser una clase | Pedido |
| ¿Solo describe una característica simple? | Probablemente sea atributo | nombre, edad, email |
| ¿Tiene ciclo de vida propio? | Puede ser una clase | Usuario |
| ¿Solo es un valor calculado? | Puede ser método o atributo derivado | totalPedido |
Este paso es donde más se nota la diferencia entre dibujar por intuición y diseñar con criterio.
En mi forma de trabajar, tanto en desarrollo como en docencia, intento insistir mucho en esta idea: no estamos buscando “muchas clases”, estamos buscando buenas clases. Un diagrama UML de clases enorme no es necesariamente mejor. A veces solo es más confuso.
6. Asignar atributos a cada clase
Una vez detectadas las clases principales, toca repartir los datos.
Por ejemplo:
| Clase | Atributos |
|---|---|
| Libro | titulo: String, autor: String, isbn: String, disponible: boolean |
| Usuario | nombre: String, dni: String, email: String |
| Prestamo | fechaInicio: LocalDate, fechaPrevistaDevolucion: LocalDate, finalizado: boolean |
Los atributos deben tener sentido dentro de la clase. Si un dato no pertenece claramente a esa clase, quizá el diseño necesita revisarse.
7. Añadir métodos relevantes
Después puedes añadir métodos. No todos, solo los más importantes para entender el comportamiento de la clase.
Por ejemplo:
| Clase | Métodos posibles |
|---|---|
| Libro | prestar(), devolver(), estaDisponible() |
| Usuario | actualizarEmail(), puedePedirPrestamo() |
| Prestamo | finalizar(), estaRetrasado() |
Un diagrama UML de clases no necesita incluir cada getter, setter o método auxiliar. Debe mostrar las operaciones relevantes del diseño.
8. Dibujar relaciones
Ahora toca conectar las clases.
En el ejemplo de biblioteca:
Usuario 1 ---------------- 0..* Prestamo
Libro 1 ---------------- 0..* Prestamo
Esto significa:
- Un usuario puede tener cero o muchos préstamos.
- Cada préstamo pertenece a un único usuario.
- Un libro puede aparecer en cero o muchos préstamos a lo largo del tiempo.
- Cada préstamo corresponde a un único libro.
Aquí el diagrama UML de clases empieza a comunicar de verdad el diseño.
9. Añadir multiplicidades
Las multiplicidades aclaran cuántos objetos participan en cada relación.
Sin multiplicidades, la relación puede quedar demasiado abierta. Con multiplicidades, el diseño es más preciso.
Por ejemplo:
Cliente 1 ---------------- 0..* Pedido
Pedido 1 ◆---------------- 1..* LineaPedido
Producto 1 ---------------- 0..* LineaPedido
Esto se entiende así:
- Un cliente puede tener cero o muchos pedidos.
- Cada pedido pertenece a un cliente.
- Un pedido debe tener una o varias líneas de pedido.
- Cada línea de pedido pertenece a un pedido.
- Un producto puede aparecer en muchas líneas de pedido.
- Cada línea de pedido está asociada a un producto.
Esto es mucho más útil que limitarse a dibujar tres líneas sin explicar cantidades.
10. Revisar el diagrama UML de clases
Antes de dar por terminado el diagrama UML de clases, revisa:
- Si las clases tienen nombres claros.
- Si los atributos pertenecen a la clase correcta.
- Si hay métodos innecesarios.
- Si faltan relaciones importantes.
- Si las multiplicidades son coherentes.
- Si hay herencias forzadas.
- Si hay clases con demasiadas responsabilidades.
- Si el diagrama se entiende sin una explicación larga.
Un buen diagrama UML de clases debe poder leerse casi como una explicación visual del sistema.
Ejemplo de diagrama UML de clases: biblioteca
Vamos a construir un ejemplo sencillo de diagrama UML de clases para una biblioteca.
Enunciado
Una biblioteca gestiona libros y usuarios. Cada libro tiene título, autor, ISBN y disponibilidad. Cada usuario tiene nombre, DNI y correo electrónico. Un usuario puede realizar varios préstamos. Cada préstamo guarda la fecha de inicio, la fecha de devolución prevista y si está finalizado. Un préstamo corresponde a un único libro y a un único usuario.
Identificación de clases
Los sustantivos importantes son:
- Biblioteca.
- Libros.
- Usuarios.
- Préstamo.
- Título.
- Autor.
- ISBN.
- Disponibilidad.
- Nombre.
- DNI.
- Correo electrónico.
- Fecha de inicio.
- Fecha de devolución.
Pero no todos son clases.
En este caso, las clases principales serían:
| Clase | Motivo |
|---|---|
| Libro | Tiene datos propios y participa en préstamos |
| Usuario | Tiene datos propios y realiza préstamos |
| Prestamo | Representa una operación importante y conecta Usuario con Libro |
Biblioteca podría ser una clase si queremos modelar el sistema completo, pero para este ejemplo nos centraremos en las entidades principales.
Atributos
| Clase | Atributos |
|---|---|
| Libro | titulo: String, autor: String, isbn: String, disponible: boolean |
| Usuario | nombre: String, dni: String, email: String |
| Prestamo | fechaInicio: LocalDate, fechaPrevistaDevolucion: LocalDate, finalizado: boolean |
Métodos
| Clase | Métodos posibles |
|---|---|
| Libro | prestar(), devolver(), estaDisponible() |
| Usuario | actualizarEmail(), puedePedirPrestamo() |
| Prestamo | finalizar(), estaRetrasado() |
Relaciones y multiplicidades
Las relaciones serían:
Usuario 1 ---------------- 0..* Prestamo
Libro 1 ---------------- 0..* Prestamo
Esto significa que un usuario puede tener varios préstamos, pero cada préstamo pertenece a un solo usuario. También significa que un libro puede aparecer en varios préstamos a lo largo del tiempo, pero cada préstamo corresponde a un único libro.
Representación textual del diagrama UML de clases
+-----------------------------+
| Usuario |
+-----------------------------+
| - nombre: String |
| - dni: String |
| - email: String |
+-----------------------------+
| + actualizarEmail(email): void |
| + puedePedirPrestamo(): boolean |
+-----------------------------+
1
|
| 0..*
+-----------------------------+
| Prestamo |
+-----------------------------+
| - fechaInicio: LocalDate |
| - fechaPrevista: LocalDate |
| - finalizado: boolean |
+-----------------------------+
| + finalizar(): void |
| + estaRetrasado(): boolean |
+-----------------------------+
0..*
|
| 1
+-----------------------------+
| Libro |
+-----------------------------+
| - titulo: String |
| - autor: String |
| - isbn: String |
| - disponible: boolean |
+-----------------------------+
| + prestar(): void |
| + devolver(): void |
| + estaDisponible(): boolean |
+-----------------------------+
Este ejemplo de diagrama UML de clases es sencillo, pero muy útil para entender la lógica: primero detectamos entidades, después datos, luego operaciones y finalmente relaciones.
Versión en PlantUML
PlantUML permite escribir diagramas mediante texto. Esto resulta práctico porque el diagrama puede versionarse junto al código.
@startuml
class Usuario {
- nombre: String
- dni: String
- email: String
+ actualizarEmail(email: String): void
+ puedePedirPrestamo(): boolean
}
class Libro {
- titulo: String
- autor: String
- isbn: String
- disponible: boolean
+ prestar(): void
+ devolver(): void
+ estaDisponible(): boolean
}
class Prestamo {
- fechaInicio: LocalDate
- fechaPrevistaDevolucion: LocalDate
- finalizado: boolean
+ finalizar(): void
+ estaRetrasado(): boolean
}
Usuario "1" -- "0..*" Prestamo
Libro "1" -- "0..*" Prestamo
@enduml
PlantUML no es obligatorio para hacer un diagrama UML de clases, pero es una herramienta muy cómoda cuando queremos trabajar con texto, guardar versiones y compartir el diseño en proyectos técnicos.
Ejemplo de diagrama UML de clases: tienda online
Veamos ahora otro ejemplo clásico: una tienda online.
Enunciado
Una tienda online permite que los clientes realicen pedidos. Cada cliente tiene nombre, email y dirección. Cada pedido tiene fecha, estado y total. Un pedido está formado por una o varias líneas de pedido. Cada línea indica cantidad y precio unitario, y está asociada a un producto. Cada producto tiene nombre, precio y stock.
Clases principales
En este caso, las clases detectadas son:
| Clase | Justificación |
|---|---|
| Cliente | Realiza pedidos y tiene datos propios |
| Pedido | Entidad central del proceso de compra |
| LineaPedido | Detalle interno del pedido |
| Producto | Elemento vendible con precio y stock |
Atributos y métodos
Una posible organización sería:
Cliente
- nombre: String
- email: String
- direccion: String
+ cambiarDireccion(direccion: String): void
Pedido
- fecha: LocalDate
- estado: String
- total: double
+ calcularTotal(): double
+ cambiarEstado(estado: String): void
LineaPedido
- cantidad: int
- precioUnitario: double
+ calcularSubtotal(): double
Producto
- nombre: String
- precio: double
- stock: int
+ reducirStock(cantidad: int): void
Relaciones
Las relaciones principales serían:
Cliente 1 ---------------- 0..* Pedido
Pedido 1 ◆---------------- 1..* LineaPedido
Producto 1 ---------------- 0..* LineaPedido
Aquí aparece una composición entre Pedido y LineaPedido. Tiene sentido porque una línea de pedido depende del pedido. Si se elimina el pedido, normalmente se eliminan sus líneas.
En cambio, Producto no depende de LineaPedido. Un producto puede existir aunque no aparezca en ningún pedido.
Este tipo de razonamiento es justo lo que hace valioso un diagrama UML de clases. No solo dibujamos conexiones: pensamos qué significa cada conexión.
Diagrama en PlantUML
@startuml
class Cliente {
- nombre: String
- email: String
- direccion: String
+ cambiarDireccion(direccion: String): void
}
class Pedido {
- fecha: LocalDate
- estado: String
- total: double
+ calcularTotal(): double
+ cambiarEstado(estado: String): void
}
class LineaPedido {
- cantidad: int
- precioUnitario: double
+ calcularSubtotal(): double
}
class Producto {
- nombre: String
- precio: double
- stock: int
+ reducirStock(cantidad: int): void
}
Cliente "1" -- "0..*" Pedido
Pedido "1" *-- "1..*" LineaPedido
Producto "1" -- "0..*" LineaPedido
@enduml
Código Java inicial equivalente
A partir del diagrama UML de clases podemos crear un primer esqueleto de código Java.
public class Cliente {
private String nombre;
private String email;
private String direccion;
public void cambiarDireccion(String direccion) {
this.direccion = direccion;
}
}
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
public class Pedido {
private LocalDate fecha;
private String estado;
private double total;
private List<LineaPedido> lineas = new ArrayList<>();
public double calcularTotal() {
double suma = 0;
for (LineaPedido linea : lineas) {
suma += linea.calcularSubtotal();
}
total = suma;
return total;
}
public void cambiarEstado(String estado) {
this.estado = estado;
}
}
public class LineaPedido {
private int cantidad;
private double precioUnitario;
private Producto producto;
public double calcularSubtotal() {
return cantidad * precioUnitario;
}
}
public class Producto {
private String nombre;
private double precio;
private int stock;
public void reducirStock(int cantidad) {
if (cantidad > 0 && cantidad <= stock) {
stock -= cantidad;
}
}
}
Este código todavía no es una aplicación completa, pero ya tiene una estructura clara. Y esa es una de las grandes ventajas del diagrama UML de clases: permite pasar del diseño al código con más orden.
Herramientas para crear un diagrama UML de clases
Un diagrama UML de clases puede hacerse de muchas formas. Lo importante no es la herramienta, sino que el diseño sea correcto, legible y útil.
Aun así, hay herramientas que facilitan bastante el trabajo.
PlantUML
PlantUML permite crear diagramas mediante texto. Es especialmente útil cuando queremos guardar el diagrama junto al código o trabajar con control de versiones.
Una ventaja de PlantUML es que el diagrama no depende solo de una imagen. El archivo fuente puede revisarse, modificarse y versionarse como cualquier otro archivo del proyecto.
Para proyectos educativos o técnicos, PlantUML suele ser una opción muy interesante.
Herramientas CASE
Las herramientas CASE permiten crear modelos de software de forma visual. Suelen incluir funciones como:
- Crear clases con atributos y métodos.
- Dibujar relaciones.
- Añadir multiplicidades.
- Organizar paquetes.
- Generar código inicial.
- Obtener diagramas desde código existente.
- Exportar el diagrama como imagen o PDF.
Algunas herramientas posibles son:
| Herramienta | Tipo | Uso habitual |
|---|---|---|
| Papyrus | Plugin/herramienta para Eclipse | Trabajo UML dentro del ecosistema Eclipse |
| PlantUML | Modelado textual | Diagramas versionables como texto |
| StarUML | Herramienta de escritorio | Diseño visual de diagramas |
| diagrams.net | Herramienta gráfica | Diagramas rápidos |
| Visual Paradigm | Herramienta profesional | UML, documentación y diseño |
Plugins en Eclipse
Si trabajas con Eclipse, también puedes instalar plugins de modelado. El proceso habitual suele ser:
- Abrir Eclipse.
- Ir a Help > Eclipse Marketplace.
- Buscar una herramienta de modelado.
- Revisar versión y compatibilidad.
- Instalar el plugin.
- Reiniciar Eclipse si es necesario.
- Crear un proyecto de prueba.
- Comprobar que permite crear diagramas.
En un entorno real, antes de instalar cualquier plugin conviene revisar compatibilidad, licencia y seguridad.
Del diagrama UML de clases al código
La ingeniería directa consiste en generar código a partir de un modelo. En este caso, significa partir de un diagrama UML de clases y obtener una estructura inicial de clases en Java.
Un diagrama UML de clases puede ayudar a generar:
| Elemento del diagrama | Código que puede generarse |
|---|---|
| Clase | Archivo .java |
| Atributos | Variables de instancia |
| Métodos | Firmas de métodos |
| Constructores | Constructores básicos |
| Herencia | extends |
| Interfaces | implements |
| Asociaciones | Referencias o colecciones |
Por ejemplo, a partir de este diseño:
Persona
- nombre: String
- dni: String
+ saludar(): void
Alumno extends Persona
- curso: String
+ matricular(curso: String): void
Podríamos obtener este código:
public class Persona {
private String nombre;
private String dni;
public void saludar() {
// TODO: implementar saludo
}
}
public class Alumno extends Persona {
private String curso;
public void matricular(String curso) {
this.curso = curso;
}
}
Ahora bien, hay que tener clara una limitación: la herramienta puede generar la estructura, pero no sabe exactamente qué debe hacer cada método.
Puede crear saludar(), pero no sabe qué saludo quieres. Puede crear matricular(), pero no conoce todas las reglas de negocio. El diagrama UML de clases ayuda, pero no sustituye el razonamiento del programador.
Esta idea me parece especialmente importante: modelar no es dejar que una herramienta piense por ti. Modelar es ordenar tus propias decisiones antes de convertirlas en código.
Del código al diagrama UML de clases
También puede ocurrir lo contrario: tener código ya escrito y querer obtener un diagrama UML de clases.
Esto se conoce como ingeniería inversa.
La ingeniería inversa consiste en generar o reconstruir un modelo a partir de código existente. Puede ser útil cuando:
- Tenemos un proyecto heredado.
- Falta documentación.
- Queremos entender rápidamente la estructura.
- Necesitamos preparar una refactorización.
- Queremos explicar la arquitectura a otras personas.
- Buscamos detectar clases demasiado grandes o mal relacionadas.
Por ejemplo:
public class Profesor {
private String nombre;
private String email;
public void asignarModulo(Modulo modulo) {
// lógica pendiente
}
}
public class Modulo {
private String nombre;
private int horasSemanales;
}
A partir de este código, una herramienta podría detectar dos clases: Profesor y Modulo. También podría detectar atributos y una relación de uso desde Profesor hacia Modulo.
Sin embargo, el diagrama generado no siempre será perfecto. Puede mostrar relaciones técnicas que no interesan, generar un diagrama demasiado grande o no interpretar bien la intención del diseño.
Por eso, después de generar un diagrama UML de clases desde código, conviene limpiarlo y simplificarlo. El objetivo no es enseñar todas las dependencias internas, sino representar lo importante para entender el sistema.
Buenas prácticas para crear un diagrama UML de clases
Un buen diagrama UML de clases debe ser claro, útil y coherente. No se trata de meter toda la información posible, sino de representar lo necesario para entender el diseño.
Estas son algunas buenas prácticas.
Usa nombres de clase en singular
Es mejor usar:
Libro
Usuario
Pedido
Producto
En lugar de:
Libros
Usuarios
Pedidos
Productos
Una clase representa el molde de un tipo de objeto, por eso suele nombrarse en singular.
Usa nombres claros y relacionados con el dominio
Evita nombres genéricos como:
Datos
Gestor
Sistema
Controlador
A veces pueden tener sentido, pero muchas veces esconden falta de diseño.
Si una clase se llama Sistema y gestiona usuarios, pedidos, productos, pagos y facturas, probablemente tiene demasiadas responsabilidades.
No incluyas todos los métodos imaginables
Un diagrama UML de clases no tiene que ser una copia exacta del código. Incluye los métodos relevantes para entender el diseño.
Por ejemplo, en una clase Pedido puede tener sentido mostrar:
+ calcularTotal(): double
+ cambiarEstado(estado: String): void
Pero quizá no hace falta incluir cada getter y setter.
Añade multiplicidades
Las multiplicidades son fundamentales para interpretar correctamente las relaciones.
No es lo mismo:
Cliente ---------------- Pedido
que:
Cliente 1 ---------------- 0..* Pedido
La segunda versión nos dice mucho más.
Distingue asociación, agregación y composición
No todas las relaciones significan lo mismo.
Si una clase simplemente está relacionada con otra, puede ser asociación. También, si una clase agrupa a otra, pero la parte puede existir por separado, puede ser agregación. Además, si una clase contiene a otra y la parte depende del todo, puede ser composición.
Esta diferencia mejora mucho la calidad del diagrama UML de clases.
No abuses de la herencia
La herencia debe usarse cuando existe una relación clara de tipo “es un”.
Empleado es una Persona puede tener sentido.
Pedido es un Producto no tiene sentido.
Factura es un Email tampoco.
Cuando dudes, pregúntate si la relación representa realmente especialización o si solo estás intentando reutilizar código.
Mantén una responsabilidad clara por clase
Una clase debería tener una responsabilidad principal.
Si una clase hace demasiadas cosas, probablemente conviene dividirla.
Por ejemplo:
| Mal diseño | Problema | Mejor opción |
|---|---|---|
| Sistema gestiona usuarios, pedidos, productos y pagos | Demasiadas responsabilidades | Separar en UsuarioService, Pedido, Producto, Pago |
| Libro se conecta a base de datos | Mezcla dominio y persistencia | Separar Libro y LibroRepository |
| Pedido imprime facturas, calcula stock y envía emails | Responsabilidades no relacionadas | Separar FacturaService, StockService y EmailService |
En consultoría y desarrollo real, esta es una de las cosas que más se agradecen con el tiempo. Un diseño con responsabilidades claras es más fácil de explicar, mantener y modificar.
Mantén el diagrama legible
Un diagrama UML de clases saturado deja de ser útil.
Si el diagrama tiene demasiadas clases, demasiadas relaciones o demasiados detalles, quizá convenga dividirlo en varios diagramas más pequeños.
Un buen diagrama UML de clases debe ayudar a entender, no abrumar.
Errores frecuentes en un diagrama UML de clases
Crear un diagrama UML de clases requiere práctica. Al principio es normal cometer errores, pero muchos se repiten bastante.
Convertir atributos en clases
Uno de los errores más habituales es convertir todos los sustantivos del enunciado en clases.
Por ejemplo, en una biblioteca no tiene sentido crear clases como:
Titulo
Autor
ISBN
Disponibilidad
En un ejercicio básico, eso normalmente serán atributos de Libro.
La solución es preguntarse:
¿Este elemento tiene datos propios, comportamiento propio o ciclo de vida propio?
Si no los tiene, probablemente sea un atributo.
No poner multiplicidades
Si no indicamos multiplicidades, el diagrama queda incompleto.
Una relación sin multiplicidad puede ser demasiado ambigua. No sabemos si un usuario tiene un préstamo, muchos, ninguno o exactamente cinco.
Añadir 1, 0..1, 0.., 1.. o cualquier multiplicidad necesaria mejora mucho la precisión del diagrama UML de clases.
Confundir agregación y composición
Otro error frecuente es confundir agregación y composición.
Recuerda:
- Agregación: la parte puede existir sin el todo.
- Composición: la parte depende del todo.
Equipo y Jugador suele ser agregación.
Pedido y LineaPedido suele ser composición.
La pregunta clave es:
¿La parte tiene sentido si desaparece el todo?
Abusar de la herencia
La herencia mal usada genera diseños rígidos.
No uses herencia solo porque dos clases comparten atributos. Úsala cuando exista una relación clara “es un”.
Si no estás seguro, quizá sea mejor usar composición o una asociación.
No indicar visibilidad
Un diagrama UML de clases sin visibilidad pierde información importante de diseño.
No es lo mismo:
nombre: String
que:
- nombre: String
El segundo caso indica claramente que el atributo es privado.
Incluir detalles irrelevantes
Un diagrama demasiado detallado puede ser tan malo como uno incompleto.
Si incluyes todos los métodos auxiliares, todos los getters, todos los setters y todas las dependencias técnicas, el diagrama puede volverse ilegible.
El objetivo es representar el diseño, no ahogar a quien lo lee.
No actualizar el diagrama cuando cambia el código
Un diagrama UML de clases desactualizado puede ser peor que no tener ninguno, porque genera una falsa sensación de documentación.
Si el código cambia mucho, el diagrama debe revisarse. Por eso herramientas como PlantUML pueden ser útiles: facilitan mantener el diagrama junto al proyecto.
Actividad guiada: diagrama UML de clases para gestión de tareas
Veamos otro ejemplo práctico.
Enunciado
Se quiere crear una aplicación sencilla de gestión de tareas. Cada usuario tiene nombre y correo electrónico. Un usuario puede crear varias tareas. Cada tarea tiene título, descripción, fecha límite, prioridad y estado. Una tarea puede tener varios comentarios. Cada comentario tiene texto, fecha y autor. El estado de una tarea puede ser pendiente, en progreso o finalizada.
Paso 1: identificar clases
Las clases posibles son:
Usuario
Tarea
Comentario
EstadoTarea
EstadoTarea podría representarse como un enum si vamos a trabajar en Java.
Paso 2: identificar atributos
| Clase | Atributos |
|---|---|
| Usuario | nombre: String, email: String |
| Tarea | titulo: String, descripcion: String, fechaLimite: LocalDate, prioridad: int, estado: EstadoTarea |
| Comentario | texto: String, fecha: LocalDate |
| EstadoTarea | PENDIENTE, EN_PROGRESO, FINALIZADA |
Paso 3: identificar relaciones
Las relaciones principales serían:
- Un usuario puede crear cero o muchas tareas.
- Cada tarea tiene un creador.
- Una tarea puede tener cero o muchos comentarios.
- Cada comentario pertenece a una tarea.
- Cada comentario tiene un autor.
- Una tarea tiene un estado.
Solución orientativa en PlantUML
@startuml
class Usuario {
- nombre: String
- email: String
+ cambiarEmail(email: String): void
}
class Tarea {
- titulo: String
- descripcion: String
- fechaLimite: LocalDate
- prioridad: int
- estado: EstadoTarea
+ cambiarEstado(estado: EstadoTarea): void
+ estaVencida(): boolean
}
class Comentario {
- texto: String
- fecha: LocalDate
}
enum EstadoTarea {
PENDIENTE
EN_PROGRESO
FINALIZADA
}
Usuario "1" -- "0..*" Tarea : crea
Tarea "1" *-- "0..*" Comentario
Usuario "1" -- "0..*" Comentario : escribe
Tarea --> EstadoTarea
@enduml
Este ejemplo de diagrama UML de clases es muy útil porque introduce una entidad principal, relaciones uno a muchos, composición y un enum para representar estados.
Cómo saber si tu diagrama UML de clases está bien hecho
Una forma sencilla de revisar un diagrama UML de clases es hacerte estas preguntas:
- ¿Las clases representan entidades importantes del problema?
- ¿Cada clase tiene una responsabilidad clara?
- ¿Los atributos están en la clase correcta?
- ¿Los métodos representan comportamientos relevantes?
- ¿Las relaciones se pueden explicar en lenguaje natural?
- ¿Las multiplicidades tienen sentido?
- ¿Hay alguna herencia forzada?
- ¿La composición está justificada?
- ¿El diagrama se entiende sin explicarlo durante diez minutos?
- ¿El código que saldría de este diagrama tendría una estructura razonable?
Si la mayoría de respuestas son positivas, probablemente tienes un buen punto de partida.
Un diagrama UML de clases no tiene que ser perfecto a la primera. De hecho, lo normal es revisarlo varias veces. Lo importante es que cada revisión mejore la claridad del diseño.
En mi caso, cuando explico este tipo de contenidos, intento transmitir una idea sencilla: un diagrama UML de clases no es un trámite académico. Es una forma de pensar. Si lo haces bien, después programas con más seguridad. Si lo haces mal o lo haces sin pensar, solo tendrás un dibujo que no ayuda demasiado.
Conclusión
Un diagrama UML de clases es una herramienta fundamental para diseñar software orientado a objetos antes de programarlo. Permite representar clases, atributos, métodos y relaciones de forma visual, ayudando a comprender mejor la estructura de una aplicación.
La clave no está solo en saber dibujar símbolos. Lo importante es razonar correctamente:
- Qué clases necesita el sistema.
- Qué responsabilidad tiene cada una.
- Qué datos almacena.
- Qué operaciones realiza.
- Cómo se relaciona con las demás.
- Qué multiplicidades existen.
- Qué relaciones son simples, de herencia, agregación o composición.
Un buen diagrama UML de clases ayuda a evitar errores de diseño, mejora la comunicación y facilita el paso al código. También sirve para documentar sistemas existentes y entender proyectos que ya están desarrollados.
Modelar no es perder tiempo antes de programar. Modelar es ahorrar errores, ordenar ideas y construir software con más criterio.
Preguntas frecuentes sobre diagrama UML de clases
¿Qué es un diagrama UML de clases?
Un diagrama UML de clases es una representación visual de las clases de un sistema, sus atributos, sus métodos y las relaciones que existen entre ellas. Se utiliza para diseñar la estructura de una aplicación orientada a objetos antes de programarla.
¿Para qué sirve un diagrama UML de clases?
Un diagrama UML de clases sirve para entender y planificar la estructura de un sistema. Ayuda a identificar clases, repartir responsabilidades, definir atributos y métodos, representar relaciones y preparar el paso al código.
¿Qué elementos tiene un diagrama UML de clases?
Un diagrama UML de clases suele incluir clases, atributos, métodos y relaciones. También puede mostrar visibilidad, tipos de datos, multiplicidades, herencia, agregación, composición, dependencias e interfaces.
¿Qué diferencia hay entre clase y objeto?
Una clase es una plantilla que define cómo serán ciertos objetos. Un objeto es una instancia concreta creada a partir de una clase. En un diagrama UML de clases normalmente se representan las clases, no los objetos concretos.
¿Qué diferencia hay entre atributo y método?
Un atributo representa un dato o característica de una clase. Un método representa una operación o comportamiento. Por ejemplo, en una clase Libro, titulo puede ser un atributo y prestar() puede ser un método.
¿Qué es la multiplicidad en un diagrama UML de clases?
La multiplicidad indica cuántos objetos de una clase pueden relacionarse con objetos de otra. Por ejemplo, 1, 0..1, 0..* o 1..*. Es fundamental para entender correctamente las relaciones.
¿Cuál es la diferencia entre agregación y composición?
La agregación es una relación todo-parte débil: la parte puede existir sin el todo. La composición es una relación todo-parte fuerte: la parte depende del todo. Equipo y Jugador suele ser agregación; Pedido y LineaPedido suele ser composición.
¿Cuándo conviene usar herencia?
Conviene usar herencia cuando existe una relación clara de tipo “es un”. Por ejemplo, Alumno es una Persona. No conviene usar herencia solo para reutilizar código.
¿Cómo se hace un diagrama UML de clases desde un enunciado?
Primero se lee el enunciado completo. Después se detectan sustantivos importantes, verbos relevantes, posibles clases, atributos, métodos, relaciones y multiplicidades. Finalmente se revisa que el diagrama sea claro y coherente.
¿Qué herramienta puedo usar para crear un diagrama UML de clases?
Puedes usar herramientas visuales como diagrams.net, StarUML o Visual Paradigm, herramientas CASE integradas en IDEs o herramientas textuales como PlantUML. Lo importante es que el diagrama UML de clases sea legible y represente bien el diseño.


