Skip to content
IRC-CodingIRC-Coding
UMLдиаграммы классовотношенияAssociationAggregationCompositionDependencyInheritancePlantUML

UML диаграммы классов: связи и отношения

UML диаграммы классов с Association, Aggregation, Composition, Dependency и Inheritance. Примеры PlantUML.

S

schutzgeist

34 min read
UML диаграммы классов: связи и отношения

UML диаграммы классов: связи, ассоциация, агрегация и композиция

UML диаграммы классов являются основным инструментом для визуализации архитектуры объектно-ориентированного программного обеспечения. Связи между классами определяют структуру и поведение системы.

Во время своего обучения я уделял этим диаграммам недостаточно внимания, однако они критически важны при подготовке к экзаменам. Позже они помогают разобраться в проектах и объяснить их другим людям.

Если ты проходишь обучение по специальности системный администратор или программист, то читай здесь каждый день по одной странице случайным образом. Даже если не все темы покажутся понятными, ты все равно будешь накапливать знания к моменту экзамена.

Основы UML диаграмм классов

Представление класса

Простой класс с интерфейсом

    @startuml
' Базовый класс с атрибутами и методами
class Student {
-matrikelNr: int
-name: String
-semester: int
+getName(): String
+setMatrikelNr(nr: int): void
+studieren(): void
}

' Интерфейс с методами
interface Lernbar {
+lernen(): void
+pruefungAblegen(): boolean
}

' Интерфейс
interface Lernfaehig {
+{abstract} lernen(fach: String): void
+{abstract} pruefungAblegen(fach: String): boolean
}

' Student реализует Lernbar
Student ..|> Lernbar
@enduml
  

Модификаторы видимости

СимволЗначениеОписание
+publicДоступ отовсюду
-privateДоступ только внутри класса
#protectedДоступ внутри класса и подклассов
~packageДоступ только внутри пакета

Типы связей в UML

1. Ассоциация (Association)

Ассоциация описывает структурные связи между классами.

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

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

' Простая ассоциация
Student "1" -- "n" Kurs : belegt >

' Ассоциация с ролями и атрибутами
Student "1" -- "*" Note : \
  note : hat
  note : noteWert: double
  note : datum: Date

' Ориентированная ассоциация
Student "1" -> "*" Projekt : leitet >

@enduml

Реализация ассоциаций на Java

public class AssociationExamples {
    
    // Связь многие-ко-многим
    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 и 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 и 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 и 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 и Setter
        public String getName() { return name; }
        public Student getLeiter() { return leiter; }
    }
}

2. Агрегация

Агрегация представляет собой специальный вид ассоциации, при котором объект-часть может существовать независимо от целого.

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

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

' Агрегация (пустой ромб)
Abteilung o-- "*" Mitarbeiter : hat >

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

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

' Агрегация
Universitaet o-- "*" Fakultaet : besitzt >

@enduml

Реализация агрегации на Java

public class AggregationExamples {
    
    // Отдел может существовать без сотрудников
    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);
        }
        
        // Сотрудники могут существовать без отдела
        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; // Необязательно
        
        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;
            }
        }
        
        // Сотрудники существуют независимо от отдела
        public String getName() { return name; }
        public Abteilung getAbteilung() { return abteilung; }
    }
}

3. Композиция (Composition)

Композиция представляет собой более строгую форму агрегации, при которой объект-часть не может существовать без целого.

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

' Композиция (заполненный ромб)
Auto *-- "1" Motor : hat >
Auto *-- "4" Reifen : hat >

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

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

' Композиция
Haus *-- "*" Zimmer : hat >

@enduml

Java-реализация композиции

public class CompositionExamples {
    
    // Motor существует только в автомобиле
    public class Auto {
        private String marke;
        private String modell;
        private Motor motor; // Существует только с автомобилем
        private List<Reifen> reifen = new ArrayList<>(); // Существуют только с автомобилем
        
        public Auto(String marke, String modell) {
            this.marke = marke;
            this.modell = modell;
            this.motor = new Motor(200, "Diesel"); // Мотор создаётся внутри автомобиля
            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;
        
        // Приватный конструктор - доступен только из Auto
        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 Reifen(int groesse) {
            this.groesse = groesse;
            this.druck = 2.5; // Стандартное давление
        }
        
        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. Зависимость (Dependency)

Зависимости показывают отношения использования между классами.

@startuml
class Bestellung {
  -datum: Date
  -gesamtsumme: double
  +berechneGesamtsumme(): double
}

class PreisRechner {
  +berechnePreis(produkte: List<Produkt>): double
  +berechneRabatt(betrag: double, rabatt: double): double
}

' Зависимость (пунктирная линия)
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-реализация зависимостей

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); }
    }
    
    // Классы зависимостей
    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. Наследование (Inheritance)

