UML Klassendiagramme: Beziehungen, Association, Aggregation & Komposition
UML Klassendiagramme sind das wichtigste Werkzeug zur Visualisierung von objektorientierten Softwarearchitekturen. Beziehungen zwischen Klassen definieren die Struktur und das Verhalten des Systems.
Während meiner Ausbildung habe ich diesen Diagrammen zu wenig Zeit geschenkt,aber sie sind für die Prüfungsphasen wichtig. Auch später, um Projekte zu verstehen oder anderen zu erläutern.
Wenn Du gerade eine Ausbildung zum Fachinformatiker durchläufst, dann lese jeden Tag eine “zufällige” Seite hier durch. Auch wenn Du nicht alle Themen nachvollziehen kannst, Du wirst immer etwas mitnehmen, bis zur Prüfung.
Grundlagen der UML-Klassendiagramme
Klassendarstellung
Einfache Klasse mit Interface
@startuml
' Basis-Klasse mit Attributen und Methoden
class Student {
-matrikelNr: int
-name: String
-semester: int
+getName(): String
+setMatrikelNr(nr: int): void
+studieren(): void
}
' Interface mit Methoden
interface Lernbar {
+lernen(): void
+pruefungAblegen(): boolean
}
' Interface
interface Lernfaehig {
+{abstract} lernen(fach: String): void
+{abstract} pruefungAblegen(fach: String): boolean
}
' Student implementiert Lernbar
Student ..|> Lernbar
@enduml
Sichtbarkeitsmodifizierer
| Symbol | Bedeutung | Beschreibung |
|---|---|---|
+ | public | Von überall zugreifbar |
- | private | Nur innerhalb der Klasse |
# | protected | Innerhalb der Klasse und Unterklassen |
~ | package | Nur innerhalb des Pakets |
Beziehungsarten in UML
1. Assoziation (Association)
Assoziationen beschreiben strukturelle Beziehungen zwischen Klassen.
@startuml
class Student {
-name: String
+getName(): String
}
class Kurs {
-titel: String
-credits: int
+getTitel(): String
}
' Einfache Assoziation
Student "1" -- "n" Kurs : belegt >
' Assoziation mit Rollen und Attributen
Student "1" -- "*" Note : \
note : hat
note : noteWert: double
note : datum: Date
' Gerichtete Assoziation
Student "1" -> "*" Projekt : leitet >
@enduml
Java-Implementierung von Assoziationen
public class AssociationExamples {
// Viele-zu-viele Assoziation
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 und 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 und 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 und 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 und Setter
public String getName() { return name; }
public Student getLeiter() { return leiter; }
}
}
2. Aggregation
Aggregation ist eine spezielle Form der Assoziation, bei der ein Teil-Objekt auch ohne das Ganze existieren kann.
@startuml
class Abteilung {
-name: String
+getName(): String
}
class Mitarbeiter {
-name: String
-position: String
+getName(): String
}
' Aggregation (leere Raute)
Abteilung o-- "*" Mitarbeiter : hat >
class Universitaet {
-name: String
+getName(): String
}
class Fakultaet {
-name: String
+getName(): String
}
' Aggregation
Universitaet o-- "*" Fakultaet : besitzt >
@enduml
Java-Implementierung von Aggregation
public class AggregationExamples {
// Abteilung kann ohne Mitarbeiter existieren
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 können auch ohne Abteilung existieren
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; // Optional
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 existieren unabhängig von Abteilung
public String getName() { return name; }
public Abteilung getAbteilung() { return abteilung; }
}
}
3. Komposition (Composition)
Komposition ist eine stärkere Form der Aggregation, bei der das Teil-Objekt ohne das Ganze nicht existieren kann.
@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
}
' Komposition (gefüllte Raute)
Auto *-- "1" Motor : hat >
Auto *-- "4" Reifen : hat >
class Haus {
-adresse: String
+wohnen(): void
}
class Zimmer {
-flaeche: double
-typ: String
+reinigen(): void
}
' Komposition
Haus *-- "*" Zimmer : hat >
@enduml
Java-Implementierung von Komposition
public class CompositionExamples {
// Motor existiert nur im Auto
public class Auto {
private String marke;
private String modell;
private Motor motor; // Existiert nur mit Auto
private List<Reifen> reifen = new ArrayList<>(); // Existieren nur mit Auto
public Auto(String marke, String modell) {
this.marke = marke;
this.modell = modell;
this.motor = new Motor(200, "Diesel"); // Motor wird im Auto erstellt
this.reifen.addAll(Arrays.asList(
new Reifen(205), new Reifen(205),
new Reifen(205), new Reifen(205)
));
}
public void fahren() {
motor.starten();
System.out.println(marke + " " + modell + " fährt");
}
// Getter
public String getMarke() { return marke; }
public Motor getMotor() { return motor; }
public List<Reifen> getReifen() {
return new ArrayList<>(reifen);
}
}
public class Motor {
private int leistung;
private String typ;
// Private Constructor - nur von Auto aufrufbar
private Motor(int leistung, String typ) {
this.leistung = leistung;
this.typ = typ;
}
public void starten() {
System.out.println("Motor (" + typ + ", " + leistung + " PS) gestartet");
}
// Getter
public int getLeistung() { return leistung; }
public String getTyp() { return typ; }
}
public class Reifen {
private int groesse;
private double druck;
// Private Constructor
private Reifen(int groesse) {
this.groesse = groesse;
this.druck = 2.5; // Standarddruck
}
public void aufpumpen(double druck) {
this.druck = druck;
System.out.println("Reifen auf " + druck + " bar aufgepumpt");
}
// Getter
public int getGroesse() { return groesse; }
public double getDruck() { return druck; }
}
}
4. Abhängigkeit (Dependency)
Abhängigkeiten zeigen Verwendungsbeziehungen zwischen Klassen.
@startuml
class Bestellung {
-datum: Date
-gesamtsumme: double
+berechneGesamtsumme(): double
}
class PreisRechner {
+berechnePreis(produkte: List<Produkt>): double
+berechneRabatt(betrag: double, rabatt: double): double
}
' Abhängigkeit (gestrichelte Linie)
Bestellung ..> PreisRechner : verwendet >
class Logger {
+logInfo(nachricht: String): void
+logError(nachricht: String): void
}
class DatenbankService {
+speichern(daten: Object): void
+laden(id: String): Object
}
Bestellung ..> Logger : loggt >
Bestellung ..> DatenbankService : speichert >
@enduml
Java-Implementierung von Abhängigkeiten
public class DependencyExamples {
public class Bestellung {
private Date datum;
private List<Produkt> produkte = new ArrayList<>();
private PreisRechner preisRechner;
private Logger logger;
private DatenbankService datenbankService;
public Bestellung(PreisRechner preisRechner, Logger logger,
DatenbankService datenbankService) {
this.datum = new Date();
this.preisRechner = preisRechner;
this.logger = logger;
this.datenbankService = datenbankService;
}
public void addProdukt(Produkt produkt) {
produkte.add(produkt);
logger.logInfo("Produkt hinzugefügt: " + produkt.getName());
}
public double berechneGesamtsumme() {
double summe = preisRechner.berechnePreis(produkte);
double rabatt = preisRechner.berechneRabatt(summe, 0.1);
logger.logInfo("Gesamtsumme berechnet: " + (summe - rabatt));
return summe - rabatt;
}
public void speichern() {
try {
datenbankService.speichern(this);
logger.logInfo("Bestellung gespeichert");
} catch (Exception e) {
logger.logError("Fehler beim Speichern: " + e.getMessage());
}
}
// Getter
public Date getDatum() { return datum; }
public List<Produkt> getProdukte() { return new ArrayList<>(produkte); }
}
// Abhängigkeits-Klassen
public class PreisRechner {
public double berechnePreis(List<Produkt> produkte) {
return produkte.stream()
.mapToDouble(Produkt::getPreis)
.sum();
}
public double berechneRabatt(double betrag, double rabattProzent) {
return betrag * rabattProzent;
}
}
public class Logger {
public void logInfo(String nachricht) {
System.out.println("INFO: " + nachricht);
}
public void logError(String nachricht) {
System.err.println("ERROR: " + nachricht);
}
}
public class DatenbankService {
public void speichern(Object daten) {
System.out.println("Daten gespeichert: " + daten);
}
public Object laden(String id) {
System.out.println("Daten geladen: " + id);
return null;
}
}
public class Produkt {
private String name;
private double preis;
public Produkt(String name, double preis) {
this.name = name;
this.preis = preis;
}
// Getter
public String getName() { return name; }
public double getPreis() { return preis; }
}
}
5. Vererbung (Inheritance)
Vererbung zeigt “ist-ein” Beziehungen zwischen Klassen.
@startuml
' Abstrakte Basisklasse
abstract class Fahrzeug {
#marke: String
#baujahr: int
+{abstract} fahren(): void
+{abstract} bremsen(): void
+getInfo(): String
}
' Konkrete Unterklassen
class Auto extends Fahrzeug {
-anzahlTueren: int
+fahren(): void
+bremsen(): void
+offnen(): void
}
class Motorrad extends Fahrzeug {
-typ: String
+fahren(): void
+bremsen(): void
+lenken(): void
}
class Fahrrad extends Fahrzeug {
-anzahlGaenge: int
+fahren(): void
+bremsen(): void
+schalten(): void
}
@enduml
Java-Implementierung von Vererbung
public class InheritanceExamples {
// Abstrakte Basisklasse
public abstract class Fahrzeug {
protected String marke;
protected int baujahr;
public Fahrzeug(String marke, int baujahr) {
this.marke = marke;
this.baujahr = baujahr;
}
// Abstrakte Methoden
public abstract void fahren();
public abstract void bremsen();
// Konkrete Methode
public String getInfo() {
return marke + " aus " + baujahr;
}
// Getter
public String getMarke() { return marke; }
public int getBaujahr() { return baujahr; }
}
// Konkrete Unterklassen
public class Auto extends Fahrzeug {
private int anzahlTueren;
public Auto(String marke, int baujahr, int anzahlTueren) {
super(marke, baujahr);
this.anzahlTueren = anzahlTueren;
}
@Override
public void fahren() {
System.out.println("Auto " + marke + " fährt mit " + anzahlTueren + " Türen");
}
@Override
public void bremsen() {
System.out.println("Auto bremst mit Bremsscheiben");
}
public void offnen() {
System.out.println("Autotür wird geöffnet");
}
@Override
public String getInfo() {
return super.getInfo() + " (Auto, " + anzahlTueren + " Türen)";
}
}
public class Motorrad extends Fahrzeug {
private String typ;
public Motorrad(String marke, int baujahr, String typ) {
super(marke, baujahr);
this.typ = typ;
}
@Override
public void fahren() {
System.out.println("Motorrad " + marke + " (" + typ + ") fährt");
}
@Override
public void bremsen() {
System.out.println("Motorrad bremst mit Scheibenbremsen");
}
public void lenken() {
System.out.println("Motorrad wird gelenkt");
}
@Override
public String getInfo() {
return super.getInfo() + " (Motorrad, " + typ + ")";
}
}
public class Fahrrad extends Fahrzeug {
private int anzahlGaenge;
public Fahrrad(String marke, int baujahr, int anzahlGaenge) {
super(marke, baujahr);
this.anzahlGaenge = anzahlGaenge;
}
@Override
public void fahren() {
System.out.println("Fahrrad " + marke + " wird in die Pedale getreten");
}
@Override
public void bremsen() {
System.out.println("Fahrrad bremst mit Felgenbremsen");
}
public void schalten() {
System.out.println("Fahrrad schaltet Gang");
}
@Override
public String getInfo() {
return super.getInfo() + " (Fahrrad, " + anzahlGaenge + " Gänge)";
}
}
}
6. Implementierung (Implementation)
Implementierung zeigt die Beziehung zwischen Interfaces und implementierenden Klassen.
@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
Java-Implementierung von Interfaces
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; }
}
}
Komplexe Klassendiagramme
Vollständiges Beispiel: Universitätssystem
@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
Java-Implementierung des Universitätssystems
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; }
}
}
Best Practices für UML-Klassendiagramme
1. Konsistente Namenskonventionen
// Gute Namenskonventionen
public class NamingConventions {
// Klassen: Substantive, PascalCase
public class StudentManagementSystem {}
// Methoden: Verben, camelCase
public void calculateGrade() {}
public void validateInput() {}
// Variablen: camelCase, beschreibend
private List<Student> enrolledStudents;
private double averageGrade;
// Konstanten: UPPER_CASE
public static final int MAX_STUDENTS_PER_COURSE = 100;
// Interfaces: Adjektive oder Fähigkeiten, PascalCase
public interface Printable {}
public interface Serializable {}
public interface StudentRepository {}
}
2. Verantwortlichkeiten trennen
@startuml
' Gute Aufteilung nach Verantwortlichkeiten
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
}
' Klare Abhängigkeiten
UserService ..> UserRepository : verwendet >
UserService ..> EmailService : verwendet >
@enduml
3. Vermeidung von zyklischen Abhängigkeiten
@startuml
' Schlecht: Zyklische Abhängigkeiten
class A {
-b: B
+doSomething(): void
}
class B {
-c: C
+doSomethingElse(): void
}
class C {
-a: A
+doAnotherThing(): void
}
A --> B
B --> C
C --> A ' Zyklus!
' Gut: Keine Zyklen
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
Erweiterte Konzepte und Patterns
1. Multipizität und Kardinalität
Die Multipizität gibt an, wie viele Instanzen einer Klasse an einer Beziehung beteiligt sein können.
@startuml
' Verschiedene Multipizitäten
class Kunde {
-kundenNr: String
}
class Bestellung {
-bestellNr: String
}
class Produkt {
-produktId: String
}
' 1-zu-n Beziehung
Kunde "1" -- "0..*" Bestellung : gibtAuf >
' n-zu-m Beziehung über Assoziationsklasse
Bestellung "n" -- "m" Bestellposition : enthält >
Produkt "1" -- "0..*" Bestellposition : hat >
class Bestellposition {
-menge: int
-einzelpreis: double
+getGesamtpreis(): double
}
@enduml
Multipizitäts-Notationen:
1genau eine0..1null oder eine*viele (null oder mehr)1..*mindestens eine2..5zwischen 2 und 50,1null oder eine (alternative Schreibweise)
2. Qualifizierte Assoziationen
Qualifizierte Assoziationen verwenden einen Schlüssel, um Objekte eindeutig zu identifizieren.
@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
}
' Qualifizierte Assoziation
Map "1" -- "*" Object : entries >
{key} String
@enduml
3. Abstrakte Klassen und Interfaces
Abstrakte Klassen können abstrakte Methoden enthalten und nicht instanziiert werden.
@startuml
' Abstrakte Klasse
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
}
' Konkrete Klassen
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 in UML
Singleton Pattern
@startuml
class Singleton {
-instance: Singleton
-Singleton()
+getInstance(): Singleton
+doSomething(): void
}
note top of Singleton
Private Constructor
Static Instance
Global Access Point
end note
@enduml
Observer Pattern
@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 : notifies >
@enduml
Factory Pattern
@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 : creates >
ConcreteFactory ..> ConcreteProductB : creates >
@enduml
5. Pakete und Namespaces
Pakete gruppieren zusammengehörige Klassen und bieten Namensräume.
@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
}
}
' Abhängigkeiten zwischen Paketen
com.example.service ..> com.example.model : uses >
com.example.service ..> com.example.repository : uses >
@enduml
6. Stereotypen und Notationen
Stereotypen erweitern die UML-Notation für spezifische Zwecke.
@startuml
' Stereotypen
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
}
' Spezielle Notationen
class API <<REST>> {
+{GET} /users: List<User>
+{POST} /users: User
+{PUT} /users/{id}: User
}
@enduml
Prüfungsrelevante Konzepte
Wichtige Beziehungsarten
| Beziehungstyp | Symbol | Bedeutung | Lebensdauer |
|---------------|--------|-----------|-------------|
| Assoziation | ---- | Strukturelle Beziehung | Unabhängig |
| Aggregation | o-- | "hat-ein" (teilweise) | Unabhängig |
| Komposition | *-- | "hat-ein" (ganz) | Abhängig |
| Abhängigkeit | ..> | "verwendet" | Temporär |
| Vererbung | < |-- | "ist-ein" | Permanent |
| Implementierung | ..|> | "implementiert" | Permanent |
SOLID-Prinzipien in UML
Single Responsibility Principle (SRP)
@startuml
' Schlecht: Eine Klasse mit vielen Verantwortlichkeiten
class BadUserManager {
-userData: UserData
+saveUser(): void
+sendWelcomeEmail(): void
+logActivity(): void
+validateInput(): void
}
' Gut: Verantwortlichkeiten getrennt
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
' Offen für Erweiterung, geschlossen für Änderung
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
}
' Neue Formen können hinzugefügt werden, ohne existing Code zu ändern
@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 ' Verletzt LSP!
+makeSound(): void
}
' Besser: Interface trennen
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-Patterns und ihre Vermeidung
God Object Anti-Pattern
@startuml
' Anti-Pattern: 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
}
' Besser: Spezialisierte Klassen
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
Circular Dependency Anti-Pattern
@startuml
' Anti-Pattern: Zyklische Abhängigkeiten
class ServiceA {
-serviceB: ServiceB
+doA(): void
}
class ServiceB {
-serviceC: ServiceC
+doB(): void
}
class ServiceC {
-serviceA: ServiceA
+doC(): void
}
ServiceA --> ServiceB
ServiceB --> ServiceC
ServiceC --> ServiceA ' Zyklus!
' Lösung: Dependency Injection mit 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
Typische Prüfungsaufgaben
- Zeichnen Sie Klassendiagramme für gegebene Szenarien
- Identifizieren Sie Beziehungsarten
- Implementieren Sie Beziehungen in Java
- Refaktorieren Sie schlechte Designs
- Erklären Sie Unterschiede zwischen Aggregation und Komposition
Lösungen für die Prüfungsaufgaben
Aufgabe 1: Klassendiagramm für Bibliothekssystem zeichnen
Szenario: Eine Bibliothek verwaltet Bücher, Kunden und Ausleihvorgänge. Kunden können Bücher ausleihen und zurückgeben.
Lösungsschritte:
- Klassen identifizieren: Bibliothek, Buch, Kunde, Ausleihvorgang
- Attribute definieren:
- Buch: ISBN, Titel, Autor, Jahr
- Kunde: KundenNr, Name, Adresse
- Ausleihvorgang: Ausleihdatum, Rückgabedatum
- Beziehungen modellieren:
- Kunde 0..* Ausleihvorgänge
- Buch 0..* Ausleihvorgänge
- Bibliothek 1..* Bücher
- Methoden hinzufügen:
- Buch: ausleihen(), zurückgeben()
- Kunde: ausleihen(), zurückgeben()
- Ausleihvorgang: verlängern()
@startuml
class Bibliothek {
-name: String
+addBuch(buch: Buch): void
+findeBuch(isbn: String): Buch
}
class Buch {
-isbn: String
-titel: String
-autor: String
-jahr: int
+ausleihen(): void
+zurückgeben(): void
}
class Kunde {
-kundenNr: String
-name: String
-adresse: String
+ausleihen(buch: Buch): void
+zurückgeben(buch: Buch): void
}
class Ausleihvorgang {
-ausleihdatum: Date
-rückgabedatum: Date
+verlängern(tage: int): void
}
Bibliothek "1" -- "*" Buch : besitzt >
Kunde "n" -- "n" Ausleihvorgang : hat >
Buch "n" -- "n" Ausleihvorgang : beteiligt >
@enduml
Aufgabe 2: Beziehungsarten identifizieren
Szenario: Ein Auto hat einen Motor und vier Reifen. Ein Mitarbeiter gehört zu einer Abteilung. Ein Student belegt Kurse.
Lösung:
- Auto-Motor-Reifen: Komposition (*—) - Teile existieren nur mit dem Ganzen
- Mitarbeiter-Abteilung: Aggregation (o—) - Mitarbeiter können ohne Abteilung existieren
- Student-Kurs: Assoziation (----) - Unabhängige Objekte mit Beziehung
Aufgabe 3: Beziehungen in Java implementieren
Szenario: Implementieren Sie eine Komposition zwischen Auto und Motor.
Lösung:
public class Auto {
private String marke;
private Motor motor; // Komposition
public Auto(String marke) {
this.marke = marke;
this.motor = new Motor(200); // Motor wird im Auto erstellt
}
public void fahren() {
motor.starten();
System.out.println(marke + " fährt");
}
}
public class Motor {
private int leistung;
// Private Constructor - nur von Auto aufrufbar
private Motor(int leistung) {
this.leistung = leistung;
}
public void starten() {
System.out.println("Motor mit " + leistung + " PS gestartet");
}
}
Aufgabe 4: Schlechtes Design refaktorieren
Schlechtes Design: Eine Klasse AllInOne macht alles.
Gutes Design: Verantwortlichkeiten trennen.
Vorher:
class AllInOne {
// User Management
private List<User> users;
public void saveUser(User user) {}
// Email Service
public void sendEmail(User user, String message) {}
// Logging
public void log(String message) {}
}
Nachher:
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) {}
}
Aufgabe 5: Aggregation vs. Komposition erklären
Aggregation (leere Raute o—):
- “hat-ein” Beziehung
- Teile können unabhängig existieren
- Beispiel: Abteilung hat Mitarbeiter
- Lebenszyklus unabhängig
*Komposition (gefüllte Raute —):
- “hat-ein” Beziehung
- Teile sind vom Ganzen abhängig
- Beispiel: Auto hat Motor
- Lebenszyklus gekoppelt
Unterschied in Java:
// Aggregation
class Abteilung {
private List<Mitarbeiter> mitarbeiter = new ArrayList<>();
public void addMitarbeiter(Mitarbeiter m) {
mitarbeiter.add(m); // Mitarbeiter existiert unabhängig
}
}
// Komposition
class Auto {
private Motor motor = new Motor(); // Motor wird mit Auto erstellt
public Auto() {
// Motor existiert nur mit Auto
}
}
Zusammenfassung
UML-Klassendiagramme sind essentiell für Softwarearchitektur:
- Assoziation: Strukturelle Beziehungen zwischen Klassen
- Aggregation: “hat-ein” Beziehung, Teile können unabhängig existieren
- Komposition: “hat-ein” Beziehung, Teile sind abhängig
- Abhängigkeit: Verwendung von Klassen oder Interfaces
- Vererbung: “ist-ein” Beziehung zwischen Klassen
- Implementierung: Interface-Realisierung
Gutes Klassendesign folgt SOLID-Prinzipien und vermeidet zyklische Abhängigkeiten.
Praxisbeispiel: E-Commerce System mit allen Assoziationsformen
Hier zeigen wir ein komplettes E-Commerce System, das alle wichtigen Beziehungsarten demonstriert:
@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
Java-Implementierung der wichtigsten Beziehungen
// --- Vererbung und Abstraktion ---
public abstract class Person {
protected String name;
protected String email;
public abstract String getInfo();
// Gemeinsame Methode
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;
}
}
// --- Komposition: Bestellung mit 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;
// Komposition: Positionen werden mit Bestellung erstellt
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;
// Private Constructor - nur von Bestellung aufrufbar
private Bestellposition(Produkt produkt, int menge) {
this.produkt = produkt;
this.menge = menge;
this.einzelpreis = produkt.calculatePrice();
}
public double getGesamtpreis() {
return einzelpreis * menge;
}
}
// --- Aggregation: Firma mit Mitarbeitern ---
public class Firma {
private String name;
private List<Mitarbeiter> mitarbeiter = new ArrayList<>();
public void addMitarbeiter(Mitarbeiter mitarbeiter) {
this.mitarbeiter.add(mitarbeiter);
// Mitarbeiter existiert unabhängig von der Firma
}
public void removeMitarbeiter(Mitarbeiter mitarbeiter) {
this.mitarbeiter.remove(mitarbeiter);
// Mitarbeiter kann weiter existieren
}
}
// --- Abhängigkeiten: Services ---
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) {
// Abhängigkeit: ZahlungService verwenden
if (zahlungService.processPayment(bestellung.berechneGesamtpreis())) {
// Abhängigkeit: LagerService verwenden
lagerService.reserviereProdukte(bestellung.getPositionen());
// Abhängigkeit: EmailService verwenden
emailService.sendBestellbestaetigung(bestellung);
}
}
}
// --- Interface-Implementierung ---
public interface Zahlungsart {
boolean bezahlen(double betrag);
}
public class Kreditkarte implements Zahlungsart {
private String kartenNr;
private Date gueltigBis;
@Override
public boolean bezahlen(double betrag) {
// Implementierung der Kreditkartenzahlung
System.out.println("Zahlung mit Kreditkarte: " + betrag + "€");
return true;
}
}
public class PayPal implements Zahlungsart {
private String email;
@Override
public boolean bezahlen(double betrag) {
// Implementierung der PayPal-Zahlung
System.out.println("Zahlung mit PayPal: " + betrag + "€");
return true;
}
}
Zusammenfassung der Beziehungen in diesem Beispiel:
| Beziehungstyp | Beispiel im E-Commerce | Bedeutung |
|---|---|---|
| Vererbung | Kunde extends Person | Kunde ist eine Person |
| Implementierung | Kreditkarte implements Zahlungsart | Kreditkarte ist eine Zahlungsart |
| Assoziation | Kunde -- Bestellung | Kunde hat Bestellungen |
| Aggregation | Firma o-- Mitarbeiter | Firma beschäftigt Mitarbeiter |
| Komposition | Bestellung *-- Bestellposition | Bestellung hat Positionen |
| Abhängigkeit | Bestellung ..> ZahlungService | Bestellung verwendet ZahlungService |
Dieses Beispiel zeigt, wie alle Beziehungsarten in einem realen System zusammenarbeiten und wie sie in Java implementiert werden.
Typische Prüfungsfragen, die Dir ein Prüfer stellen könnte
1. Was ist der Unterschied zwischen Assoziation und Aggregation in UML?
Antwort: Der Unterschied liegt in der Lebensdauer und Abhängigkeit. Bei einer Assoziation haben beide Objekte unabhängige Lebenszyklen und können ohne einander existieren. Bei einer Aggregation (leere Raute o—) hat das Ganze eine “hat-ein” Beziehung zu den Teilen, aber die Teile können auch unabhängig existieren. Beispiel: Eine Abteilung hat Mitarbeiter, aber Mitarbeiter können auch ohne Abteilung existieren.
2. Erklären Sie den Unterschied zwischen Aggregation und Komposition.
Antwort: Aggregation (leere Raute o—) beschreibt eine “hat-ein” Beziehung, bei der die Teile unabhängig vom Ganzen existieren können. Komposition (gefüllte Raute *—) ist eine stärkere Form, bei der die Teile ohne das Ganze nicht existieren können. Bei Komposition werden die Teil-Objekte typischerweise mit dem Ganzen erstellt und mit ihm zerstört. Beispiel: Auto hat Motor (Komposition) vs Fakultät hat Dozenten (Aggregation).
3. Wie werden Multipizitäten in UML-Klassendiagrammen dargestellt?
Antwort: Multipizitäten werden an den Enden von Beziehungen notiert und geben die Anzahl der möglichen Instanzen an. Wichtige Notationen: 1 (genau eine), 0..1 (null oder eine), * (viele, null oder mehr), 1..* (mindestens eine), 2..5 (zwischen 2 und 5). Beispiel: Ein Kunde kann 0..* Bestellungen haben, jede Bestellung gehört zu genau einem Kunden.
4. Was ist eine Abhängigkeit (Dependency) und wann wird sie verwendet?
Antwort: Eine Abhängigkeit (gestrichelte Linie mit Pfeil ..>) zeigt eine “verwendet” Beziehung zwischen Klassen. Sie ist temporär und schwächer als Assoziation. Typische Verwendung: eine Klasse verwendet eine andere Klasse als Parameter in Methoden, als lokale Variable oder als Return-Typ. Beispiel: Eine Bestellung verwendet einen PreisRechner zur Berechnung der Gesamtsumme.
5. Wie unterscheidet sich Vererbung von Implementierung?
Antwort: Vererbung (durchgezogene Linie mit geschlossenem Pfeil <|—>) zeigt eine “ist-ein” Beziehung zwischen Klassen. Eine Klasse erbt Attribute und Methoden einer anderen Klasse. Implementierung (gestrichelte Linie mit geschlossenem Pfeil ..|>) zeigt, dass eine Klasse ein Interface realisiert. Vererbung bei Klassen, Implementierung bei Interfaces. Beispiel: Auto erbt von Fahrzeug, Auto implementiert Fahrbar.
6. Was sind abstrakte Klassen und wie werden sie in UML dargestellt?
Antwort: Abstrakte Klassen können nicht instanziiert werden und enthalten oft abstrakte Methoden (ohne Implementierung). In UML werden sie mit dem Stereotyp <<abstract>> oder kursiv geschrieben. Abstrakte Methoden werden ebenfalls kursiv dargestellt. Sie dienen als Vorlagen für Konkrete Klassen und ermöglichen Code-Wiederverwendung.
7. Erklären Sie das Single Responsibility Principle (SRP) in UML.
Antwort: Das Single Responsibility Principle besagt, dass jede Klasse nur eine Verantwortlichkeit haben sollte. In UML zeigt sich dies durch Klassen mit fokussierten Attributen und Methoden. Schlechtes Design: Eine UserManager-Klasse, die Benutzer speichert, E-Mails sendet und Protokolle schreibt. Gutes Design: Separate Klassen UserService, EmailService, LoggingService.
8. Was ist eine qualifizierte Assoziation und wann wird sie verwendet?
Antwort: Eine qualifizierte Assoziation verwendet einen Schlüssel (qualifier), um ein Objekt in einer Menge eindeutig zu identifizieren. Sie wird verwendet, wenn ein Objekt über einen bestimmten Schlüssel auf ein anderes zugreift. In UML wird der Qualifier in einem Rechteck an der Assoziationslinie dargestellt. Beispiel: Eine Map verwendet einen String-Schlüssel, um auf Object-Werte zuzugreifen.
9. Wie werden Interfaces in UML-Klassendiagrammen dargestellt?
Antwort: Interfaces werden mit dem Stereotyp <<interface>> oder als reine Interface-Notation dargestellt. Sie enthalten nur abstrakte Methoden (kursiv) und keine implementierten Methoden. Klassen, die ein Interface implementieren, verwenden die Implementierungsbeziehung (gestrichelte Linie mit geschlossenem Pfeil ..|>). Beispiel: Fliegend-Interface mit Methoden fliegen() und landen().
10. Was sind Pakete in UML und warum werden sie verwendet?
Antwort: Pakete gruppieren zusammengehörige Klassen und bieten Namensräume. Sie helfen bei der Organisation großer Systeme und reduzieren Komplexität. In UML werden Pakete als Ordner mit dem Paketnamen dargestellt. Abhängigkeiten zwischen Paketen zeigen, welche Pakete andere verwenden. Beispiel: com.example.model, com.example.service, com.example.repository.
11. Beschreiben Sie das Observer Pattern in UML.
Antwort: Das Observer Pattern definiert eine 1-zu-n Beziehung zwischen Objekten. Ein Subject benachrichtigt mehrere Observer bei Zustandsänderungen. In UML: Subject-Interface mit attach(), detach(), notify() Methoden. Observer-Interface mit update() Methode. Konkrete Klassen implementieren diese Interfaces und der Subject hält eine Liste von Observern.
12. Was ist das God Object Anti-Pattern und wie wird es vermieden?
Antwort: Das God Object Anti-Pattern beschreibt eine Klasse, die zu viele Verantwortlichkeiten hat und zu groß geworden ist. Sie verletzt das Single Responsibility Principle. Vermeidung durch Aufteilung in spezialisierte Klassen mit klaren Verantwortlichkeiten. Beispiel: Statt einer UserManager-Klasse, die alles macht, separate Klassen UserService, OrderService, PaymentService.
13. Wie werden zyklische Abhängigkeiten in UML vermieden?
Antwort: Zyklische Abhängigkeiten entstehen, wenn Klasse A von B abhängt, B von C und C wieder von A. Vermeidung durch Dependency Injection mit Interfaces, Einführung einer gemeinsamen Abhängigkeit oder Umstrukturierung der Architektur. In UML zeigt sich dies durch gestrichelte Abhängigkeitspfeile, die einen Kreis bilden.
14. Was ist der Unterschied zwischen Klassen- und Objektdiagrammen?
Antwort: Klassendiagramme zeigen die statische Struktur mit Klassen, Attributen, Methoden und Beziehungen. Objektdiagramme zeigen konkrete Instanzen von Klassen mit ihren aktuellen Werten und Beziehungen zu einem bestimmten Zeitpunkt. Klassendiagramme sind Blaupausen, Objektdiagramme sind Schnappschüsse des laufenden Systems.
15. Erklären Sie das Open/Closed Principle (OCP) in UML.
Antwort: Das Open/Closed Principle besagt, dass Software für Erweiterungen offen, aber für Änderungen geschlossen sein sollte. In UML zeigt sich dies durch die Verwendung von Interfaces und abstrakten Klassen. Neue Funktionalität wird durch neue Implementierungen hinzugefügt, ohne existing Code zu ändern. Beispiel: Shape-Interface mit verschiedenen Implementierungen Circle, Rectangle, Triangle.
16. Was sind Stereotypen in UML und wofür werden sie verwendet?
Antwort: Stereotypen erweitern die UML-Notation für spezifische Zwecke. Sie werden in doppelten spitzen Klammern dargestellt. Häufige Stereotypen: <<entity>> für Datenklassen, <<controller>> für Controller, <<service>> für Services, <<utility>> für Hilfsklassen, <<REST>> für REST-APIs.
17. Wie wird das Factory Pattern in UML dargestellt?
Antwort: Das Factory Pattern verwendet eine Factory-Klasse oder ein Factory-Interface zur Erstellung von Objekten. In UML: Product-Interface, konkrete Product-Klassen, Factory-Interface mit createProduct() Methode, und eine ConcreteFactory-Klasse, die das Factory-Interface implementiert und konkrete Produkte erstellt.
18. Was ist der Unterschied zwischen Komposition und Aggregation in der Lebensdauer?
Antwort: Bei Komposition sind die Teil-Objekte vom Lebenszyklus des Ganzen abhängig - sie werden mit dem Ganzen erstellt und mit ihm zerstört. Bei Aggregation haben die Teil-Objekte einen unabhängigen Lebenszyklus und können über die Existenz des Ganzen hinaus bestehen. Komposition: Auto besitzt Motor (Motor stirbt mit Auto). Aggregation: Abteilung hat Mitarbeiter (Mitarbeiter überleben Abteilung).
19. Erklären Sie das Liskov Substitution Principle (LSP) in UML.
Antwort: Das Liskov Substitution Principle besagt, dass Unterklassen ihre Basisklassen ersetzen können müssen, ohne das Programmverhalten zu ändern. In UML zeigt sich dies durch korrekte Vererbungshierarchien. Verletzung: Penguin erbt von Bird mit fly()-Methode, kann aber nicht fliegen. Lösung: Interfaces aufteilen in FlyingBird und NonFlyingBird.
20. Wie werden Sichtbarkeitsmodifizierer in UML dargestellt?
Antwort: Sichtbarkeitsmodifizierer werden vor Attributen und Methoden notiert: + für public (von überall zugreifbar), - für private (nur innerhalb der Klasse), # für protected (Klasse und Unterklassen), ~ für package (nur innerhalb des Pakets). Beispiel: -name: String (privates Attribut), +getName(): String (öffentliche Methode).
21. Was ist eine Assoziationsklasse und wann wird sie verwendet?
Antwort: Eine Assoziationsklasse wird verwendet, um eine m:n-Beziehung mit zusätzlichen Attributen zu modellieren. Sie ist mit der Assoziationslinie verbunden und enthält Attribute, die zur Beziehung gehören. Beispiel: Student und Kurs haben eine m:n-Beziehung, die Bestellposition-Assoziationsklasse enthält menge und einzelpreis.
22. Wie werden abstrakte Methoden in UML dargestellt?
Antwort: Abstrakte Methoden werden in kursiver Schrift dargestellt oder mit dem Stereotyp <<abstract>>. Sie haben keine Implementierung und müssen von Unterklassen implementiert werden. Beispiel: +{abstract} draw(): void in einer abstrakten Shape-Klasse. Konkrete Klassen wie Circle und Rectangle müssen diese Methode implementieren.
23. Was ist der Unterschied zwischen strukturellen und Verhaltensdiagrammen?
Antwort: Strukturelle Diagramme zeigen die statische Struktur des Systems (Klassen, Objekte, Pakete, Komponenten). Verhaltensdiagramme zeigen das dynamische Verhalten (Anwendungsfälle, Aktivitäten, Sequenzen, Zustände). Klassendiagramme gehören zu den strukturellen Diagrammen, während Sequenzdiagramme oder Aktivitätsdiagramme Verhalten zeigen.
24. Erklären Sie das Singleton Pattern in UML.
Antwort: Das Singleton Pattern stellt sicher, dass von einer Klasse nur eine Instanz existiert. In UML: Eine Klasse mit einem privaten Konstruktor, einer statischen Instanzvariable und einer statischen getInstance()-Methode. Die Klasse ist für die Erstellung und Verwaltung ihrer einzigen Instanz verantwortlich.
25. Bereiten Sie sich auf eine typische UML-Prüfungsfrage vor.
Antwort: Frage: “Zeichnen Sie ein Klassendiagramm für ein einfaches Bankensystem mit Kunden, Konten und Transaktionen.” Antwortstruktur: 1. Klassen identifizieren (Customer, Account, Transaction), 2. Attribute definieren (Customer: name, address; Account: accountNumber, balance; Transaction: amount, date), 3. Beziehungen modellieren (Customer 1..* Accounts, Account 1..* Transactions), 4. Methoden hinzufügen (deposit(), withdraw(), transfer()), 5. Sichtbarkeiten festlegen und Multiplizitäten notieren. Diese Struktur zeigt systematisches Vorgehen und UML-Kompetenz.