Skip to content
IRC-CodingIRC-Coding
UMLClass DiagramsRelationshipsAssociationAggregationCompositionDependencyInheritancePlantUML

UML Class Diagrams: Relationships & Composition

UML class diagrams with associations, aggregation, composition, dependency, and inheritance. PlantUML examples included.

S

schutzgeist

35 min read
UML Class Diagrams: Relationships & Composition

UML Class Diagrams: Relationships, Association, Aggregation & Composition

UML class diagrams are the primary tool for visualizing object-oriented software architectures. Relationships between classes define the structure and behavior of a system.

Early in my training, I didn’t give these diagrams enough attention, but they’re crucial for exam preparation. Later on, they become invaluable for understanding projects and explaining them to others.

If you’re working toward your IT specialist certification, try reading a random page here each day. Even if you don’t fully grasp every topic, you’ll pick up something useful by the time exams roll around.

Basics of UML Class Diagrams

Class Representation

Simple class with interface

    @startuml
' Basic class with attributes and methods
class Student {
-matrikelNr: int
-name: String
-semester: int
+getName(): String
+setMatrikelNr(nr: int): void
+studieren(): void
}

' Interface with methods
interface Lernbar {
+lernen(): void
+pruefungAblegen(): boolean
}

' Interface
interface Lernfaehig {
+{abstract} lernen(fach: String): void
+{abstract} pruefungAblegen(fach: String): boolean
}

' Student implements Lernbar
Student ..|> Lernbar
@enduml
  

Visibility Modifiers

SymbolMeaningDescription
+publicAccessible from anywhere
-privateOnly within the class
#protectedWithin the class and subclasses
~packageOnly within the package

Relationship Types in UML

1. Association

Associations describe structural relationships between classes.

@startuml
class Student {
  -name: String
  +getName(): String
}

class Kurs {
  -titel: String
  -credits: int
  +getTitel(): String
}

' Simple association
Student "1" -- "n" Kurs : belegt >

' Association with roles and attributes
Student "1" -- "*" Note : \
  note : hat
  note : noteWert: double
  note : datum: Date

' Directed association
Student "1" -> "*" Projekt : leitet >

@enduml

Java Implementation of Associations

public class AssociationExamples {
    
    // Many-to-many association
    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);
        }
        
        // Getters and setters
        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);
        }
        
        // Getters and setters
        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);
        }
        
        // Getters and setters
        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);
        }
        
        // Getters and setters
        public String getName() { return name; }
        public Student getLeiter() { return leiter; }
    }
}

2. Aggregation

Aggregation is a special form of association where a part object can exist independently of the whole.

@startuml
class Abteilung {
  -name: String
  +getName(): String
}

class Mitarbeiter {
  -name: String
  -position: String
  +getName(): String
}

' Aggregation (empty diamond)
Abteilung o-- "*" Mitarbeiter : hat >

class Universitaet {
  -name: String
  +getName(): String
}

class Fakultaet {
  -name: String
  +getName(): String
}

' Aggregation
Universitaet o-- "*" Fakultaet : besitzt >

@enduml

Java Implementation of Aggregation

public class AggregationExamples {
    
    // Department can exist without employees
    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);
        }
        
        // Employees can exist without the department
        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;
            }
        }
        
        // Employees exist independently of the department
        public String getName() { return name; }
        public Abteilung getAbteilung() { return abteilung; }
    }
}

3. Composition

Composition is a stronger form of aggregation where a part object cannot exist without the whole.

@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
}

' Composition (filled diamond)
Auto *-- "1" Motor : hat >
Auto *-- "4" Reifen : hat >

class Haus {
  -adresse: String
  +wohnen(): void
}

class Zimmer {
  -flaeche: double
  -typ: String
  +reinigen(): void
}

' Composition
Haus *-- "*" Zimmer : hat >

@enduml

Java Implementation of Composition

public class CompositionExamples {
    
    // Engine exists only within the car
    public class Car {
        private String brand;
        private String model;
        private Engine engine; // Exists only with the car
        private List<Tire> tires = new ArrayList<>(); // Exist only with the car
        
        public Car(String brand, String model) {
            this.brand = brand;
            this.model = model;
            this.engine = new Engine(200, "Diesel"); // Engine is created within the car
            this.tires.addAll(Arrays.asList(
                new Tire(205), new Tire(205), 
                new Tire(205), new Tire(205)
            ));
        }
        
