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
+{abstract} fahren(): void
+{abstract} 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 {
+{abstract} fliegen(): void
+{abstract} landen(): void
}
interface Schwimmend {
+{abstract} schwimmen(): void
+{abstract} tauchen(): void
}
' Klasse implementiert ein Interface
class Vogel implements Fliegend {
-art: String
+fliegen(): void
+landen(): void
}
' Klasse implementiert mehrere Interfaces
class Ente implements Fliegend, Schwimmend {
-rasse: String
+fliegen(): void
+landen(): void
+schwimmen(): void
+tauchen(): void
}
' Abstrakte Klasse implementiert Interface
abstract class Wasserlebewesen implements Schwimmend {
-lebensraum: String
+{abstract} atmen(): void
+schwimmen(): void
+tauchen(): void
}
class Fisch extends Wasserlebewesen {
-art: String
+atmen(): void
}
@enduml
Реализация интерфейсов на Java
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
+{abstract} getInfo(): String
+geburtstagFeiern(): void
}
abstract class Mitarbeiter extends Person {
#mitarbeiterNr: String
#gehalt: double
+{abstract} arbeiten(): void
+gehaltErhoehen(prozent: double): void
}
' Konkrete Klassen
class Student extends Person {
-matrikelNr: String
-fach: String
+studieren(): void
+pruefungAblegen(): boolean
+getInfo(): String
}
class Dozent extends Mitarbeiter {
-fachbereich: String
+arbeiten(): void
+vorlesungHalten(): void
+klausurKorrigieren(): void
+getInfo(): String
}
class Verwaltungsmitarbeiter extends Mitarbeiter {
-abteilung: String
+arbeiten(): void
+dokumenteVerwalten(): void
+getInfo(): String
}
' Weitere Klassen
class Kurs {
-titel: String
-credits: int
-dozent: Dozent
+getTitel(): String
+setDozent(dozent: Dozent): void
}
class Pruefung {
-datum: Date
-note: double
-student: Student
-kurs: Kurs
+noteEintragen(note: double): void
+bestanden(): boolean
}
class Raum {
-nummer: String
-kapazitaet: int
+gebucht(): boolean
+reservieren(): void
}
' Beziehungen
Person <|-- Student
Person <|-- Mitarbeiter
Mitarbeiter <|-- Dozent
Mitarbeiter <|-- Verwaltungsmitarbeiter
Dozent "1" -- "*" Kurs : unterrichtet >
Student "n" -- "n" Kurs : belegt >
Student "n" -- "n" Pruefung : schreibt >
Kurs "1" -- "n" Pruefung : hat >
Kurs "n" -- "1" Raum : findetStattIn >
' Aggregation
Fakultaet o-- "*" Dozent : beschaeftigt >
Fakultaet o-- "*" Student : immatrikuliert >
' Komposition
Universitaet *-- "*" Fakultaet : besitzt >
@enduml
Реализация системы университета на Java
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 до 50,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
+{abstract} draw(): void
+{abstract} getArea(): double
+move(x: double, y: double): void
}
' Интерфейс
interface Movable {
+{abstract} move(dx: double, dy: double): void
+{abstract} getPosition(): Point
}
interface Resizable {
+{abstract} scale(factor: double): void
+{abstract} 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 {
+{abstract} update(subject: Subject): void
}
interface Subject {
+{abstract} attach(observer: Observer): void
+{abstract} detach(observer: Observer): void
+{abstract} notify(): void
}
class ConcreteObserver implements Observer {
-state: String
+update(subject: Subject): void
}
class ConcreteSubject implements Subject {
-state: String
-observers: List<Observer>
+attach(observer: Observer): void
+detach(observer: Observer): void
+notify(): void
+getState(): String
+setState(state: String): void
}
ConcreteSubject --> ConcreteObserver : notifies >
@enduml
Factory Pattern
@startuml
interface Product {
+{abstract} operation(): void
}
class ConcreteProductA implements Product {
+operation(): void
}
class ConcreteProductB implements Product {
+operation(): void
}
interface Factory {
+{abstract} createProduct(type: String): Product
}
class ConcreteFactory implements Factory {
+createProduct(type: String): Product
}
ConcreteFactory ..> ConcreteProductA : creates >
ConcreteFactory ..> ConcreteProductB : creates >
@enduml
5. Пакеты и пространства имён
Пакеты группируют связанные классы и предоставляют пространства имён.
@startuml
package "com.example.model" {
class User {
-id: String
-name: String
}
class Product {
-id: String
-name: String
-price: double
}
}
package "com.example.service" {
class UserService {
-userRepository: UserRepository
+createUser(userData: UserData): User
+findUser(id: String): User
}
class ProductService {
-productRepository: ProductRepository
+createProduct(productData: ProductData): Product
}
}
package "com.example.repository" {
interface UserRepository {
+{abstract} save(user: User): void
+{abstract} findById(id: String): User
}
interface ProductRepository {
+{abstract} save(product: Product): void
+{abstract} findById(id: String): Product
}
}
' Зависимости между пакетами
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
Типичные экзаменационные задания
- Нарисуйте диаграммы классов для данных сценариев
- Определите типы связей
- Реализуйте связи на Java
- Отрефакторьте плохие проекты
- Объясните отличия между агрегацией и композицией
Решения экзаменационных задач
Задание 1: Нарисовать диаграмму классов библиотечной системы
Сценарий: Библиотека управляет книгами, читателями и процессами выдачи. Читатели могут брать и возвращать книги.
Шаги решения:
- Выделить классы: Библиотека, Книга, Читатель, Выдача
- Определить атрибуты:
- Книга: ISBN, название, автор, год
- Читатель: номер читателя, имя, адрес
- Выдача: дата выдачи, дата возврата
- Смоделировать связи:
- Читатель 0..* Выдач
- Книга 0..* Выдач
- Библиотека 1..* Книг
- Добавить методы:
- Книга: выдать(), вернуть()
- Читатель: выдать(), вернуть()
- Выдача: продлить()
@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
+{abstract} getInfo(): String
}
abstract class Produkt {
#produktId: String
#name: String
#preis: double
+{abstract} 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 {
+{abstract} bezahlen(betrag: double): boolean
}
class Kreditkarte implements Zahlungsart {
-kartenNr: String
-gueltigBis: Date
+bezahlen(betrag: double): boolean
}
class PayPal implements Zahlungsart {
-email: String
-passwort: String
+bezahlen(betrag: double): boolean
}
' --- Отношения ---
' Наследование
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-диаграмм – диаграмма классов, диаграмма последовательности, диаграмма активности и диаграмма прецедентов.