Наследование показывает отношения “является” между классами.

@startuml
' Абстрактный базовый класс
abstract class Fahrzeug {
  #marke: String
  #baujahr: int
  
  +&#123;abstract&#125; fahren(): void
  +&#123;abstract&#125; bremsen(): void
  +getInfo(): String
}

' Конкретные подклассы
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-реализация наследования

public class InheritanceExamples {
    
    // Абстрактный базовый класс
    public abstract class Fahrzeug {
        protected String marke;
        protected int baujahr;
        
        public Fahrzeug(String marke, int baujahr) {
            this.marke = marke;
            this.baujahr = baujahr;
        }
        
        // Абстрактные методы
        public abstract void fahren();
        public abstract void bremsen();
        
        // Конкретный метод
        public String getInfo() {
            return marke + " aus " + baujahr;
        }
        
        // Getter
        public String getMarke() { return marke; }
        public int getBaujahr() { return baujahr; }
    }
    
    // Конкретные подклассы
    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. Реализация интерфейсов

Реализация показывает связь между интерфейсами и классами, которые их реализуют.

@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

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

Сложные диаграммы классов

Полный пример: система университета

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

' 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

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 для диаграмм классов UML

1. Согласованные соглашения об именовании

// Хорошие соглашения об именовании
public class NamingConventions {
    
    // Классы: существительные, PascalCase
    public class StudentManagementSystem {}
    
    // Методы: глаголы, camelCase
    public void calculateGrade() {}
    public void validateInput() {}
    
    // Переменные: camelCase, описательные
    private List<Student> enrolledStudents;
    private double averageGrade;
    
    // Константы: UPPER_CASE
    public static final int MAX_STUDENTS_PER_COURSE = 100;
    
    // Интерфейсы: прилагательные или возможности, PascalCase
    public interface Printable {}
    public interface Serializable {}
    public interface StudentRepository {}
}

2. Разделение ответственности

@startuml
' Правильное разделение по ответственности
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
}

' Ясные зависимости
UserService ..> UserRepository : использует >
UserService ..> EmailService : использует >

@enduml

3. Избегание циклических зависимостей

@startuml
' Плохо: циклические зависимости
class A {
  -b: B
  +doSomething(): void
}

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

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

A --> B
B --> C
C --> A ' Цикл!

' Хорошо: без циклов
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

Продвинутые концепции и паттерны

1. Мультипликативность и кардинальность

Мультипликативность указывает, сколько экземпляров класса может участвовать в отношении.

@startuml
' Различные варианты мультипликативности
class Kunde {
  -kundenNr: String
}

class Bestellung {
  -bestellNr: String
}

class Produkt {
  -produktId: String
}

' Отношение один-ко-многим
Kunde "1" -- "0..*" Bestellung : gibtAuf >

' Отношение многие-ко-многим через класс ассоциации
Bestellung "n" -- "m" Bestellposition : enthält >
Produkt "1" -- "0..*" Bestellposition : hat >

class Bestellposition {
  -menge: int
  -einzelpreis: double
  +getGesamtpreis(): double
}

@enduml

Обозначения мультипликативности:

  • 1 ровно один
  • 0..1 ноль или один
  • * много (ноль или больше)
  • 1..* минимум один
  • 2..5 от 2 до 5
  • 0,1 ноль или один (альтернативное обозначение)

2. Квалифицированные ассоциации

Квалифицированные ассоциации используют ключ для уникальной идентификации объектов.

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

' Квалифицированная ассоциация
Map "1" -- "*" Object : entries >
{key} String

@enduml

3. Абстрактные классы и интерфейсы

Абстрактные классы могут содержать абстрактные методы и не могут быть инстанцированы.

@startuml
' Абстрактный класс
abstract class Shape {
  #color: String
  #position: Point
  +&#123;abstract&#125; draw(): void
  +&#123;abstract&#125; getArea(): double
  +move(x: double, y: double): void
}

' Интерфейс
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
}

' Конкретные классы
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 в 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. Пакеты и пространства имён