        public void drive() {
            engine.start();
            System.out.println(brand + " " + model + " is driving");
        }
        
        // Getters
        public String getBrand() { return brand; }
        public Engine getEngine() { return engine; }
        public List<Tire> getTires() { 
            return new ArrayList<>(tires); 
        }
    }
    
    public class Engine {
        private int horsepower;
        private String type;
        
        // Private constructor - only callable by Car
        private Engine(int horsepower, String type) {
            this.horsepower = horsepower;
            this.type = type;
        }
        
        public void start() {
            System.out.println("Engine (" + type + ", " + horsepower + " hp) started");
        }
        
        // Getters
        public int getHorsepower() { return horsepower; }
        public String getType() { return type; }
    }
    
    public class Tire {
        private int size;
        private double pressure;
        
        // Private constructor
        private Tire(int size) {
            this.size = size;
            this.pressure = 2.5; // Default pressure
        }
        
        public void inflate(double pressure) {
            this.pressure = pressure;
            System.out.println("Tire inflated to " + pressure + " bar");
        }
        
        // Getters
        public int getSize() { return size; }
        public double getPressure() { return pressure; }
    }
}

4. Dependency

Dependencies represent usage relationships between classes.

@startuml
class Order {
  -date: Date
  -totalAmount: double
  +calculateTotal(): double
}

class PriceCalculator {
  +calculatePrice(products: List<Product>): double
  +calculateDiscount(amount: double, discount: double): double
}

' Dependency (dashed line)
Order ..> PriceCalculator : uses >

class Logger {
  +logInfo(message: String): void
  +logError(message: String): void
}

class DatabaseService {
  +save(data: Object): void
  +load(id: String): Object
}

Order ..> Logger : logs >
Order ..> DatabaseService : persists >

@enduml

Java Implementation of Dependency

public class DependencyExamples {
    
    public class Order {
        private Date date;
        private List<Product> products = new ArrayList<>();
        private PriceCalculator priceCalculator;
        private Logger logger;
        private DatabaseService databaseService;
        
        public Order(PriceCalculator priceCalculator, Logger logger, 
                     DatabaseService databaseService) {
            this.date = new Date();
            this.priceCalculator = priceCalculator;
            this.logger = logger;
            this.databaseService = databaseService;
        }
        
        public void addProduct(Product product) {
            products.add(product);
            logger.logInfo("Product added: " + product.getName());
        }
        
        public double calculateTotal() {
            double total = priceCalculator.calculatePrice(products);
            double discount = priceCalculator.calculateDiscount(total, 0.1);
            
            logger.logInfo("Total calculated: " + (total - discount));
            return total - discount;
        }
        
        public void save() {
            try {
                databaseService.save(this);
                logger.logInfo("Order saved");
            } catch (Exception e) {
                logger.logError("Error saving order: " + e.getMessage());
            }
        }
        
        // Getters
        public Date getDate() { return date; }
        public List<Product> getProducts() { return new ArrayList<>(products); }
    }
    
    // Dependency classes
    public class PriceCalculator {
        public double calculatePrice(List<Product> products) {
            return products.stream()
                .mapToDouble(Product::getPrice)
                .sum();
        }
        
        public double calculateDiscount(double amount, double discountPercent) {
            return amount * discountPercent;
        }
    }
    
    public class Logger {
        public void logInfo(String message) {
            System.out.println("INFO: " + message);
        }
        
        public void logError(String message) {
            System.err.println("ERROR: " + message);
        }
    }
    
    public class DatabaseService {
        public void save(Object data) {
            System.out.println("Data saved: " + data);
        }
        
        public Object load(String id) {
            System.out.println("Data loaded: " + id);
            return null;
        }
    }
    
    public class Product {
        private String name;
        private double price;
        
        public Product(String name, double price) {
            this.name = name;
            this.price = price;
        }
        
        // Getters
        public String getName() { return name; }
        public double getPrice() { return price; }
    }
}

5. Inheritance

Inheritance represents “is-a” relationships between classes.

@startuml
' Abstract base class
abstract class Vehicle {
  #brand: String
  #year: int
  
