Diagramas de clases UML: relaciones, asociación, agregación y composición
Los diagramas de clases UML son la herramienta más importante para visualizar arquitecturas de software orientadas a objetos. Las relaciones entre clases definen la estructura y el comportamiento del sistema.
Durante mi formación no dediqué suficiente tiempo a estos diagramas, pero son fundamentales para las fases de examen. También resultan invaluables más adelante, tanto para comprender proyectos como para explicarlos a otros.
Si estás cursando una formación como técnico informático, dedica cada día a leer una página “aleatoria” de aquí. Aunque no entiendas todos los temas, siempre aprenderás algo que te será útil hasta el examen.
Fundamentos de los diagramas de clases UML
Representación de clases
Clase simple con interfaz
@startuml
' Clase base con atributos y métodos
class Student {
-matrikelNr: int
-name: String
-semester: int
+getName(): String
+setMatrikelNr(nr: int): void
+studieren(): void
}
' Interface con métodos
interface Lernbar {
+lernen(): void
+pruefungAblegen(): boolean
}
' Interface
interface Lernfaehig {
+{abstract} lernen(fach: String): void
+{abstract} pruefungAblegen(fach: String): boolean
}
' Student implementa Lernbar
Student ..|> Lernbar
@enduml
Modificadores de visibilidad
| Símbolo | Significado | Descripción |
|---|---|---|
+ | public | Accesible desde cualquier lugar |
- | private | Accesible solo dentro de la clase |
# | protected | Accesible dentro de la clase y subclases |
~ | package | Accesible solo dentro del paquete |
Tipos de relaciones en UML
1. Asociación
Las asociaciones describen relaciones estructurales entre clases.
@startuml
class Student {
-name: String
+getName(): String
}
class Kurs {
-titel: String
-credits: int
+getTitel(): String
}
' Asociación simple
Student "1" -- "n" Kurs : belegt >
' Asociación con roles y atributos
Student "1" -- "*" Note : \
note : hat
note : noteWert: double
note : datum: Date
' Asociación dirigida
Student "1" -> "*" Projekt : leitet >
@enduml
Implementación en Java de asociaciones
public class AssociationExamples {
// Asociación muchos-a-muchos
public class Student {
private String name;
private List<Kurs> belegteKurse = new ArrayList<>();
private List<Note> noten = new ArrayList<>();
private List<Projekt> geleiteteProjekte = new ArrayList<>();
public void belegeKurs(Kurs kurs) {
belegteKurse.add(kurs);
kurs.addStudent(this);
}
public void addNote(Note note) {
noten.add(note);
}
public void leiteProjekt(Projekt projekt) {
geleiteteProjekte.add(projekt);
}
// Getter y Setter
public String getName() { return name; }
public List<Kurs> getBelegteKurse() { return new ArrayList<>(belegteKurse); }
}
public class Kurs {
private String titel;
private int credits;
private List<Student> studenten = new ArrayList<>();
public void addStudent(Student student) {
studenten.add(student);
}
// Getter y Setter
public String getTitel() { return titel; }
public List<Student> getStudenten() { return new ArrayList<>(studenten); }
}
public class Note {
private double noteWert;
private Date datum;
private Student student;
public Note(double noteWert, Date datum, Student student) {
this.noteWert = noteWert;
this.datum = datum;
this.student = student;
student.addNote(this);
}
// Getter y Setter
public double getNoteWert() { return noteWert; }
public Date getDatum() { return datum; }
}
public class Projekt {
private String name;
private Student leiter;
private List<Student> mitglieder = new ArrayList<>();
public Projekt(String name, Student leiter) {
this.name = name;
this.leiter = leiter;
leiter.leiteProjekt(this);
}
// Getter y Setter
public String getName() { return name; }
public Student getLeiter() { return leiter; }
}
}
2. Agregación
La agregación es una forma especial de asociación en la que un objeto componente puede existir independientemente del todo.
@startuml
class Abteilung {
-name: String
+getName(): String
}
class Mitarbeiter {
-name: String
-position: String
+getName(): String
}
' Agregación (rombo vacío)
Abteilung o-- "*" Mitarbeiter : hat >
class Universitaet {
-name: String
+getName(): String
}
class Fakultaet {
-name: String
+getName(): String
}
' Agregación
Universitaet o-- "*" Fakultaet : besitzt >
@enduml
Implementación en Java de agregación
public class AggregationExamples {
// Abteilung puede existir sin Mitarbeiter
public class Abteilung {
private String name;
private List<Mitarbeiter> mitarbeiter = new ArrayList<>();
public Abteilung(String name) {
this.name = name;
}
public void addMitarbeiter(Mitarbeiter mitarbeiter) {
this.mitarbeiter.add(mitarbeiter);
}
public void removeMitarbeiter(Mitarbeiter mitarbeiter) {
this.mitarbeiter.remove(mitarbeiter);
}
// Mitarbeiter pueden existir sin Abteilung
public String getName() { return name; }
public List<Mitarbeiter> getMitarbeiter() {
return new ArrayList<>(mitarbeiter);
}
}
public class Mitarbeiter {
private String name;
private String position;
private Abteilung abteilung; // Opcional
public Mitarbeiter(String name, String position) {
this.name = name;
this.position = position;
}
public void setAbteilung(Abteilung abteilung) {
this.abteilung = abteilung;
if (abteilung != null) {
abteilung.addMitarbeiter(this);
}
}
public void removeAbteilung() {
if (abteilung != null) {
abteilung.removeMitarbeiter(this);
this.abteilung = null;
}
}
// Mitarbeiter existen independientemente de Abteilung
public String getName() { return name; }
public Abteilung getAbteilung() { return abteilung; }
}
}
3. Composición
La composición es una forma más fuerte de agregación, donde el objeto componente no puede existir sin el todo.
@startuml
class Auto {
-marke: String
-modell: String
+fahren(): void
}
class Motor {
-leistung: int
-typ: String
+starten(): void
}
class Reifen {
-groesse: int
-druck: double
+aufpumpen(): void
}
' Composición (rombo lleno)
Auto *-- "1" Motor : hat >
Auto *-- "4" Reifen : hat >
class Haus {
-adresse: String
+wohnen(): void
}
class Zimmer {
-flaeche: double
-typ: String
+reinigen(): void
}
' Composición
Haus *-- "*" Zimmer : hat >
@enduml
Implementación en Java de Composición
public class CompositionExamples {
// El motor solo existe dentro del auto
public class Auto {
private String marque;
private String modelo;
private Motor motor; // Existe solo con el auto
private List<Llanta> llantas = new ArrayList<>(); // Existen solo con el auto
public Auto(String marque, String modelo) {
this.marque = marque;
this.modelo = modelo;
this.motor = new Motor(200, "Diesel"); // El motor se crea dentro del auto
this.llantas.addAll(Arrays.asList(
new Llanta(205), new Llanta(205),
new Llanta(205), new Llanta(205)
));
}
public void conducir() {
motor.iniciar();
System.out.println(marque + " " + modelo + " está circulando");
}
// Getters
public String getMarque() { return marque; }
public Motor getMotor() { return motor; }
public List<Llanta> getLlantas() {
return new ArrayList<>(llantas);
}
}
public class Motor {
private int potencia;
private String tipo;
// Constructor privado - solo invocable desde Auto
private Motor(int potencia, String tipo) {
this.potencia = potencia;
this.tipo = tipo;
}
public void iniciar() {
System.out.println("Motor (" + tipo + ", " + potencia + " CV) iniciado");
}
// Getters
public int getPotencia() { return potencia; }
public String getTipo() { return tipo; }
}
public class Llanta {
private int tamaño;
private double presion;
// Constructor privado
private Llanta(int tamaño) {
this.tamaño = tamaño;
this.presion = 2.5; // Presión estándar
}
public void inflar(double presion) {
this.presion = presion;
System.out.println("Llanta inflada a " + presion + " bar");
}
// Getters
public int getTamaño() { return tamaño; }
public double getPresion() { return presion; }
}
}
4. Dependencia (Dependency)
Las dependencias representan relaciones de uso entre clases.
@startuml
class Pedido {
-fecha: Date
-totalSuma: double
+calcularTotalSuma(): double
}
class CalculadoraPrecios {
+calcularPrecio(productos: List<Producto>): double
+calcularDescuento(monto: double, descuento: double): double
}
' Dependencia (línea punteada)
Pedido ..> CalculadoraPrecios : utiliza >
class Logger {
+logInfo(mensaje: String): void
+logError(mensaje: String): void
}
class ServicioBaseDatos {
+guardar(datos: Object): void
+cargar(id: String): Object
}
Pedido ..> Logger : registra >
Pedido ..> ServicioBaseDatos : persiste >
@enduml
Implementación en Java de Dependencias
public class DependencyExamples {
public class Pedido {
private Date fecha;
private List<Producto> productos = new ArrayList<>();
private CalculadoraPrecios calculadoraPrecios;
private Logger logger;
private ServicioBaseDatos servicioBaseDatos;
public Pedido(CalculadoraPrecios calculadoraPrecios, Logger logger,
ServicioBaseDatos servicioBaseDatos) {
this.fecha = new Date();
this.calculadoraPrecios = calculadoraPrecios;
this.logger = logger;
this.servicioBaseDatos = servicioBaseDatos;
}
public void agregarProducto(Producto producto) {
productos.add(producto);
logger.logInfo("Producto añadido: " + producto.getNombre());
}
public double calcularTotalSuma() {
double suma = calculadoraPrecios.calcularPrecio(productos);
double descuento = calculadoraPrecios.calcularDescuento(suma, 0.1);
logger.logInfo("Total calculado: " + (suma - descuento));
return suma - descuento;
}
public void guardar() {
try {
servicioBaseDatos.guardar(this);
logger.logInfo("Pedido guardado");
} catch (Exception e) {
logger.logError("Error al guardar: " + e.getMessage());
}
}
// Getters
public Date getFecha() { return fecha; }
public List<Producto> getProductos() { return new ArrayList<>(productos); }
}
// Clases de dependencia
public class CalculadoraPrecios {
public double calcularPrecio(List<Producto> productos) {
return productos.stream()
.mapToDouble(Producto::getPrecio)
.sum();
}
public double calcularDescuento(double monto, double porcentajeDescuento) {
return monto * porcentajeDescuento;
}
}
public class Logger {
public void logInfo(String mensaje) {
System.out.println("INFO: " + mensaje);
}
public void logError(String mensaje) {
System.err.println("ERROR: " + mensaje);
}
}
public class ServicioBaseDatos {
public void guardar(Object datos) {
System.out.println("Datos guardados: " + datos);
}
public Object cargar(String id) {
System.out.println("Datos cargados: " + id);
return null;
}
}
public class Producto {
private String nombre;
private double precio;
public Producto(String nombre, double precio) {
this.nombre = nombre;
this.precio = precio;
}
// Getters
public String getNombre() { return nombre; }
public double getPrecio() { return precio; }
}
}
5. Herencia (Inheritance)
La herencia expresa relaciones “es-un” entre clases.
@startuml
' Clase base abstracta
abstract class Vehiculo {
#marque: String
#anio: int
+{abstract} conducir(): void
+{abstract} frenar(): void
+obtenerInfo(): String
}
' Clases concretas derivadas
class Auto extends Vehiculo {
-cantidadPuertas: int
+conducir(): void
+frenar(): void
+abrir(): void
}
class Motocicleta extends Vehiculo {
-tipo: String
+conducir(): void
+frenar(): void
+girar(): void
}
class Bicicleta extends Vehiculo {
-cantidadVelocidades: int
+conducir(): void
+frenar(): void
+cambiarMarcha(): void
}
@enduml
Implementación en Java de Herencia
public class InheritanceExamples {
// Clase base abstracta
public abstract class Vehiculo {
protected String marque;
protected int anio;
public Vehiculo(String marque, int anio) {
this.marque = marque;
this.anio = anio;
}
// Métodos abstractos
public abstract void conducir();
public abstract void frenar();
// Método concreto
public String obtenerInfo() {
return marque + " del " + anio;
}
// Getters
public String getMarque() { return marque; }
public int getAnio() { return anio; }
}
// Clases concretas derivadas
public class Auto extends Vehiculo {
private int cantidadPuertas;
public Auto(String marque, int anio, int cantidadPuertas) {
super(marque, anio);
this.cantidadPuertas = cantidadPuertas;
}
@Override
public void conducir() {
System.out.println("Auto " + marque + " circula con " + cantidadPuertas + " puertas");
}
@Override
public void frenar() {
System.out.println("Auto frena con discos de freno");
}
public void abrir() {
System.out.println("La puerta del auto se abre");
}
@Override
public String obtenerInfo() {
return super.obtenerInfo() + " (Auto, " + cantidadPuertas + " puertas)";
}
}
public class Motocicleta extends Vehiculo {
private String tipo;
public Motocicleta(String marque, int anio, String tipo) {
super(marque, anio);
this.tipo = tipo;
}
@Override
public void conducir() {
System.out.println("Motocicleta " + marque + " (" + tipo + ") circula");
}
@Override
public void frenar() {
System.out.println("Motocicleta frena con frenos de disco");
}
public void girar() {
System.out.println("La motocicleta gira");
}
@Override
public String obtenerInfo() {
return super.obtenerInfo() + " (Motocicleta, " + tipo + ")";
}
}
public class Bicicleta extends Vehiculo {
private int cantidadVelocidades;
public Bicicleta(String marque, int anio, int cantidadVelocidades) {
super(marque, anio);
this.cantidadVelocidades = cantidadVelocidades;
}
@Override
public void conducir() {
System.out.println("Bicicleta " + marque + " se pedalea");
}
@Override
public void frenar() {
System.out.println("Bicicleta frena con frenos de llanta");
}
public void cambiarMarcha() {
System.out.println("Bicicleta cambia de marcha");
}
@Override
public String obtenerInfo() {
return super.obtenerInfo() + " (Bicicleta, " + cantidadVelocidades + " velocidades)";
}
}
}
6. Implementación
La implementación muestra la relación entre interfaces y las clases que las implementan.
@startuml
' Interfaces
interface Fliegend {
+{abstract} fliegen(): void
+{abstract} landen(): void
}
interface Schwimmend {
+{abstract} schwimmen(): void
+{abstract} tauchen(): void
}
' Klasse implementiert ein Interface
class Vogel implements Fliegend {
-art: String
+fliegen(): void
+landen(): void
}
' Klasse implementiert mehrere Interfaces
class Ente implements Fliegend, Schwimmend {
-rasse: String
+fliegen(): void
+landen(): void
+schwimmen(): void
+tauchen(): void
}
' Abstrakte Klasse implementiert Interface
abstract class Wasserlebewesen implements Schwimmend {
-lebensraum: String
+{abstract} atmen(): void
+schwimmen(): void
+tauchen(): void
}
class Fisch extends Wasserlebewesen {
-art: String
+atmen(): void
}
@enduml
Implementación de interfaces en Java
public class InterfaceExamples {
// Interfaces
public interface Fliegend {
void fliegen();
void landen();
}
public interface Schwimmend {
void schwimmen();
void tauchen();
}
// Einfache Implementierung
public class Vogel implements Fliegend {
private String art;
public Vogel(String art) {
this.art = art;
}
@Override
public void fliegen() {
System.out.println(art + " fliegt in die Luft");
}
@Override
public void landen() {
System.out.println(art + " landet sanft");
}
public String getArt() { return art; }
}
// Mehrere Interfaces implementieren
public class Ente implements Fliegend, Schwimmend {
private String rasse;
public Ente(String rasse) {
this.rasse = rasse;
}
@Override
public void fliegen() {
System.out.println(rasse + " fliegt in Formation");
}
@Override
public void landen() {
System.out.println(rasse + " landet auf dem Wasser");
}
@Override
public void schwimmen() {
System.out.println(rasse + " schwimmt elegant");
}
@Override
public void tauchen() {
System.out.println(rasse + " taucht nach Fischen");
}
public String getRasse() { return rasse; }
}
// Abstrakte Klasse mit Interface
public abstract class Wasserlebewesen implements Schwimmend {
protected String lebensraum;
public Wasserlebewesen(String lebensraum) {
this.lebensraum = lebensraum;
}
public abstract void atmen();
@Override
public void schwimmen() {
System.out.println("Schwimmt im " + lebensraum);
}
@Override
public void tauchen() {
System.out.println("Taucht tief im " + lebensraum);
}
public String getLebensraum() { return lebensraum; }
}
// Konkrete Unterklasse
public class Fisch extends Wasserlebewesen {
private String art;
public Fisch(String art, String lebensraum) {
super(lebensraum);
this.art = art;
}
@Override
public void atmen() {
System.out.println(art + " atmet mit Kiemen im Wasser");
}
public String getArt() { return art; }
}
}
Diagramas de clases complejos
Ejemplo completo: Sistema universitario
@startuml
' Abstrakte Basisklassen
abstract class Person {
#name: String
#alter: int
+{abstract} getInfo(): String
+geburtstagFeiern(): void
}
abstract class Mitarbeiter extends Person {
#mitarbeiterNr: String
#gehalt: double
+{abstract} arbeiten(): void
+gehaltErhoehen(prozent: double): void
}
' Konkrete Klassen
class Student extends Person {
-matrikelNr: String
-fach: String
+studieren(): void
+pruefungAblegen(): boolean
+getInfo(): String
}
class Dozent extends Mitarbeiter {
-fachbereich: String
+arbeiten(): void
+vorlesungHalten(): void
+klausurKorrigieren(): void
+getInfo(): String
}
class Verwaltungsmitarbeiter extends Mitarbeiter {
-abteilung: String
+arbeiten(): void
+dokumenteVerwalten(): void
+getInfo(): String
}
' Weitere Klassen
class Kurs {
-titel: String
-credits: int
-dozent: Dozent
+getTitel(): String
+setDozent(dozent: Dozent): void
}
class Pruefung {
-datum: Date
-note: double
-student: Student
-kurs: Kurs
+noteEintragen(note: double): void
+bestanden(): boolean
}
class Raum {
-nummer: String
-kapazitaet: int
+gebucht(): boolean
+reservieren(): void
}
' Beziehungen
Person <|-- Student
Person <|-- Mitarbeiter
Mitarbeiter <|-- Dozent
Mitarbeiter <|-- Verwaltungsmitarbeiter
Dozent "1" -- "*" Kurs : unterrichtet >
Student "n" -- "n" Kurs : belegt >
Student "n" -- "n" Pruefung : schreibt >
Kurs "1" -- "n" Pruefung : hat >
Kurs "n" -- "1" Raum : findetStattIn >
' Aggregation
Fakultaet o-- "*" Dozent : beschaeftigt >
Fakultaet o-- "*" Student : immatrikuliert >
' Komposition
Universitaet *-- "*" Fakultaet : besitzt >
@enduml
Implementación Java del sistema universitario
public class UniversitySystem {
// Abstrakte Basisklasse
public abstract class Person {
protected String name;
protected int alter;
public Person(String name, int alter) {
this.name = name;
this.alter = alter;
}
public abstract String getInfo();
public void geburtstagFeiern() {
alter++;
System.out.println("Herzlichen Glückwunsch zum " + alter + ". Geburtstag, " + name + "!");
}
// Getter
public String getName() { return name; }
public int getAlter() { return alter; }
}
// Abstrakte Mitarbeiter-Klasse
public abstract class Mitarbeiter extends Person {
protected String mitarbeiterNr;
protected double gehalt;
public Mitarbeiter(String name, int alter, String mitarbeiterNr, double gehalt) {
super(name, alter);
this.mitarbeiterNr = mitarbeiterNr;
this.gehalt = gehalt;
}
public abstract void arbeiten();
public void gehaltErhoehen(double prozent) {
gehalt = gehalt * (1 + prozent / 100);
System.out.println("Gehalt erhöht auf: " + gehalt);
}
// Getter
public String getMitarbeiterNr() { return mitarbeiterNr; }
public double getGehalt() { return gehalt; }
}
// Konkrete Klassen
public class Student extends Person {
private String matrikelNr;
private String fach;
private List<Kurs> belegteKurse = new ArrayList<>();
private List<Pruefung> pruefungen = new ArrayList<>();
public Student(String name, int alter, String matrikelNr, String fach) {
super(name, alter);
this.matrikelNr = matrikelNr;
this.fach = fach;
}
@Override
public String getInfo() {
return "Student: " + name + " (" + matrikelNr + "), Fach: " + fach;
}
public void studieren() {
System.out.println(name + " studiert " + fach);
}
public boolean pruefungAblegen() {
return belegteKurse.stream().anyMatch(kurs ->
pruefungen.stream()
.anyMatch(pruefung ->
pruefung.getKurs().equals(kurs) && pruefung.bestanden()
)
);
}
public void belegeKurs(Kurs kurs) {
belegteKurse.add(kurs);
kurs.addStudent(this);
}
public void addPruefung(Pruefung pruefung) {
pruefungen.add(pruefung);
}
// Getter
public String getMatrikelNr() { return matrikelNr; }
public String getFach() { return fach; }
public List<Kurs> getBelegteKurse() { return new ArrayList<>(belegteKurse); }
}
public class Dozent extends Mitarbeiter {
private String fachbereich;
private List<Kurs> unterrichteteKurse = new ArrayList<>();
public Dozent(String name, int alter, String mitarbeiterNr, double gehalt, String fachbereich) {
super(name, alter, mitarbeiterNr, gehalt);
this.fachbereich = fachbereich;
}
@Override
public String getInfo() {
return "Dozent: " + name + " (" + mitarbeiterNr + "), Fachbereich: " + fachbereich;
}
@Override
public void arbeiten() {
System.out.println(name + " arbeitet im Fachbereich " + fachbereich);
}
public void vorlesungHalten(Kurs kurs) {
unterrichteteKurse.add(kurs);
kurs.setDozent(this);
System.out.println(name + " hält Vorlesung: " + kurs.getTitel());
}
public void klausurKorrigieren(Pruefung pruefung) {
System.out.println(name + " korrigiert Klausur für " + pruefung.getStudent().getName());
pruefung.noteEintragen(Math.random() * 4 + 1); // Zufallsnote 1-5
}
// Getter
public String getFachbereich() { return fachbereich; }
public List<Kurs> getUnterrichteteKurse() { return new ArrayList<>(unterrichteteKurse); }
}
public class Verwaltungsmitarbeiter extends Mitarbeiter {
private String abteilung;
public Verwaltungsmitarbeiter(String name, int alter, String mitarbeiterNr, double gehalt, String abteilung) {
super(name, alter, mitarbeiterNr, gehalt);
this.abteilung = abteilung;
}
@Override
public String getInfo() {
return "Verwaltungsmitarbeiter: " + name + " (" + mitarbeiterNr + "), Abteilung: " + abteilung;
}
@Override
public void arbeiten() {
System.out.println(name + " arbeitet in der Verwaltung: " + abteilung);
}
public void dokumenteVerwalten() {
System.out.println(name + " verwaltet Dokumente in " + abteilung);
}
// Getter
public String getAbteilung() { return abteilung; }
}
// Weitere Klassen
public class Kurs {
private String titel;
private int credits;
private Dozent dozent;
private List<Student> studenten = new ArrayList<>();
private List<Pruefung> pruefungen = new ArrayList<>();
public Kurs(String titel, int credits) {
this.titel = titel;
this.credits = credits;
}
public void addStudent(Student student) {
studenten.add(student);
}
public void setDozent(Dozent dozent) {
this.dozent = dozent;
}
public void addPruefung(Pruefung pruefung) {
pruefungen.add(pruefung);
}
// Getter
public String getTitel() { return titel; }
public int getCredits() { return credits; }
public Dozent getDozent() { return dozent; }
public List<Student> getStudenten() { return new ArrayList<>(studenten); }
}
public class Pruefung {
private Date datum;
private double note;
private Student student;
private Kurs kurs;
public Pruefung(Student student, Kurs kurs) {
this.student = student;
this.kurs = kurs;
this.datum = new Date();
this.note = 0.0; // Noch nicht benotet
}
public void noteEintragen(double note) {
this.note = note;
student.addPruefung(this);
kurs.addPruefung(this);
}
public boolean bestanden() {
return note > 0 && note <= 4.0;
}
// Getter
public Date getDatum() { return datum; }
public double getNote() { return note; }
public Student getStudent() { return student; }
public Kurs getKurs() { return kurs; }
}
public class Raum {
private String nummer;
private int kapazitaet;
private boolean gebucht = false;
public Raum(String nummer, int kapazitaet) {
this.nummer = nummer;
this.kapazitaet = kapazitaet;
}
public void reservieren() {
gebucht = true;
System.out.println("Raum " + nummer + " reserviert");
}
public boolean istGebucht() {
return gebucht;
}
// Getter
public String getNummer() { return nummer; }
public int getKapazitaet() { return kapazitaet; }
}
}
Prácticas recomendadas para diagramas de clases UML
1. Convenciones de nomenclatura consistentes
// Convenciones de nomenclatura adecuadas
public class NamingConventions {
// Clases: sustantivos, PascalCase
public class StudentManagementSystem {}
// Métodos: verbos, camelCase
public void calculateGrade() {}
public void validateInput() {}
// Variables: camelCase, descriptivas
private List<Student> enrolledStudents;
private double averageGrade;
// Constantes: UPPER_CASE
public static final int MAX_STUDENTS_PER_COURSE = 100;
// Interfaces: adjetivos o capacidades, PascalCase
public interface Printable {}
public interface Serializable {}
public interface StudentRepository {}
}
2. Separación de responsabilidades
@startuml
' División clara de responsabilidades
class UserRepository {
+save(user: User): void
+findById(id: String): User
+findAll(): List<User>
+delete(id: String): void
}
class UserService {
-userRepository: UserRepository
-emailService: EmailService
+registerUser(userData: UserData): User
+authenticateUser(username: String, password: String): boolean
+updateUserProfile(userId: String, profile: UserProfile): void
}
class EmailService {
+sendWelcomeEmail(user: User): void
+sendPasswordReset(user: User): void
}
' Dependencias claras
UserService ..> UserRepository : utiliza >
UserService ..> EmailService : utiliza >
@enduml
3. Evitar dependencias cíclicas
@startuml
' Incorrecto: Dependencias cíclicas
class A {
-b: B
+doSomething(): void
}
class B {
-c: C
+doSomethingElse(): void
}
class C {
-a: A
+doAnotherThing(): void
}
A --> B
B --> C
C --> A ' ¡Ciclo!
' Correcto: Sin ciclos
class GoodA {
-service: CommonService
+doSomething(): void
}
class GoodB {
-service: CommonService
+doSomethingElse(): void
}
class GoodC {
-service: CommonService
+doAnotherThing(): void
}
class CommonService {
+sharedOperation(): void
}
GoodA --> CommonService
GoodB --> CommonService
GoodC --> CommonService
@enduml
Conceptos avanzados y patrones
1. Multiplicidad y cardinalidad
La multiplicidad indica cuántas instancias de una clase pueden participar en una relación.
@startuml
' Diferentes multiplicidades
class Cliente {
-clienteNr: String
}
class Pedido {
-pedidoNr: String
}
class Producto {
-productoId: String
}
' Relación uno a muchos
Cliente "1" -- "0..*" Pedido : realiza >
' Relación muchos a muchos a través de clase de asociación
Pedido "n" -- "m" LineaPedido : contiene >
Producto "1" -- "0..*" LineaPedido : incluye >
class LineaPedido {
-cantidad: int
-precioUnitario: double
+getPrecioTotal(): double
}
@enduml
Notaciones de multiplicidad:
1exactamente una0..1cero o una*muchas (cero o más)1..*al menos una2..5entre 2 y 50,1cero o una (notación alternativa)
2. Asociaciones cualificadas
Las asociaciones cualificadas utilizan una clave para identificar objetos de forma única.
@startuml
class Map {
-entries: Map<String, Object>
+put(key: String, value: Object): void
+get(key: String): Object
}
class String {
+length(): int
+charAt(index: int): char
}
class Object {
+toString(): String
}
' Asociación cualificada
Map "1" -- "*" Object : entries >
{key} String
@enduml
3. Clases abstractas e interfaces
Las clases abstractas pueden contener métodos abstractos y no pueden instanciarse.
@startuml
' Clase abstracta
abstract class Shape {
#color: String
#position: Point
+{abstract} draw(): void
+{abstract} getArea(): double
+move(x: double, y: double): void
}
' Interface
interface Movable {
+{abstract} move(dx: double, dy: double): void
+{abstract} getPosition(): Point
}
interface Resizable {
+{abstract} scale(factor: double): void
+{abstract} getSize(): Size
}
' Clases concretas
class Circle extends Shape implements Movable, Resizable {
-radius: double
+draw(): void
+getArea(): double
+move(dx: double, dy: double): void
+scale(factor: double): void
+getSize(): Size
}
class Rectangle extends Shape implements Movable, Resizable {
-width: double
-height: double
+draw(): void
+getArea(): double
+move(dx: double, dy: double): void
+scale(factor: double): void
+getSize(): Size
}
@enduml
4. Design Patterns en UML
Patrón Singleton
@startuml
class Singleton {
-instance: Singleton
-Singleton()
+getInstance(): Singleton
+doSomething(): void
}
note top of Singleton
Constructor Privado
Instancia Estática
Punto de Acceso Global
end note
@enduml
Patrón Observer
@startuml
interface Observer {
+{abstract} update(subject: Subject): void
}
interface Subject {
+{abstract} attach(observer: Observer): void
+{abstract} detach(observer: Observer): void
+{abstract} notify(): void
}
class ConcreteObserver implements Observer {
-state: String
+update(subject: Subject): void
}
class ConcreteSubject implements Subject {
-state: String
-observers: List<Observer>
+attach(observer: Observer): void
+detach(observer: Observer): void
+notify(): void
+getState(): String
+setState(state: String): void
}
ConcreteSubject --> ConcreteObserver : notifica >
@enduml
Patrón Factory
@startuml
interface Product {
+{abstract} operation(): void
}
class ConcreteProductA implements Product {
+operation(): void
}
class ConcreteProductB implements Product {
+operation(): void
}
interface Factory {
+{abstract} createProduct(type: String): Product
}
class ConcreteFactory implements Factory {
+createProduct(type: String): Product
}
ConcreteFactory ..> ConcreteProductA : crea >
ConcreteFactory ..> ConcreteProductB : crea >
@enduml
5. Paquetes y espacios de nombres
Los paquetes agrupan clases relacionadas y proporcionan espacios de nombres.
@startuml
package "com.example.model" {
class User {
-id: String
-name: String
}
class Product {
-id: String
-name: String
-price: double
}
}
package "com.example.service" {
class UserService {
-userRepository: UserRepository
+createUser(userData: UserData): User
+findUser(id: String): User
}
class ProductService {
-productRepository: ProductRepository
+createProduct(productData: ProductData): Product
}
}
package "com.example.repository" {
interface UserRepository {
+{abstract} save(user: User): void
+{abstract} findById(id: String): User
}
interface ProductRepository {
+{abstract} save(product: Product): void
+{abstract} findById(id: String): Product
}
}
' Dependencias entre paquetes
com.example.service ..> com.example.model : utiliza >
com.example.service ..> com.example.repository : utiliza >
@enduml
6. Estereotipos y notaciones
Los estereotipos extienden la notación UML para propósitos específicos.
@startuml
' Estereotipos
class DatabaseConnection <<utility>> {
+{static} getConnection(): Connection
+{static} closeConnection(conn: Connection): void
}
class User <<entity>> {
-id: String
-name: String
}
class UserController <<controller>> {
-userService: UserService
+createUser(): void
+updateUser(): void
}
class UserService <<service>> {
-userRepository: UserRepository
+createUser(userData: UserData): User
}
' Notaciones especiales
class API <<REST>> {
+{GET} /users: List<User>
+{POST} /users: User
+{PUT} /users/{id}: User
}
@enduml
Conceptos relevantes para el examen
Tipos de relaciones importantes
| Tipo de relación | Símbolo | Significado | Duración |
|------------------|---------|------------|----------|
| Asociación | ---- | Relación estructural | Independiente |
| Agregación | o-- | "tiene-un" (parcial) | Independiente |
| Composición | *-- | "tiene-un" (completo) | Dependiente |
| Dependencia | ..> | "utiliza" | Temporal |
| Herencia | < |-- | "es-un" | Permanente |
| Implementación | ..|> | "implementa" | Permanente |
Principios SOLID en UML
Single Responsibility Principle (SRP)
@startuml
' Mal: Una clase con muchas responsabilidades
class BadUserManager {
-userData: UserData
+saveUser(): void
+sendWelcomeEmail(): void
+logActivity(): void
+validateInput(): void
}
' Bien: Responsabilidades separadas
class UserRepository {
+save(user: User): void
+findById(id: String): User
}
class EmailService {
+sendWelcomeEmail(user: User): void
}
class LoggingService {
+logActivity(message: String): void
}
class UserValidator {
+validateInput(userData: UserData): boolean
}
class UserService {
-userRepository: UserRepository
-emailService: EmailService
-loggingService: LoggingService
-validator: UserValidator
}
UserService ..> UserRepository : uses >
UserService ..> EmailService : uses >
UserService ..> LoggingService : uses >
UserService ..> UserValidator : uses >
@enduml
Open/Closed Principle (OCP)
@startuml
' Abierto para extensión, cerrado para modificación
interface Shape {
+{abstract} draw(): void
+{abstract} getArea(): double
}
class Circle implements Shape {
-radius: double
+draw(): void
+getArea(): double
}
class Rectangle implements Shape {
-width: double
-height: double
+draw(): void
+getArea(): double
}
class Triangle implements Shape {
-base: double
-height: double
+draw(): void
+getArea(): double
}
' Se pueden agregar nuevas formas sin modificar el código existente
@enduml
Liskov Substitution Principle (LSP)
@startuml
class Bird {
+{abstract} fly(): void
+{abstract} makeSound(): void
}
class Sparrow extends Bird {
+fly(): void
+makeSound(): void
}
class Penguin extends Bird {
+fly(): void ' ¡Viola LSP!
+makeSound(): void
}
' Mejor: Separar interfaces
interface FlyingBird {
+{abstract} fly(): void
}
interface Bird {
+{abstract} makeSound(): void
}
class GoodSparrow implements FlyingBird, Bird {
+fly(): void
+makeSound(): void
}
class GoodPenguin implements Bird {
+makeSound(): void
}
@enduml
Anti-patrones y cómo evitarlos
Anti-patrón God Object
@startuml
' Anti-patrón: God Object
class UserManager {
-userData: UserData
-orderData: OrderData
-productData: ProductData
-paymentData: PaymentData
-reportData: ReportData
+createUser(): void
+deleteUser(): void
+createOrder(): void
+cancelOrder(): void
+addProduct(): void
+removeProduct(): void
+processPayment(): void
+refundPayment(): void
+generateReport(): void
+exportData(): void
}
' Mejor: Clases especializadas
class UserService {
+createUser(): void
+deleteUser(): void
}
class OrderService {
+createOrder(): void
+cancelOrder(): void
}
class ProductService {
+addProduct(): void
+removeProduct(): void
}
class PaymentService {
+processPayment(): void
+refundPayment(): void
}
class ReportService {
+generateReport(): void
+exportData(): void
}
@enduml
Anti-patrón Circular Dependency
@startuml
' Anti-patrón: Dependencias cíclicas
class ServiceA {
-serviceB: ServiceB
+doA(): void
}
class ServiceB {
-serviceC: ServiceC
+doB(): void
}
class ServiceC {
-serviceA: ServiceA
+doC(): void
}
ServiceA --> ServiceB
ServiceB --> ServiceC
ServiceC --> ServiceA ' ¡Ciclo!
' Solución: Dependency Injection con Interface
interface ServiceAInterface {
+{abstract} doA(): void
}
interface ServiceBInterface {
+{abstract} doB(): void
}
interface ServiceCInterface {
+{abstract} doC(): void
}
class GoodServiceA implements ServiceAInterface {
-serviceB: ServiceBInterface
+doA(): void
}
class GoodServiceB implements ServiceBInterface {
-serviceC: ServiceCInterface
+doB(): void
}
class GoodServiceC implements ServiceCInterface {
-serviceA: ServiceAInterface
+doC(): void
}
GoodServiceA ..> ServiceBInterface
GoodServiceB ..> ServiceCInterface
GoodServiceC ..> ServiceAInterface
@enduml
Tareas típicas del examen
- Dibuja diagramas de clases para escenarios dados
- Identifica tipos de relaciones
- Implementa relaciones en Java
- Refactoriza diseños deficientes
- Explica las diferencias entre agregación y composición
Soluciones para las tareas del examen
Tarea 1: Dibuja el diagrama de clases para un sistema de biblioteca
Escenario: Una biblioteca gestiona libros, clientes y préstamos. Los clientes pueden pedir libros prestados y devolverlos.
Pasos de la solución:
- Identifica las clases: Biblioteca, Libro, Cliente, Préstamo
- Define los atributos:
- Libro: ISBN, Título, Autor, Año
- Cliente: NumeroCliente, Nombre, Dirección
- Préstamo: FechaPréstamo, FechaRetorno
- Modela las relaciones:
- Cliente 0..* Préstamos
- Libro 0..* Préstamos
- Biblioteca 1..* Libros
- Añade los métodos:
- Libro: prestar(), devolver()
- Cliente: prestar(), devolver()
- Préstamo: renovar()
@startuml
class Biblioteca {
-nombre: String
+addLibro(libro: Libro): void
+encontrarLibro(isbn: String): Libro
}
class Libro {
-isbn: String
-titulo: String
-autor: String
-anio: int
+prestar(): void
+devolver(): void
}
class Cliente {
-numeroCliente: String
-nombre: String
-direccion: String
+prestar(libro: Libro): void
+devolver(libro: Libro): void
}
class Prestamo {
-fechaPrestamo: Date
-fechaRetorno: Date
+renovar(dias: int): void
}
Biblioteca "1" -- "*" Libro : posee >
Cliente "n" -- "n" Prestamo : tiene >
Libro "n" -- "n" Prestamo : involucrado >
@enduml
Tarea 2: Identifica los tipos de relaciones
Escenario: Un auto tiene un motor y cuatro ruedas. Un empleado pertenece a un departamento. Un estudiante se matricula en cursos.
Solución:
- Auto-Motor-Ruedas: Composición (*—) - Las partes existen solo dentro del todo
- Empleado-Departamento: Agregación (o—) - El empleado puede existir sin el departamento
- Estudiante-Curso: Asociación (----) - Objetos independientes con una relación
Tarea 3: Implementa relaciones en Java
Escenario: Implementa una composición entre Auto y Motor.
Solución:
public class Auto {
private String marca;
private Motor motor; // Composición
public Auto(String marca) {
this.marca = marca;
this.motor = new Motor(200); // Motor se crea dentro del Auto
}
public void conducir() {
motor.iniciar();
System.out.println(marca + " está conduciendo");
}
}
public class Motor {
private int potencia;
// Constructor privado, solo accesible desde Auto
private Motor(int potencia) {
this.potencia = potencia;
}
public void iniciar() {
System.out.println("Motor con " + potencia + " CV iniciado");
}
}
Tarea 4: Refactoriza un diseño deficiente
Diseño deficiente: Una clase TodoEnUno hace de todo.
Diseño mejorado: Separa las responsabilidades.
Antes:
class TodoEnUno {
// Gestión de usuarios
private List<User> users;
public void saveUser(User user) {}
// Servicio de email
public void sendEmail(User user, String message) {}
// Logging
public void log(String message) {}
}
Después:
class UserService {
private UserRepository userRepository;
private EmailService emailService;
public void saveUser(User user) {
userRepository.save(user);
emailService.sendWelcomeEmail(user);
}
}
class EmailService {
public void sendWelcomeEmail(User user) {}
}
class UserRepository {
public void save(User user) {}
}
Tarea 5: Explica agregación vs. composición
Agregación (rombo vacío o—):
- Relación “tiene-un”
- Las partes pueden existir independientemente
- Ejemplo: Departamento tiene empleados
- Ciclo de vida independiente
*Composición (rombo relleno —):
- Relación “tiene-un”
- Las partes dependen del todo
- Ejemplo: Auto tiene motor
- Ciclo de vida acoplado
Diferencia en Java:
// Agregación
class Departamento {
private List<Empleado> empleados = new ArrayList<>();
public void addEmpleado(Empleado e) {
empleados.add(e); // El empleado existe independientemente
}
}
// Composición
class Auto {
private Motor motor = new Motor(); // Motor se crea con el Auto
public Auto() {
// El motor solo existe mientras exista el Auto
}
}
Resumen
Los diagramas de clases UML son fundamentales para la arquitectura de software:
- Asociación: Relaciones estructurales entre clases
- Agregación: Relación “tiene-un”, las partes pueden existir independientemente
- Composición: Relación “tiene-un”, las partes son dependientes
- Dependencia: Uso de clases o interfaces
- Herencia: Relación “es-un” entre clases
- Implementación: Realización de interfaces
Un buen diseño de clases sigue los principios SOLID y evita dependencias cíclicas.
Caso práctico: Sistema de e-commerce con todos los tipos de asociación
Aquí mostramos un sistema de e-commerce completo que demuestra todos los tipos de relaciones importantes:
@startuml
' --- Abstrakte Basisklassen ---
abstract class Person {
#name: String
#email: String
+{abstract} getInfo(): String
}
abstract class Produkt {
#produktId: String
#name: String
#preis: double
+{abstract} calculatePrice(): double
}
' --- Konkrete Klassen ---
class Kunde extends Person {
-kundenNr: String
-adresse: String
-bestellungen: List<Bestellung>
+bestellen(produkte: List<Produkt>): Bestellung
+getInfo(): String
}
class Mitarbeiter extends Person {
-mitarbeiterNr: String
-position: String
+getInfo(): String
}
class Buch extends Produkt {
-isbn: String
-autor: String
+calculatePrice(): double
}
class Elektronik extends Produkt {
-garantie: int
+calculatePrice(): double
}
' --- Bestellungssystem ---
class Bestellung {
-bestellNr: String
-datum: Date
-gesamtpreis: double
-kunde: Kunde
-positionen: List<Bestellposition>
+berechneGesamtpreis(): double
+addPosition(produkt: Produkt, menge: int): void
}
class Bestellposition {
-menge: int
-einzelpreis: double
-produkt: Produkt
+getGesamtpreis(): double
}
' --- Services ---
class WarenkorbService {
-warenkoerbe: Map<String, Warenkorb>
+createWarenkorb(kunde: Kunde): Warenkorb
+addProdukt(kunde: Kunde, produkt: Produkt): void
}
class ZahlungService {
+processPayment(bestellung: Bestellung): boolean
+refund(bestellung: Bestellung): boolean
}
class EmailService {
+sendBestellbestaetigung(bestellung: Bestellung): void
+sendLieferbenachrichtigung(bestellung: Bestellung): void
}
' --- Interfaces ---
interface Zahlungsart {
+{abstract} bezahlen(betrag: double): boolean
}
class Kreditkarte implements Zahlungsart {
-kartenNr: String
-gueltigBis: Date
+bezahlen(betrag: double): boolean
}
class PayPal implements Zahlungsart {
-email: String
-passwort: String
+bezahlen(betrag: double): boolean
}
' --- Beziehungen ---
' Vererbung
Person <|-- Kunde
Person <|-- Mitarbeiter
Produkt <|-- Buch
Produkt <|-- Elektronik
' Implementierung
Zahlungsart <|.. Kreditkarte
Zahlungsart <|.. PayPal
' Assoziationen
Kunde "1" -- "0..*" Bestellung : gibtAuf >
Bestellung "1" -- "1..*" Bestellposition : enthält >
Produkt "1" -- "0..*" Bestellposition : istIn >
' Aggregation (Mitarbeiter können ohne Firma existieren)
Firma o-- "*" Mitarbeiter : beschaeftigt >
Kunde o-- "*" ZahlungService : nutzt >
' Komposition (Bestellpositionen existieren nur mit Bestellung)
Bestellung *-- "1..*" Bestellposition : hat >
' Abhängigkeiten
Bestellung ..> ZahlungService : verwendet >
Bestellung ..> EmailService : benachrichtigt >
WarenkorbService ..> Produkt : verwaltet >
WarenkorbService ..> Kunde : gehoertZu >
@enduml
Implementación en Java de las relaciones principales
// --- Herencia y Abstracción ---
public abstract class Person {
protected String name;
protected String email;
public abstract String getInfo();
// Método común
public String getContactInfo() {
return name + " - " + email;
}
}
public class Kunde extends Person {
private String kundenNr;
private List<Bestellung> bestellungen = new ArrayList<>();
@Override
public String getInfo() {
return "Kunde: " + name + " (Nr: " + kundenNr + ")";
}
public Bestellung bestellen(List<Produkt> produkte) {
Bestellung bestellung = new Bestellung(this, produkte);
bestellungen.add(bestellung);
return bestellung;
}
}
// --- Composición: Bestellung con Bestellpositionen ---
public class Bestellung {
private String bestellNr;
private Kunde kunde;
private List<Bestellposition> positionen = new ArrayList<>();
public Bestellung(Kunde kunde, List<Produkt> produkte) {
this.bestellNr = "B" + System.currentTimeMillis();
this.kunde = kunde;
// Composición: Las posiciones se crean con la orden
for (Produkt produkt : produkte) {
positionen.add(new Bestellposition(produkt, 1));
}
}
public double berechneGesamtpreis() {
return positionen.stream()
.mapToDouble(Bestellposition::getGesamtpreis)
.sum();
}
}
public class Bestellposition {
private Produkt produkt;
private int menge;
private double einzelpreis;
// Constructor privado - solo invocable desde Bestellung
private Bestellposition(Produkt produkt, int menge) {
this.produkt = produkt;
this.menge = menge;
this.einzelpreis = produkt.calculatePrice();
}
public double getGesamtpreis() {
return einzelpreis * menge;
}
}
// --- Agregación: Firma con Mitarbeiter ---
public class Firma {
private String name;
private List<Mitarbeiter> mitarbeiter = new ArrayList<>();
public void addMitarbeiter(Mitarbeiter mitarbeiter) {
this.mitarbeiter.add(mitarbeiter);
// El empleado existe independientemente de la empresa
}
public void removeMitarbeiter(Mitarbeiter mitarbeiter) {
this.mitarbeiter.remove(mitarbeiter);
// El empleado puede seguir existiendo
}
}
// --- Dependencias: Servicios ---
public class BestellungService {
private ZahlungService zahlungService;
private EmailService emailService;
private LagerService lagerService;
public BestellungService(ZahlungService zahlungService,
EmailService emailService,
LagerService lagerService) {
this.zahlungService = zahlungService;
this.emailService = emailService;
this.lagerService = lagerService;
}
public void bearbeiteBestellung(Bestellung bestellung) {
// Dependencia: Usar ZahlungService
if (zahlungService.processPayment(bestellung.berechneGesamtpreis())) {
// Dependencia: Usar LagerService
lagerService.reserviereProdukte(bestellung.getPositionen());
// Dependencia: Usar EmailService
emailService.sendBestellbestaetigung(bestellung);
}
}
}
// --- Implementación de interfaz ---
public interface Zahlungsart {
boolean bezahlen(double betrag);
}
public class Kreditkarte implements Zahlungsart {
private String kartenNr;
private Date gueltigBis;
@Override
public boolean bezahlen(double betrag) {
// Implementación del pago con tarjeta de crédito
System.out.println("Zahlung mit Kreditkarte: " + betrag + "€");
return true;
}
}
public class PayPal implements Zahlungsart {
private String email;
@Override
public boolean bezahlen(double betrag) {
// Implementación del pago con PayPal
System.out.println("Zahlung mit PayPal: " + betrag + "€");
return true;
}
}
Resumen de las relaciones en este ejemplo:
| Tipo de relación | Ejemplo en E-commerce | Significado |
|---|---|---|
| Herencia | Cliente extends Persona | Cliente es una Persona |
| Implementación | TarjetaCredito implements MetodoPago | TarjetaCredito es un MetodoPago |
| Asociación | Cliente -- Pedido | Cliente tiene Pedidos |
| Agregación | Empresa o-- Empleado | Empresa emplea Empleados |
| Composición | Pedido *-- LineaPedido | Pedido tiene LineaS |
| Dependencia | Pedido ..> ServicioPago | Pedido utiliza ServicioPago |
Este ejemplo demuestra cómo todas las relaciones funcionan en conjunto en un sistema real y cómo se implementan en Java.
Preguntas típicas que te puede hacer un examinador
1. ¿Cuál es la diferencia entre asociación y agregación en UML?
Respuesta: La diferencia radica en el ciclo de vida y la dependencia. En una asociación, ambos objetos tienen ciclos de vida independientes y pueden existir sin necesidad el uno del otro. En una agregación (rombo vacío o—), el todo tiene una relación “tiene-un” con las partes, pero las partes pueden existir de forma independiente. Ejemplo: un departamento tiene empleados, pero los empleados pueden existir sin un departamento.
2. Explica la diferencia entre agregación y composición.
Respuesta: Agregación (rombo vacío o—) describe una relación “tiene-un” donde las partes pueden existir independientemente del todo. Composición (rombo relleno *—) es más fuerte: las partes no pueden existir sin el todo. En composición, los objetos-parte generalmente se crean con el todo y se destruyen junto con él. Ejemplo: Auto contiene Motor (composición) versus Facultad tiene Profesores (agregación).
3. ¿Cómo se representan las multiplicidades en los diagramas de clases UML?
Respuesta: Las multiplicidades se anotan en los extremos de las relaciones e indican cuántas instancias son posibles. Notaciones importantes: 1 (exactamente una), 0..1 (cero o una), * (muchas, cero o más), 1..* (al menos una), 2..5 (entre 2 y 5). Ejemplo: un Cliente puede tener 0..* Pedidos, cada pedido pertenece a exactamente un cliente.
4. ¿Qué es una dependencia y cuándo se utiliza?
Respuesta: Una dependencia (línea punteada con flecha ..>) muestra una relación “utiliza” entre clases. Es temporal y más débil que una asociación. Se usa típicamente cuando una clase emplea otra como parámetro en métodos, como variable local o como tipo de retorno. Ejemplo: un Pedido utiliza un CalculadorPrecios para calcular el total.
5. ¿En qué se diferencia la herencia de la implementación?
Respuesta: Herencia (línea sólida con flecha cerrada |<|--) muestra una relación “es-un” entre clases. Una clase hereda atributos y métodos de otra. Implementación (línea punteada con flecha cerrada ..|>) indica que una clase realiza una interfaz. Herencia para clases, implementación para interfaces. Ejemplo: Auto hereda de Vehículo, Auto implementa Conducible.
6. ¿Qué son las clases abstractas y cómo se representan en UML?
Respuesta: Las clases abstractas no pueden instanciarse y a menudo contienen métodos abstractos sin implementación. En UML se marcan con el estereotipo <<abstract>> o se escriben en cursiva. Los métodos abstractos también aparecen en cursiva. Sirven como plantillas para clases concretas y permiten reutilización de código.
7. Explica el Principio de Responsabilidad Única (SRP) en UML.
Respuesta: El Principio de Responsabilidad Única establece que cada clase debe tener una única responsabilidad. En UML se evidencia en clases con atributos y métodos enfocados. Mal diseño: una clase GestorUsuarios que almacena usuarios, envía correos y escribe logs. Buen diseño: clases separadas ServicioUsuarios, ServicioCorreo, ServicioRegistro.
8. ¿Qué es una asociación calificada y cuándo se usa?
Respuesta: Una asociación calificada usa una clave (qualifier) para identificar de forma única un objeto en un conjunto. Se emplea cuando un objeto accede a otro mediante una clave específica. En UML, el calificador se representa en un rectángulo en la línea de asociación. Ejemplo: un Mapa usa una clave String para acceder a valores Object.
9. ¿Cómo se representan las interfaces en los diagramas de clases UML?
Respuesta: Las interfaces se marcan con el estereotipo <<interface>> o se representan con notación pura de interfaz. Contienen solo métodos abstractos (en cursiva) y sin métodos implementados. Las clases que implementan una interfaz usan la relación de implementación (línea punteada con flecha cerrada ..|>). Ejemplo: interfaz Volador con métodos volar() y aterrizar().
10. ¿Qué son los paquetes en UML y por qué se usan?
Respuesta: Los paquetes agrupan clases relacionadas y proporcionan espacios de nombres. Ayudan a organizar sistemas grandes y reducen complejidad. En UML se representan como carpetas con el nombre del paquete. Las dependencias entre paquetes muestran cuáles utilizan a otros. Ejemplo: com.example.model, com.example.service, com.example.repository.
11. Describe el Patrón Observer en UML.
Respuesta: El Patrón Observer define una relación 1-a-n entre objetos. Un Subject notifica a múltiples Observer cuando su estado cambia. En UML: interfaz Subject con métodos attach(), detach(), notify(). Interfaz Observer con método update(). Las clases concretas implementan estas interfaces y el Subject mantiene una lista de Observers.
12. ¿Qué es el Antipatrón God Object y cómo se evita?
Respuesta: El Antipatrón God Object describe una clase con demasiadas responsabilidades que se ha vuelto demasiado grande. Viola el Principio de Responsabilidad Única. Se evita dividiéndola en clases especializadas con responsabilidades claras. Ejemplo: en lugar de una clase GestorUsuarios que lo hace todo, crear clases separadas ServicioUsuarios, ServicioPedidos, ServicioPagos.
13. ¿Cómo se evitan las dependencias cíclicas en UML?
Respuesta: Las dependencias cíclicas ocurren cuando la clase A depende de B, B depende de C y C depende de A. Se evitan mediante inyección de dependencias con interfaces, introducción de una dependencia común o reestructuración de la arquitectura. En UML se evidencia por flechas de dependencia punteadas que forman un círculo.
14. ¿Cuál es la diferencia entre diagramas de clases y diagramas de objetos?
Respuesta: Los diagramas de clases muestran la estructura estática con clases, atributos, métodos y relaciones. Los diagramas de objetos muestran instancias concretas de clases con sus valores actuales y relaciones en un momento específico. Los diagramas de clases son planos, los diagramas de objetos son fotografías del sistema en ejecución.
15. Explica el Principio Abierto/Cerrado (OCP) en UML.
Respuesta: El Principio Abierto/Cerrado dice que el software debe estar abierto para extensión pero cerrado para modificación. En UML se evidencia mediante el uso de interfaces y clases abstractas. Nueva funcionalidad se añade mediante nuevas implementaciones sin cambiar código existente. Ejemplo: interfaz Forma con diferentes implementaciones Círculo, Rectángulo, Triángulo.
16. ¿Qué son los estereotipos en UML y para qué sirven?
Respuesta: Los estereotipos extienden la notación UML para propósitos específicos. Se representan entre comillas angulares dobles. Estereotipos comunes: <<entity>> para clases de datos, <<controller>> para controladores, <<service>> para servicios, <<utility>> para clases auxiliares, <<REST>> para APIs REST.
17. ¿Cómo se representa el Patrón Factory en UML?
Respuesta: El Patrón Factory usa una clase o interfaz Factory para crear objetos. En UML: interfaz Producto, clases Producto concretas, interfaz Factory con método crearProducto(), y una clase FactoriaConcreto que implementa la interfaz Factory y crea productos concretos.
18. ¿Cuál es la diferencia entre composición y agregación en el ciclo de vida?
Respuesta: En composición, los objetos-parte dependen del ciclo de vida del todo, se crean con él y se destruyen junto a él. En agregación, los objetos-parte tienen un ciclo de vida independiente y pueden sobrevivir a la existencia del todo. Composición: Auto posee Motor (Motor muere con Auto). Agregación: Departamento tiene Empleados (Empleados sobreviven al Departamento).
19. Explica el Principio de Sustitución de Liskov (LSP) en UML.
Respuesta: El Principio de Sustitución de Liskov establece que las subclases deben poder reemplazar sus clases base sin cambiar el comportamiento del programa. En UML se evidencia en jerarquías de herencia correctas. Violación: Pingüino hereda de Ave con método volar(), pero no puede volar. Solución: dividir interfaces en AveVoladora y AveNoVoladora.
20. ¿Cómo se representan los modificadores de visibilidad en UML?
Respuesta: Los modificadores de visibilidad se anotan antes de atributos y métodos: + para public (accesible desde cualquier lugar), - para private (solo dentro de la clase), # para protected (clase y subclases), ~ para package (solo dentro del paquete). Ejemplo: -nombre: String (atributo privado), +getNombre(): String (método público).
21. ¿Qué es una clase de asociación y cuándo se usa?
Respuesta: Una clase de asociación se utiliza para modelar una relación m:n con atributos adicionales. Se conecta a la línea de asociación y contiene atributos que pertenecen a la relación. Ejemplo: Estudiante y Curso tienen relación m:n, la clase de asociación Inscripción contiene fecha y calificación.
22. ¿Cómo se representan los métodos abstractos en UML?
Respuesta: Los métodos abstractos se representan en cursiva o con el estereotipo <<abstract>>. No tienen implementación y deben ser implementados por las subclases. Ejemplo: +{abstract} dibujar(): void en una clase abstracta Forma. Las clases concretas como Círculo y Rectángulo deben implementar este método.
23. ¿Cuál es la diferencia entre diagramas estructurales y de comportamiento?
Respuesta: Los diagramas estructurales muestran la estructura estática del sistema (clases, objetos, paquetes, componentes). Los diagramas de comportamiento muestran el comportamiento dinámico (casos de uso, actividades, secuencias, estados). Los diagramas de clases pertenecen a los diagramas estructurales, mientras que los diagramas de secuencia o actividad muestran comportamiento.
24. Explica el Patrón Singleton en UML.
Respuesta: El Patrón Singleton garantiza que de una clase solo exista una instancia. En UML: una clase con un constructor privado, una variable de instancia estática y un método getInstance() estático. La clase es responsable de crear y gestionar su única instancia.
25. Prepárate para una pregunta típica de examen de UML.
Respuesta: Pregunta: “Dibuja un diagrama de clases para un sistema bancario simple con clientes, cuentas y transacciones.” Estructura de respuesta: 1. Identificar clases (Cliente, Cuenta, Transacción), 2. Definir atributos (Cliente: nombre, dirección; Cuenta: numCuenta, saldo; Transacción: monto, fecha), 3. Modelar relaciones (Cliente 1..* Cuentas, Cuenta 1..* Transacciones), 4. Añadir métodos (depositar(), retirar(), transferir()), 5. Establecer visibilidades y anotar multiplicidades. Esta estructura demuestra enfoque sistemático y competencia en UML.
Continuamos en la ruta de aprendizaje UML
Ya hemos cubierto todos los artículos de UML. Regresa al primer artículo: Descripción general de diagramas UML: diagramas de clases, diagramas de secuencia, diagramas de actividad y casos de uso.