Пакеты группируют связанные классы и предоставляют пространства имён.

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

' Зависимости между пакетами
com.example.service ..> com.example.model : uses >
com.example.service ..> com.example.repository : uses >

@enduml

6. Стереотипы и обозначения

Стереотипы расширяют нотацию UML для конкретных целей.

@startuml
' Стереотипы
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
}

' Специальные обозначения
class API <<REST>> {
  +{GET} /users: List<User>
  +{POST} /users: User
  +{PUT} /users/{id}: User
}

@enduml

Концепции, необходимые для экзамена

Важные типы связей

| Тип связи | Символ | Значение | Время жизни |
|-----------|--------|----------|------------|
| Ассоциация | ---- | Структурная связь | Независимо |
| Агрегация | o-- | "содержит" (частичная) | Независимо |
| Композиция | *-- | "содержит" (полная) | Зависимо |
| Зависимость | ..> | "использует" | Временно |
| Наследование | <\|-- | "является" | Постоянно |
| Реализация | ..\|> | "реализует" | Постоянно |

SOLID-принципы в UML

Single Responsibility Principle (SRP)

@startuml
' Неправильно: класс с множественными ответственностями
class BadUserManager {
  -userData: UserData
  +saveUser(): void
  +sendWelcomeEmail(): void
  +logActivity(): void
  +validateInput(): void
}

' Правильно: ответственности разделены
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
' Открыт для расширения, закрыт для изменения
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
}

' Новые фигуры добавляются без изменения существующего кода

@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 ' Нарушает LSP!
  +makeSound(): void
}

' Лучше: разделить интерфейсы
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

Антипаттерны и способы их избежания

Антипаттерн God Object

@startuml
' Антипаттерн: объект, делающий всё
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
}

' Правильно: специализированные классы
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

@startuml
' Антипаттерн: циклические зависимости
class ServiceA {
  -serviceB: ServiceB
  +doA(): void
}

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

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

ServiceA --> ServiceB
ServiceB --> ServiceC
ServiceC --> ServiceA ' Цикл!

' Решение: Dependency Injection через интерфейсы
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

Типичные экзаменационные задания

  1. Нарисуйте диаграммы классов для данных сценариев
  2. Определите типы связей
  3. Реализуйте связи на Java
  4. Отрефакторьте плохие проекты
  5. Объясните отличия между агрегацией и композицией

Решения экзаменационных задач

Задание 1: Нарисовать диаграмму классов библиотечной системы

Сценарий: Библиотека управляет книгами, читателями и процессами выдачи. Читатели могут брать и возвращать книги.

Шаги решения:

  1. Выделить классы: Библиотека, Книга, Читатель, Выдача
  2. Определить атрибуты:
    • Книга: ISBN, название, автор, год
    • Читатель: номер читателя, имя, адрес
    • Выдача: дата выдачи, дата возврата
  3. Смоделировать связи:
    • Читатель 0..* Выдач
    • Книга 0..* Выдач
    • Библиотека 1..* Книг
  4. Добавить методы:
    • Книга: выдать(), вернуть()
    • Читатель: выдать(), вернуть()
    • Выдача: продлить()
@startuml
class Библиотека {
  -название: String
  +добавитьКнигу(книга: Книга): void
  +найтиКнигу(isbn: String): Книга
}

class Книга {
  -isbn: String
  -название: String
  -автор: String
  -год: int
  +выдать(): void
  +вернуть(): void
}

class Читатель {
  -номерЧитателя: String
  -имя: String
  -адрес: String
  +выдать(книга: Книга): void
  +вернуть(книга: Книга): void
}

class Выдача {
  -датаВыдачи: Date
  -датаВозврата: Date
  +продлить(дни: int): void
}

Библиотека "1" -- "*" Книга : содержит >
Читатель "n" -- "n" Выдача : имеет >
Книга "n" -- "n" Выдача : участвует >

@enduml

Задание 2: Определить типы связей

Сценарий: Автомобиль имеет двигатель и четыре колеса. Сотрудник принадлежит отделу. Студент посещает курсы.

Решение:

  • Автомобиль-двигатель-колеса: Композиция (*—) - части существуют только вместе с целым
  • Сотрудник-отдел: Агрегация (o—) - сотрудник может существовать без отдела
  • Студент-курс: Ассоциация (----) - независимые объекты со связью

Задание 3: Реализовать связи на Java

Сценарий: Реализуйте композицию между классами Автомобиль и Двигатель.