  +&#123;abstract&#125; drive(): void
  +&#123;abstract&#125; brake(): void
  +getInfo(): String
}

' Concrete subclasses
class Car extends Vehicle {
  -numDoors: int
  +drive(): void
  +brake(): void
  +open(): void
}

class Motorcycle extends Vehicle {
  -type: String
  +drive(): void
  +brake(): void
  +steer(): void
}

class Bicycle extends Vehicle {
  -numGears: int
  +drive(): void
  +brake(): void
  +shift(): void
}

@enduml

Java Implementation of Inheritance

public class InheritanceExamples {
    
    // Abstract base class
    public abstract class Vehicle {
        protected String brand;
        protected int year;
        
        public Vehicle(String brand, int year) {
            this.brand = brand;
            this.year = year;
        }
        
        // Abstract methods
        public abstract void drive();
        public abstract void brake();
        
        // Concrete method
        public String getInfo() {
            return brand + " from " + year;
        }
        
        // Getters
        public String getBrand() { return brand; }
        public int getYear() { return year; }
    }
    
    // Concrete subclasses
    public class Car extends Vehicle {
        private int numDoors;
        
        public Car(String brand, int year, int numDoors) {
            super(brand, year);
            this.numDoors = numDoors;
        }
        
        @Override
        public void drive() {
            System.out.println("Car " + brand + " is driving with " + numDoors + " doors");
        }
        
        @Override
        public void brake() {
            System.out.println("Car is braking with disc brakes");
        }
        
        public void open() {
            System.out.println("Car door is opening");
        }
        
        @Override
        public String getInfo() {
            return super.getInfo() + " (Car, " + numDoors + " doors)";
        }
    }
    
    public class Motorcycle extends Vehicle {
        private String type;
        
        public Motorcycle(String brand, int year, String type) {
            super(brand, year);
            this.type = type;
        }
        
        @Override
        public void drive() {
            System.out.println("Motorcycle " + brand + " (" + type + ") is driving");
        }
        
        @Override
        public void brake() {
            System.out.println("Motorcycle is braking with disc brakes");
        }
        
        public void steer() {
            System.out.println("Motorcycle is being steered");
        }
        
        @Override
        public String getInfo() {
            return super.getInfo() + " (Motorcycle, " + type + ")";
        }
    }
    
    public class Bicycle extends Vehicle {
        private int numGears;
        
        public Bicycle(String brand, int year, int numGears) {
            super(brand, year);
            this.numGears = numGears;
        }
        
        @Override
        public void drive() {
            System.out.println("Bicycle " + brand + " is pedaled");
        }
        
        @Override
        public void brake() {
            System.out.println("Bicycle is braking with rim brakes");
        }
        
        public void shift() {
            System.out.println("Bicycle is shifting gears");
        }
        
        @Override
        public String getInfo() {
            return super.getInfo() + " (Bicycle, " + numGears + " gears)";
        }
    }
}

6. Implementation

Implementation shows the relationship between interfaces and the classes that implement them.

@startuml
' Interfaces
interface Fliegend {
  +&#123;abstract&#125; fliegen(): void
  +&#123;abstract&#125; landen(): void
}

interface Schwimmend {
  +&#123;abstract&#125; schwimmen(): void
  +&#123;abstract&#125; 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
  +&#123;abstract&#125; atmen(): void
  +schwimmen(): void
  +tauchen(): void
}

class Fisch extends Wasserlebewesen {
  -art: String
  +atmen(): void
}

@enduml

Java Implementation of Interfaces

public class InterfaceExamples {
    
    // Interfaces
    public interface Fliegend {
        void fliegen();
        void landen();
    }
    
    public interface Schwimmend {
        void schwimmen();
        void tauchen();
    }
    
    // Simple implementation
    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; }
    }
    
    // Implementing multiple interfaces
    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; }
    }
    
    // Abstract class with 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; }
    }
    
    // Concrete subclass
    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; }
    }
}

Complex Class Diagrams

Complete Example: University System

@startuml
' Abstract base classes
abstract class Person {
  #name: String
  #alter: int
  +&#123;abstract&#125; getInfo(): String
  +geburtstagFeiern(): void
}

abstract class Mitarbeiter extends Person {
  #mitarbeiterNr: String
  #gehalt: double
  +&#123;abstract&#125; arbeiten(): void
  +gehaltErhoehen(prozent: double): void
}

' Concrete classes
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
}

' Additional classes
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
}

' Relationships
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 >

' Composition
Universitaet *-- "*" Fakultaet : besitzt >

@enduml

Java Implementation of the University System

public class UniversitySystem {
    
    // Abstract base class
    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; }
    }
    
    // Abstract employee class
    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; }
    }
    
    // Concrete classes
    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; }
    }
    
    // Additional classes
    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 for UML Class Diagrams

1. Consistent Naming Conventions

// Good naming conventions
public class NamingConventions {
    
    // Classes: nouns, PascalCase
    public class StudentManagementSystem {}
    
    // Methods: verbs, camelCase
    public void calculateGrade() {}
    public void validateInput() {}
    
    // Variables: camelCase, descriptive
    private List<Student> enrolledStudents;
    private double averageGrade;
    
    // Constants: UPPER_CASE
    public static final int MAX_STUDENTS_PER_COURSE = 100;
    
    // Interfaces: adjectives or capabilities, PascalCase
    public interface Printable {}
    public interface Serializable {}
    public interface StudentRepository {}
}

2. Separate Responsibilities

@startuml
' Good separation of responsibilities
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
}

' Clear dependencies
UserService ..> UserRepository : uses >
UserService ..> EmailService : uses >

@enduml

3. Avoiding Circular Dependencies

@startuml
' Bad: circular dependencies
class A {
  -b: B
  +doSomething(): void
}

class B {
  -c: C
  +doSomethingElse(): void
}

class C {
  -a: A
  +doAnotherThing(): void
}

A --> B
B --> C
C --> A ' Cycle!

' Good: no cycles
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

Advanced Concepts and Patterns

1. Multiplicity and Cardinality

Multiplicity specifies how many instances of a class can participate in a relationship.

@startuml
' Various multiplicities
class Customer {
  -customerNumber: String
}

class Order {
  -orderNumber: String
}

class Product {
  -productId: String
}

' One-to-many relationship
Customer "1" -- "0..*" Order : places >

' Many-to-many relationship via association class
Order "n" -- "m" OrderItem : contains >
Product "1" -- "0..*" OrderItem : has >

class OrderItem {
  -quantity: int
  -unitPrice: double
  +getTotalPrice(): double
}

@enduml

Multiplicity notations:

  • 1 exactly one
  • 0..1 zero or one
  • * many (zero or more)
  • 1..* at least one
  • 2..5 between 2 and 5
  • 0,1 zero or one (alternative notation)

2. Qualified Associations

Qualified associations use a key to uniquely identify objects.

@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
}

' Qualified association
Map "1" -- "*" Object : entries >
{key} String

@enduml

3. Abstract Classes and Interfaces

Abstract classes can contain abstract methods and cannot be instantiated.

@startuml
' Abstract class
abstract class Shape {
  #color: String
  #position: Point
  +&#123;abstract&#125; draw(): void
  +&#123;abstract&#125; getArea(): double
  +move(x: double, y: double): void
}

' Interface
interface Movable {
  +&#123;abstract&#125; move(dx: double, dy: double): void
  +&#123;abstract&#125; getPosition(): Point
}

interface Resizable {
  +&#123;abstract&#125; scale(factor: double): void
  +&#123;abstract&#125; getSize(): Size
}

' Concrete classes
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 {
  +&#123;abstract&#125; update(subject: Subject): void
}

interface Subject {
  +&#123;abstract&#125; attach(observer: Observer): void
  +&#123;abstract&#125; detach(observer: Observer): void
  +&#123;abstract&#125; 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 {
  +&#123;abstract&#125; operation(): void
}

class ConcreteProductA implements Product {
  +operation(): void
}

class ConcreteProductB implements Product {
  +operation(): void
}

interface Factory {
  +&#123;abstract&#125; createProduct(type: String): Product
}

class ConcreteFactory implements Factory {
  +createProduct(type: String): Product
}

ConcreteFactory ..> ConcreteProductA : creates >
ConcreteFactory ..> ConcreteProductB : creates >

@enduml

5. Packages and Namespaces

Packages group related classes together and provide namespaces.