Решение:

public class Автомобиль {
    private String марка;
    private Двигатель двигатель; // Композиция
    
    public Автомобиль(String марка) {
        this.марка = марка;
        this.двигатель = new Двигатель(200); // Двигатель создаётся в Автомобиле
    }
    
    public void ехать() {
        двигатель.запустить();
        System.out.println(марка + " едет");
    }
}

public class Двигатель {
    private int мощность;
    
    // Приватный конструктор - вызывается только из Автомобиля
    private Двигатель(int мощность) {
        this.мощность = мощность;
    }
    
    public void запустить() {
        System.out.println("Двигатель мощностью " + мощность + " л.с. запущен");
    }
}

Задание 4: Отрефакторить плохой проект

Плохой проект: Класс ВсёВОдном делает всё сразу. Хороший проект: Ответственности разделены.

До:

class ВсёВОдном {
    // Управление пользователями
    private List<Пользователь> пользователи;
    public void сохранитьПользователя(Пользователь пользователь) {}
    
    // Email сервис
    public void отправитьПочту(Пользователь пользователь, String сообщение) {}
    
    // Логирование
    public void записать(String сообщение) {}
}

После:

class ПользовательскийСервис {
    private ПользовательскийРепозиторий репозиторий;
    private СервисПочты сервисПочты;
    
    public void сохранитьПользователя(Пользователь пользователь) {
        репозиторий.сохранить(пользователь);
        сервисПочты.отправитьПриветствие(пользователь);
    }
}

class СервисПочты {
    public void отправитьПриветствие(Пользователь пользователь) {}
}

class ПользовательскийРепозиторий {
    public void сохранить(Пользователь пользователь) {}
}

Задание 5: Объяснить отличие агрегации от композиции

Агрегация (пустая ромб o—):

  • связь “содержит”
  • части могут существовать независимо
  • пример: отдел содержит сотрудников
  • жизненные циклы независимы

*Композиция (заполненная ромб —):

  • связь “содержит”
  • части зависят от целого
  • пример: автомобиль содержит двигатель
  • жизненные циклы связаны

Отличие в Java:

// Агрегация
class Отдел {
    private List<Сотрудник> сотрудники = new ArrayList<>();
    
    public void добавитьСотрудника(Сотрудник сотрудник) {
        сотрудники.add(сотрудник); // Сотрудник существует независимо
    }
}

// Композиция
class Автомобиль {
    private Двигатель двигатель = new Двигатель(); // Двигатель создаётся с Автомобилем
    
    public Автомобиль() {
        // Двигатель существует только с Автомобилем
    }
}

Резюме

UML диаграммы классов необходимы для архитектуры программного обеспечения:

  • Ассоциация: Структурные связи между классами
  • Агрегация: Отношение “содержит”, части могут существовать независимо
  • Композиция: Отношение “содержит”, части зависят от целого
  • Зависимость: Использование классов или интерфейсов
  • Наследование: Отношение “является” между классами
  • Реализация: Реализация интерфейса

Хороший дизайн классов следует принципам SOLID и избегает циклических зависимостей.

Практический пример: система электронной коммерции со всеми типами ассоциаций

Ниже представлена полная система электронной коммерции, демонстрирующая все важные типы связей:

@startuml
' --- Абстрактные базовые классы ---
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
}

' --- Конкретные классы ---
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
}

' --- Система заказов ---
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
}

' --- Сервисы ---
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
}

' --- Интерфейсы ---
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
}

' --- Отношения ---
' Наследование
Person <|-- Kunde
Person <|-- Mitarbeiter
Produkt <|-- Buch
Produkt <|-- Elektronik

' Реализация
Zahlungsart <|.. Kreditkarte
Zahlungsart <|.. PayPal

' Ассоциации
Kunde "1" -- "0..*" Bestellung : gibtAuf >
Bestellung "1" -- "1..*" Bestellposition : enthält >
Produkt "1" -- "0..*" Bestellposition : istIn >

' Агрегация (Сотрудники могут существовать независимо от компании)
Firma o-- "*" Mitarbeiter : beschaeftigt >
Kunde o-- "*" ZahlungService : nutzt >

' Композиция (Позиции заказа существуют только с заказом)
Bestellung *-- "1..*" Bestellposition : hat >

' Зависимости
Bestellung ..> ZahlungService : verwendet >
Bestellung ..> EmailService : benachrichtigt >
WarenkorbService ..> Produkt : verwaltet >
WarenkorbService ..> Kunde : gehoertZu >

@enduml

Реализация на Java основных типов связей

// --- Наследование и абстракция ---
public abstract class Person {
    protected String name;
    protected String email;
    
    public abstract String getInfo();
    
    // Общая метода
    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;
    }
}

// --- Композиция: Заказ с позициями ---
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;
        
        // Композиция: позиции создаются вместе с заказом
        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;
    
    // Приватный конструктор - доступен только для класса Bestellung
    private Bestellposition(Produkt produkt, int menge) {
        this.produkt = produkt;
        this.menge = menge;
        this.einzelpreis = produkt.calculatePrice();
    }
    
    public double getGesamtpreis() {
        return einzelpreis * menge;
    }
}

// --- Агрегация: Компания и сотрудники ---
public class Firma {
    private String name;
    private List<Mitarbeiter> mitarbeiter = new ArrayList<>();
    
    public void addMitarbeiter(Mitarbeiter mitarbeiter) {
        this.mitarbeiter.add(mitarbeiter);
        // Сотрудник существует независимо от компании
    }
    
    public void removeMitarbeiter(Mitarbeiter mitarbeiter) {
        this.mitarbeiter.remove(mitarbeiter);
        // Сотрудник может продолжать существовать
    }
}

// --- Зависимости: Сервисы ---
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) {
        // Зависимость: использование ZahlungService
        if (zahlungService.processPayment(bestellung.berechneGesamtpreis())) {
            
            // Зависимость: использование LagerService
            lagerService.reserviereProdukte(bestellung.getPositionen());
            
            // Зависимость: использование EmailService
            emailService.sendBestellbestaetigung(bestellung);
        }
    }
}

// --- Реализация интерфейса ---
public interface Zahlungsart {
    boolean bezahlen(double betrag);
}

public class Kreditkarte implements Zahlungsart {
    private String kartenNr;
    private Date gueltigBis;
    
    @Override
    public boolean bezahlen(double betrag) {
        // Реализация оплаты кредитной картой
        System.out.println("Zahlung mit Kreditkarte: " + betrag + "€");
        return true;
    }
}

public class PayPal implements Zahlungsart {
    private String email;
    
    @Override
    public boolean bezahlen(double betrag) {
        // Реализация оплаты через PayPal
        System.out.println("Zahlung mit PayPal: " + betrag + "€");
        return true;
    }
}

Обзор связей в этом примере:

Тип связиПример в электронной коммерцииЗначение
НаследованиеКлиент extends ЧеловекКлиент является человеком
РеализацияКредитнаяКарта implements СпособОплатыКредитная карта является способом оплаты
АссоциацияКлиент -- ЗаказКлиент имеет заказы
АгрегацияКомпания o-- СотрудникКомпания имеет сотрудников
КомпозицияЗаказ *-- ПозицияЗаказаЗаказ содержит позиции
ЗависимостьЗаказ ..> СервисОплатыЗаказ использует СервисОплаты

Этот пример демонстрирует, как все типы связей работают вместе в реальной системе и как их реализовать на Java.

Типичные вопросы к экзамену

1. В чём разница между ассоциацией и агрегацией в UML?

Ответ: Основное отличие состоит в времени жизни и зависимости объектов. При ассоциации оба объекта имеют независимые жизненные циклы и могут существовать друг без друга. При агрегации (пустой ромб o—) целое имеет отношение “имеет” к частям, но части могут существовать независимо. Пример: отдел имеет сотрудников, но сотрудники могут существовать без отдела.

2. Объясните разницу между агрегацией и композицией.

Ответ: Агрегация (пустой ромб o—) описывает отношение “имеет”, в котором части могут существовать независимо от целого. Композиция (заполненный ромб *—) это более сильная форма связи, где части не могут существовать без целого. При композиции части обычно создаются вместе с целым и удаляются вместе с ним. Пример: автомобиль содержит двигатель (композиция) в сравнении с факультет имеет преподавателей (агрегация).

3. Как изображаются мультиплексности в UML диаграммах классов?

Ответ: Мультиплексности указываются на концах связей и показывают количество возможных экземпляров. Основные обозначения: 1 (ровно один), 0..1 (ноль или один), * (много, ноль или больше), 1..* (минимум один), 2..5 (от двух до пяти). Пример: клиент может иметь 0..* заказов, каждый заказ принадлежит ровно одному клиенту.