@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 {
    +&#123;abstract&#125; save(user: User): void
    +&#123;abstract&#125; findById(id: String): User
  }
  
  interface ProductRepository {
    +&#123;abstract&#125; save(product: Product): void
    +&#123;abstract&#125; findById(id: String): Product
  }
}

' Dependencies between packages
com.example.service ..> com.example.model : uses >
com.example.service ..> com.example.repository : uses >

@enduml

6. Stereotypes and Notations

Stereotypes extend UML notation for specific purposes.

@startuml
' Stereotypes
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
}

' Special notations
class API <<REST>> {
  +{GET} /users: List<User>
  +{POST} /users: User
  +{PUT} /users/{id}: User
}

@enduml

Exam-Relevant Concepts

Key Relationship Types

| Relationship Type | Symbol | Meaning | Lifetime |
|---|---|---|---|
| Association | ---- | Structural relationship | Independent |
| Aggregation | o-- | "has-a" (partial) | Independent |
| Composition | *-- | "has-a" (complete) | Dependent |
| Dependency | ..> | "uses" | Temporary |
| Inheritance | <\|-- | "is-a" | Permanent |
| Implementation | .\|> | "implements" | Permanent |

SOLID Principles in UML

Single Responsibility Principle (SRP)

@startuml
' Bad: One class with many responsibilities
class BadUserManager {
  -userData: UserData
  +saveUser(): void
  +sendWelcomeEmail(): void
  +logActivity(): void
  +validateInput(): void
}

' Good: Responsibilities separated
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
' Open for extension, closed for modification
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
}

' New shapes can be added without changing existing code

@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 ' Violates LSP!
  +makeSound(): void
}

' Better: Separate 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-Patterns and How to Avoid Them

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
}

' Better: Specialized classes
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: Circular dependencies
class ServiceA {
  -serviceB: ServiceB
  +doA(): void
}

class ServiceB {
  -serviceC: ServiceC
  +doB(): void
}

class ServiceC {
  -serviceA: ServiceA
  +doC(): void
}

ServiceA --> ServiceB
ServiceB --> ServiceC
ServiceC --> ServiceA ' Cycle!

' Solution: Dependency injection with interfaces
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

Typical Exam Tasks

  1. Draw class diagrams for given scenarios
  2. Identify relationship types
  3. Implement relationships in Java
  4. Refactor poor designs
  5. Explain the differences between aggregation and composition

Solutions to Exam Tasks

Task 1: Draw a Class Diagram for a Library System

Scenario: A library manages books, customers, and borrowing records. Customers can borrow and return books.

Solution steps:

  1. Identify classes: Library, Book, Customer, BorrowingRecord
  2. Define attributes:
    • Book: ISBN, Title, Author, Year
    • Customer: CustomerID, Name, Address
    • BorrowingRecord: BorrowDate, ReturnDate
  3. Model relationships:
    • Customer 0..* BorrowingRecords
    • Book 0..* BorrowingRecords
    • Library 1..* Books
  4. Add methods:
    • Book: borrow(), return()
    • Customer: borrow(), return()
    • BorrowingRecord: extend()
@startuml
class Library {
  -name: String
  +addBook(book: Book): void
  +findBook(isbn: String): Book
}

class Book {
  -isbn: String
  -title: String
  -author: String
  -year: int
  +borrow(): void
  +return(): void
}

class Customer {
  -customerID: String
  -name: String
  -address: String
  +borrow(book: Book): void
  +return(book: Book): void
}

class BorrowingRecord {
  -borrowDate: Date
  -returnDate: Date
  +extend(days: int): void
}

Library "1" -- "*" Book : owns >
Customer "n" -- "n" BorrowingRecord : has >
Book "n" -- "n" BorrowingRecord : involved >

@enduml

Task 2: Identify Relationship Types

Scenario: A car has an engine and four wheels. An employee belongs to a department. A student enrolls in courses.

Solution:

  • Car-Engine-Wheels: Composition (*—) — parts exist only with the whole
  • Employee-Department: Aggregation (o—) — employees can exist without a department
  • Student-Course: Association (----) — independent objects with a relationship

Task 3: Implement Relationships in Java

Scenario: Implement a composition between a car and its engine.

Solution:

public class Car {
    private String brand;
    private Engine engine; // Composition
    
    public Car(String brand) {
        this.brand = brand;
        this.engine = new Engine(200); // Engine is created within Car
    }
    