4. Что такое зависимость (Dependency) и когда её используют?

Ответ: Зависимость (пунктирная линия со стрелкой ..>) показывает отношение “использует” между классами. Это временная и более слабая связь, чем ассоциация. Типичное использование: класс использует другой класс как параметр методов, как локальную переменную или как тип возврата. Пример: заказ использует калькулятор цены для расчёта общей суммы.

5. Чем наследование отличается от реализации?

Ответ: Наследование (сплошная линия с закрытой стрелкой |<|--) показывает отношение “является” между классами. Класс наследует атрибуты и методы другого класса. Реализация (пунктирная линия с закрытой стрелкой ..|>) показывает, что класс реализует интерфейс. Наследование используется с классами, реализация с интерфейсами. Пример: автомобиль наследует транспортное средство, автомобиль реализует управляемый объект.

6. Что такое абстрактные классы и как они изображаются в UML?

Ответ: Абстрактные классы нельзя инстанцировать, они часто содержат абстрактные методы без реализации. В UML они обозначаются стереотипом <<abstract>> или записываются курсивом. Абстрактные методы также отображаются курсивом. Они служат шаблонами для конкретных классов и позволяют переиспользовать код.

7. Объясните принцип единственной ответственности (Single Responsibility Principle, SRP) в UML.

Ответ: Принцип единственной ответственности гласит, что каждый класс должен иметь только одну причину для изменения. В UML это проявляется через классы с сфокусированными атрибутами и методами. Плохой дизайн: класс UserManager сохраняет пользователей, отправляет письма и пишет логи. Хороший дизайн: отдельные классы UserService, EmailService, LoggingService.

8. Что такое квалифицированная ассоциация и когда её используют?

Ответ: Квалифицированная ассоциация использует квалификатор для уникальной идентификации объекта в наборе. Её применяют, когда один объект обращается к другому через определённый ключ. В UML квалификатор изображается прямоугольником на линии ассоциации. Пример: словарь использует строковый ключ для доступа к значениям.

9. Как интерфейсы изображаются в UML диаграммах классов?

Ответ: Интерфейсы обозначаются стереотипом <<interface>> или специальной нотацией для интерфейсов. Они содержат только абстрактные методы (курсив) и не имеют реализованных методов. Классы, реализующие интерфейс, используют связь реализации (пунктирная линия с закрытой стрелкой ..|>). Пример: интерфейс Летающий с методами летать() и приземляться().

10. Что такое пакеты в UML и зачем они нужны?

Ответ: Пакеты объединяют связанные классы и предоставляют пространства имён. Они помогают организовать большие системы и снижают сложность. В UML пакеты изображаются как папки с названием пакета. Зависимости между пакетами показывают, какие пакеты используют другие. Пример: com.example.model, com.example.service, com.example.repository.

11. Опишите паттерн Observer в UML.

Ответ: Паттерн Observer определяет отношение один ко многим между объектами. Subject уведомляет нескольких Observer об изменениях состояния. В UML: интерфейс Subject с методами attach(), detach(), notify(). Интерфейс Observer с методом update(). Конкретные классы реализуют эти интерфейсы, и Subject хранит список Observer’ов.

12. Что такое антипаттерн God Object и как его избежать?

Ответ: Антипаттерн God Object описывает класс с чрезмерной ответственностью, который стал слишком большим. Он нарушает принцип единственной ответственности. Избегайте его, разделяя функциональность на специализированные классы с чёткими обязанностями. Пример: вместо одного класса UserManager, который делает всё, создайте отдельные классы UserService, OrderService, PaymentService.

13. Как избежать циклических зависимостей в UML?

Ответ: Циклические зависимости возникают, когда класс A зависит от B, B зависит от C, а C зависит от A. Избегайте их через Dependency Injection с интерфейсами, введение общей зависимости или переструктурирование архитектуры. В UML это видно как пунктирные стрелки зависимостей, образующие цикл.

14. Какая разница между диаграммами классов и объектов?

Ответ: Диаграммы классов показывают статическую структуру с классами, атрибутами, методами и связями. Диаграммы объектов показывают конкретные экземпляры классов с их текущими значениями и связями в определённый момент времени. Диаграммы классов это чертежи, диаграммы объектов это снимки работающей системы.

15. Объясните принцип открытости/закрытости (Open/Closed Principle, OCP) в UML.