    public void drive() {
        engine.start();
        System.out.println(brand + " is driving");
    }
}

public class Engine {
    private int horsepower;
    
    // Private constructor — only callable from Car
    private Engine(int horsepower) {
        this.horsepower = horsepower;
    }
    
    public void start() {
        System.out.println("Engine with " + horsepower + " HP started");
    }
}

Task 4: Refactor Poor Design

Poor Design: A single AllInOne class does everything. Good Design: Separate responsibilities.

Before:

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) {}
}

After:

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) {}
}

Task 5: Explain Aggregation vs. Composition

Aggregation (empty diamond o—):

  • “has-a” relationship
  • Parts can exist independently
  • Example: Department has employees
  • Lifecycle is independent

*Composition (filled diamond —):

  • “has-a” relationship
  • Parts depend on the whole
  • Example: Car has an engine
  • Lifecycles are coupled

Difference in Java:

// Aggregation
class Department {
    private List<Employee> employees = new ArrayList<>();
    
    public void addEmployee(Employee e) {
        employees.add(e); // Employee exists independently
    }
}

// Composition
class Car {
    private Engine engine = new Engine(); // Engine is created with Car
    
    public Car() {
        // Engine exists only with Car
    }
}

Summary

UML class diagrams are essential to software architecture:

  • Association: Structural relationships between classes
  • Aggregation: A “has-a” relationship where parts can exist independently
  • Composition: A “has-a” relationship where parts depend on the whole
  • Dependency: One class or interface uses another
  • Inheritance: An “is-a” relationship between classes
  • Implementation: Interface realization

Good class design follows SOLID principles and avoids circular dependencies.

Practical Example: E-Commerce System with All Association Types

Here we demonstrate a complete e-commerce system that showcases all the key relationship patterns:

@startuml
' --- Abstrakte Basisklassen ---
abstract class Person {
  #name: String
  #email: String
  +&#123;abstract&#125; getInfo(): String
}

abstract class Produkt {
  #produktId: String
  #name: String
  #preis: double
  +&#123;abstract&#125; 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 {
  +&#123;abstract&#125; 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 Implementation of Key Relationships

// --- Inheritance and Abstraction ---
public abstract class Person {
    protected String name;
    protected String email;
    
    public abstract String getInfo();
    
    // Shared method
    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;
    }
}

// --- Composition: Order with line items ---
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;
        
        // Composition: Line items are created with the order
        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 - can only be called by Bestellung
    private Bestellposition(Produkt produkt, int menge) {
        this.produkt = produkt;
        this.menge = menge;
        this.einzelpreis = produkt.calculatePrice();
    }
    
    public double getGesamtpreis() {
        return einzelpreis * menge;
    }
}

// --- Aggregation: Company with employees ---
public class Firma {
    private String name;
    private List<Mitarbeiter> mitarbeiter = new ArrayList<>();
    
    public void addMitarbeiter(Mitarbeiter mitarbeiter) {
        this.mitarbeiter.add(mitarbeiter);
        // Employee exists independently of the company
    }
    
    public void removeMitarbeiter(Mitarbeiter mitarbeiter) {
        this.mitarbeiter.remove(mitarbeiter);
        // Employee can continue to exist
    }
}

// --- Dependencies: 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) {
        // Dependency: Use ZahlungService
        if (zahlungService.processPayment(bestellung.berechneGesamtpreis())) {
            
            // Dependency: Use LagerService
            lagerService.reserviereProdukte(bestellung.getPositionen());
            
            // Dependency: Use EmailService
            emailService.sendBestellbestaetigung(bestellung);
        }
    }
}

// --- Interface Implementation ---
public interface Zahlungsart {
    boolean bezahlen(double betrag);
}

public class Kreditkarte implements Zahlungsart {
    private String kartenNr;
    private Date gueltigBis;
    
    @Override
    public boolean bezahlen(double betrag) {
        // Credit card payment implementation
        System.out.println("Zahlung mit Kreditkarte: " + betrag + "€");
        return true;
    }
}

public class PayPal implements Zahlungsart {
    private String email;
    
    @Override
    public boolean bezahlen(double betrag) {
        // PayPal payment implementation
        System.out.println("Zahlung mit PayPal: " + betrag + "€");
        return true;
    }
}

Summary of Relationships in This Example:

Relationship TypeE-Commerce ExampleMeaning
InheritanceCustomer extends PersonA customer is a person
ImplementationCreditCard implements PaymentMethodA credit card is a payment method
AssociationCustomer -- OrderA customer has orders
AggregationCompany o-- EmployeeA company employs employees
CompositionOrder *-- OrderLineAn order contains line items
DependencyOrder ..> PaymentServiceAn order uses PaymentService

This example demonstrates how all relationship types work together in a real system and how they translate into Java code.

Typical Exam Questions Your Interviewer Might Ask

1. What’s the difference between association and aggregation in UML?

Answer: The difference lies in object lifetime and dependency. In an association, both objects have independent lifespans and can exist without each other. In aggregation (empty diamond o—), the whole has a “has-a” relationship to its parts, but those parts can still exist independently. Example: A department has employees, but employees can exist without a department.

2. Explain the difference between aggregation and composition.

Answer: Aggregation (empty diamond o—) describes a “has-a” relationship where parts can exist independently of the whole. Composition (filled diamond *—) is stronger: parts cannot exist without the whole. In composition, part objects are typically created and destroyed together with the whole. Example: Car contains Engine (composition) versus Faculty employs Professors (aggregation).

3. How are multiplicities shown in UML class diagrams?

Answer: Multiplicities appear at relationship endpoints and indicate how many instances can exist. Key notations: 1 (exactly one), 0..1 (zero or one), * (many, zero or more), 1..* (at least one), 2..5 (between two and five). Example: A customer can have 0..* orders, and each order belongs to exactly one customer.

4. What is a dependency and when should you use it?

Answer: A dependency (dashed line with arrow ..>) shows a “uses” relationship between classes. It’s temporary and weaker than association. Typical uses: when one class uses another as a method parameter, a local variable, or a return type. Example: An Order uses a PriceCalculator to compute the total amount.

5. How does inheritance differ from implementation?

Answer: Inheritance (solid line with closed arrow |<|--) represents an “is-a” relationship between classes. A class inherits attributes and methods from another. Implementation (dashed line with closed arrow ..|>) shows that a class realizes an interface. Use inheritance for classes and implementation for interfaces. Example: Car inherits from Vehicle, Car implements Drivable.

6. What are abstract classes and how are they shown in UML?

Answer: Abstract classes cannot be instantiated and typically contain abstract methods without implementation. In UML, they are marked with the stereotype <<abstract>> or written in italics. Abstract methods are also italicized. They serve as templates for concrete classes and enable code reuse.

7. Explain the Single Responsibility Principle (SRP) in UML.

Answer: The Single Responsibility Principle states that each class should have only one reason to change. In UML, this appears as classes with focused attributes and methods. Poor design: a UserManager class that stores users, sends emails, and logs activity. Better design: separate UserService, EmailService, and LoggingService classes.

8. What is a qualified association and when is it used?

Answer: A qualified association uses a qualifier key to uniquely identify an object in a collection. It’s used when one object accesses another through a specific key. In UML, the qualifier appears in a rectangle on the association line. Example: A Map uses a String key to access Object values.

9. How are interfaces represented in UML class diagrams?

Answer: Interfaces are marked with the stereotype <<interface>> or shown as interface symbols. They contain only abstract methods (italicized) and no implementations. Classes implementing an interface use the implementation relationship (dashed line with closed arrow ..|>). Example: Flyable interface with methods fly() and land().

10. What are packages in UML and why are they used?

Answer: Packages group related classes and provide namespaces. They help organize large systems and reduce complexity. In UML, packages appear as folders with the package name. Dependencies between packages show which packages use others. Example: com.example.model, com.example.service, com.example.repository.

11. Describe the Observer Pattern in UML.

Answer: The Observer Pattern defines a one-to-many relationship. A Subject notifies multiple Observers when its state changes. In UML: Subject interface with attach(), detach(), and notify() methods. Observer interface with an update() method. Concrete classes implement these interfaces, and the Subject maintains a list of observers.

12. What is the God Object anti-pattern and how do you avoid it?

Answer: The God Object anti-pattern describes a class that has taken on too many responsibilities and grown too large. It violates the Single Responsibility Principle. Avoid it by splitting functionality into specialized classes with clear concerns. Example: Instead of one bloated UserManager, create separate UserService, OrderService, and PaymentService classes.

13. How are circular dependencies avoided in UML?

Answer: Circular dependencies occur when class A depends on B, B depends on C, and C depends on A. Avoid them through Dependency Injection with interfaces, introducing a shared dependency, or restructuring the architecture. In UML, circular dependencies appear as dashed arrows forming a cycle.

14. What’s the difference between class and object diagrams?

Answer: Class diagrams show the static structure with classes, attributes, methods, and relationships. Object diagrams show concrete instances of classes with their current values and relationships at a specific moment. Class diagrams are blueprints; object diagrams are snapshots of a running system.

15. Explain the Open/Closed Principle (OCP) in UML.

Answer: The Open/Closed Principle states that software should be open for extension but closed for modification. In UML, this appears through the use of interfaces and abstract classes. Add new functionality through new implementations without changing existing code. Example: Shape interface with implementations like Circle, Rectangle, and Triangle.

16. What are stereotypes in UML and what are they used for?

Answer: Stereotypes extend UML notation for specific purposes. They appear in double angle brackets. Common stereotypes: <<entity>> for data classes, <<controller>> for controllers, <<service>> for services, <<utility>> for helper classes, and <<REST>> for REST APIs.

17. How is the Factory Pattern represented in UML?

Answer: The Factory Pattern uses a Factory class or interface to create objects. In UML: a Product interface, concrete Product classes, a Factory interface with a createProduct() method, and a ConcreteFactory class that implements the factory interface and creates concrete products.

18. What’s the difference between composition and aggregation in terms of lifetime?

Answer: In composition, part objects depend on the whole’s lifetime—they’re created and destroyed with it. In aggregation, parts have independent lifespans and can outlive the whole. Composition: Car owns Engine (engine dies with car). Aggregation: Department employs Employees (employees survive the department).

19. Explain the Liskov Substitution Principle (LSP) in UML.

Answer: The Liskov Substitution Principle states that subtypes must be substitutable for their base types without changing program behavior. In UML, this shows through correct inheritance hierarchies. Violation example: Penguin inherits from Bird with a fly() method but can’t fly. Solution: split interfaces into FlyingBird and NonFlyingBird.

20. How are visibility modifiers shown in UML?

Answer: Visibility modifiers appear before attributes and methods: + for public (accessible everywhere), - for private (only within the class), # for protected (class and subclasses), ~ for package (only within the package). Example: -name: String (private attribute), +getName(): String (public method).

21. What is an association class and when is it used?

Answer: An association class models an m:n relationship with additional attributes. It’s connected to the association line and holds attributes belonging to the relationship. Example: Student and Course have an m:n relationship; the Enrollment association class contains semester and grade.

22. How are abstract methods shown in UML?

Answer: Abstract methods are displayed in italic or marked with the stereotype <<abstract>>. They have no implementation and must be overridden by subclasses. Example: +{abstract} draw(): void in an abstract Shape class. Concrete classes like Circle and Rectangle must implement this method.

23. What’s the difference between structural and behavioral diagrams?

Answer: Structural diagrams show the static structure of the system (classes, objects, packages, components). Behavioral diagrams show dynamic behavior (use cases, activities, sequences, states). Class diagrams are structural, while sequence diagrams or activity diagrams show behavior.

24. Explain the Singleton Pattern in UML.

Answer: The Singleton Pattern ensures only one instance of a class exists. In UML: a class with a private constructor, a static instance variable, and a static getInstance() method. The class controls the creation and management of its single instance.

25. Prepare for a typical UML exam question.

Answer: Question: “Draw a class diagram for a simple banking system with customers, accounts, and transactions.” Answer structure: 1. Identify classes (Customer, Account, Transaction), 2. Define attributes (Customer: name, address; Account: accountNumber, balance; Transaction: amount, date), 3. Model relationships (Customer 1..* Account, Account 1..* Transaction), 4. Add methods (deposit(), withdraw(), transfer()), 5. Set visibility and note multiplicities. This structured approach demonstrates systematic thinking and UML competency.

Continue Your UML Learning Path

All UML articles are now complete. Return to the first article: UML Diagrams Overview – Class Diagrams, Sequence Diagrams, Activity Diagrams & Use Cases.

Back to Blog
Share:

Related Posts