Ответ: Принцип открытости/закрытости гласит, что программное обеспечение должно быть открыто для расширения, но закрыто для изменения. В UML это проявляется через использование интерфейсов и абстрактных классов. Новая функциональность добавляется через новые реализации, без изменения существующего кода. Пример: интерфейс Shape с реализациями Circle, Rectangle, Triangle.

16. Что такое стереотипы в UML и для чего они нужны?

Ответ: Стереотипы расширяют нотацию UML для специальных целей. Они записываются в двойных угловых скобках. Распространённые стереотипы: <<entity>> для классов данных, <<controller>> для контроллеров, <<service>> для сервисов, <<utility>> для вспомогательных классов, <<REST>> для REST API.

17. Как паттерн Factory изображается в UML?

Ответ: Паттерн Factory использует класс или интерфейс Factory для создания объектов. В UML: интерфейс Product, конкретные классы Product, интерфейс Factory с методом createProduct(), и класс ConcreteFactory, реализующий Factory и создающий конкретные продукты.

18. В чём разница между композицией и агрегацией с точки зрения времени жизни?

Ответ: При композиции части зависят от времени жизни целого - они создаются вместе с целым и удаляются с ним. При агрегации части имеют независимый жизненный цикл и могут существовать после целого. Композиция: автомобиль содержит двигатель (двигатель “умирает” с автомобилем). Агрегация: отдел имеет сотрудников (сотрудники “переживают” отдел).

19. Объясните принцип подстановки Лисков (Liskov Substitution Principle, LSP) в UML.

Ответ: Принцип подстановки Лисков гласит, что подклассы должны корректно заменять своих предков без изменения поведения программы. В UML это проявляется через правильные иерархии наследования. Нарушение: пингвин наследует птицу с методом летать(), но не может летать. Решение: разделить на интерфейсы ЛетающаяПтица и НелетающаяПтица.

20. Как изображаются модификаторы видимости в UML?

Ответ: Модификаторы видимости указываются перед атрибутами и методами: + для public (доступно везде), - для private (только внутри класса), # для protected (класс и подклассы), ~ для package (только в пакете). Пример: -имя: String (приватный атрибут), +getИмя(): String (публичный метод).

21. Что такое класс ассоциации и когда его используют?

Ответ: Класс ассоциации используется для моделирования отношения многие ко многим с дополнительными атрибутами. Он связан с линией ассоциации и содержит атрибуты, принадлежащие самой связи. Пример: студент и курс имеют отношение многие ко многим, класс ассоциации запись содержит семестр и оценку.

22. Как абстрактные методы изображаются в UML?

Ответ: Абстрактные методы записываются курсивом или отмечаются стереотипом <<abstract>>. У них нет реализации, и подклассы должны их реализовать. Пример: +{abstract} draw(): void в абстрактном классе Shape. Конкретные классы вроде Circle и Rectangle должны реализовать этот метод.

23. Какая разница между структурными и поведенческими диаграммами?

Ответ: Структурные диаграммы показывают статическую структуру системы (классы, объекты, пакеты, компоненты). Поведенческие диаграммы показывают динамическое поведение (прецеденты, деятельность, последовательность, состояния). Диаграммы классов относятся к структурным, а диаграммы последовательности или деятельности к поведенческим.

24. Объясните паттерн Singleton в UML.

Ответ: Паттерн Singleton гарантирует, что от класса существует только один экземпляр. В UML: класс с приватным конструктором, статической переменной экземпляра и статическим методом getInstance(). Класс отвечает за создание и управление своим единственным экземпляром.

25. Подготовьтесь к типичному вопросу на экзамене по UML.

Ответ: Вопрос: “Нарисуйте диаграмму классов для простой банковской системы с клиентами, счётами и транзакциями.” Структура ответа: 1. Идентифицируйте классы (Customer, Account, Transaction), 2. Определите атрибуты (Customer: name, address; Account: accountNumber, balance; Transaction: amount, date), 3. Смоделируйте связи (Customer 1..* Accounts, Account 1..* Transactions), 4. Добавьте методы (deposit(), withdraw(), transfer()), 5. Установите видимость и отметьте мультиплексности. Такой подход демонстрирует систематический процесс и компетенцию в UML.

Продолжение пути обучения UML

Все статьи по UML теперь завершены. Вернитесь к первой статье: Обзор UML-диаграмм – диаграмма классов, диаграмма последовательности, диаграмма активности и диаграмма прецедентов.

Назад к блогу
Share:

Похожие статьи