Podstawy UML – diagramy klas

Mateusz Sowa

Mateusz Sowa

25-01-2023

(aktualizacja: 14-10-2026)

„Jeden obraz jest wart więcej niż tysiąc słów” – to motto przyświecało twórcom diagramów UML. Są to grafiki, które odwzorowują systemy informatyczne.

Podstawy UML – diagramy klas
UML (Unified Modeling Language) to ustandaryzowany język graficzny do opisywania budowy i działania systemów informatycznych, a diagram klas to jego najpopularniejszy rodzaj: pokazuje klasy (nazwę, pola, metody) oraz relacje między nimi. Relacji jest sześć – zależność, implementacja, dziedziczenie, asocjacja, agregacja i kompozycja – a ich siła rośnie od zwykłego „używa” do „składa się z i nie istnieje bez”.

Diagramy UML (Unified Modeling Language) to uniwersalna metoda opisu systemów informatycznych. Dzięki niej osoby zarządzające projektami mogą w jasny sposób definiować funkcjonalności i logikę aplikacji, a programiści – przenieść taki opis na działający kod. W tym artykule zajmiemy się najczęściej używanym z nich: diagramem klas. Służy on do projektowania struktury klas (pól i metod) oraz relacji między nimi, więc pomaga zaplanować logikę aplikacji i analizować wzorce projektowe, zanim napiszesz pierwszą linijkę kodu.

Przykładowy diagram klas, źródło: „Wzorce projektowe. Nowoczesny podręcznik” - Aleksander Shvets

Przykładowy diagram klas, źródło: Aleksander Shvets, „Wzorce projektowe. Nowoczesny podręcznik”. Do rysowania diagramów polecam darmowe narzędzie diagrams.net. Kod z przykładów możesz pobrać z tego repozytorium.

Co to jest UML i do czego służy?

UML to język graficzny, a nie język programowania. Opisuje system za pomocą ustandaryzowanych symboli – prostokątów, strzałek i linii – więc każdy, kto zna notację, odczyta ten sam diagram tak samo, niezależnie od używanej technologii. Specyfikacją UML zajmuje się organizacja OMG.

Najczęściej sięgasz po niego, gdy:

  • projektujesz strukturę aplikacji, zanim zaczniesz ją kodować,
  • tłumaczysz działanie systemu osobom nietechnicznym (np. product ownerowi),
  • dokumentujesz istniejący kod albo uczysz się wzorców projektowych,
  • omawiasz rozwiązanie z zespołem na code review lub rozmowie rekrutacyjnej.

Jakie są rodzaje diagramów UML?

Standard UML wyróżnia 14 rodzajów diagramów (tyle jest od wersji 2.5), podzielonych na dwie grupy: strukturalne (z czego składa się system) i behawioralne (jak się zachowuje).

GrupaDiagramyDo czego służą
Strukturalneklas, obiektów, komponentów, struktur złożonych, wdrożenia, pakietów, profilipokazują elementy systemu i relacje między nimi
Behawioralneprzypadków użycia, czynności, maszyny stanów, sekwencji, komunikacji, przeglądu interakcji, czasowepokazują przebieg działania, stany i interakcje

W praktyce programiści najczęściej używają czterech: diagramu klas, diagramu sekwencji, diagramu przypadków użycia i diagramu czynności. My zajmiemy się pierwszym, bo to on pozwala przełożyć kod obiektowy na rysunek i odwrotnie.

Czym jest diagram klas i z czego się składa?

Diagram klas pokazuje statyczną strukturę programu: jakie klasy istnieją, jakie mają pola i metody oraz jak się ze sobą wiążą. Składa się z dwóch rodzajów elementów:

  • bloków klas,
  • relacji między klasami.

Jak narysować klasę, interfejs i klasę abstrakcyjną w UML?

W UML klasa to obramowany prostokąt podzielony na trzy sekcje:

  • nazwę klasy (lub klasy abstrakcyjnej czy interfejsu),
  • pola (właściwości),
  • metody (akcje).

Klasy

Spójrz na przykład klasy Student, która posłuży nam do określenia pól i metod związanych z uczniem.

Diagram klasy na przykładzie klasy Student

  • Pierwsza sekcja określa nazwę klasy, czyli w naszym przypadku Student.
  • Kolejna sekcja to pola. Mogą być publiczne, czyli dostępne z każdego miejsca, i oznaczane znakiem +. Pola prywatne, do których dostęp mamy jedynie z wnętrza danej klasy, oznaczamy -.
  • Ostatnia sekcja to metody. W naszym przypadku wszystkie są dostępne publicznie. Niektóre zwracają wartość, tak jak getPhone(), którego użycie zwróci typ number. Za to np. setPhone() nie zwraca nic.

Interfejsy

Interfejs w diagramie klas UML tworzymy przez dodanie oznaczenia <<interface>> nad jego nazwą. Interfejs ma metody, które klasa może implementować. W JavaScripcie interfejsy nie występują, a ich użycie jest umowne.

Przykładowy blok interfejsu

Klasy abstrakcyjne

Klasy abstrakcyjne tworzymy, dodając <<abstract>> nad nazwą klasy (w wielu narzędziach oznacza się je też kursywą nazwy). Jak widzisz na przykładzie poniżej, oprócz metod mogą one posiadać również pola. W JavaScripcie klasy abstrakcyjne również występują umownie. Różnią się od zwykłych klas tym, że na ich podstawie nie tworzymy nowych obiektów, a jedynie klasy potomne.

Przykładowy blok klasy abstrakcyjnej

Jakie są rodzaje relacji między klasami w UML?

Umiemy już tworzyć klasy (w tym abstrakcyjne) i interfejsy. Żeby powstał kompletny diagram, brakuje relacji. Wyróżniamy sześć rodzajów relacji, od najsłabszej do najsilniejszej:

RelacjaPytanie kontrolneLinia i grotCzy obiekty istnieją niezależnie?
Zależność (dependency)„Czy zmiana A może zepsuć B?”przerywana, strzałkatak
Implementacja (implementation)„Czy A realizuje interfejs B?”przerywana, trójkątny grottak
Dziedziczenie (inheritance)„Czy A jest rodzajem B?”ciągła, trójkątny grotnie dotyczy
Asocjacja (association)„Czy A używa B?”ciągła, strzałkatak
Agregacja (aggregation)„Czy A ma B, ale B żyje samo?”ciągła, pusty rombtak
Kompozycja (composition)„Czy A składa się z B i bez A nie ma B?”ciągła, wypełniony rombnie

Relacje między klasami – od najsłabszej do najsilniejszej, źródło: Aleksander Shvets

Relacje między klasami – od najsłabszej do najsilniejszej, źródło: Aleksander Shvets, „Wzorce projektowe. Nowoczesny podręcznik”.

Relacje związane z rozszerzaniem klas (implementacja i dziedziczenie) mają trójkątny grot. Relacje związane z interakcją klas (asocjacja, agregacja, kompozycja) oznaczamy strzałką lub rombem. Linią ciągłą rysujemy wszystkie z wyjątkiem implementacji i zależności – te reprezentuje linia przerywana. Przejdźmy przez każdą z nich.

Co to jest zależność (dependency) w UML?

Zależność to najsłabszy rodzaj relacji pomiędzy obiektami. Mówimy o niej, jeśli pewne zmiany w jednym z obiektów mogą skutkować zmianami w drugim.

Diagram zależności (dependency)

Tak słabych zależności raczej nie prezentujemy na diagramach. Tak naprawdę każda relacja jest zależnością, więc zdecydowanie lepszym pomysłem jest doprecyzowanie, jaki to rodzaj zależności. Duża liczba zależności na rysunku zaciemniłaby diagram i wprowadziła konsternację u czytających.

Czym jest implementacja interfejsu w diagramie klas?

Implementacja to relacja, w której jedna z klas definiuje pola lub metody zadeklarowane w interfejsie. Po prostu jest to dziedziczenie po interfejsie. W przykładzie klasa Student implementuje interfejs Smart, który określa pilnego ucznia.

Diagram implementacji

Przykład implementacji w kodzie:

class SmartInterface {
    learn() {
        console.log('Learning...');
    }

    passTest() {
        console.log('Passing test...');
    }

    beEarly() {
        console.log('Wake up at 6 am...');
    }
}

class Student extends SmartInterface {
    #school;
    #subjects;

    constructor(school, subjects) {
        super();
        this.#school = school;
        this.#subjects = subjects;
    }
}

const student = new Student('MIT', ['Math', 'Physics', 'Chemistry']);
// dzięki rozszerzeniu interfejsu mamy dostęp do metody learn()
student.learn();

W artykule o czterech filarach programowania obiektowego wspominałem, że pisząc kod obiektowy w JavaScripcie, musimy iść na pewne ustępstwa. JS nie posiada interfejsów, stąd zgodnie z konwencją umawiamy się na klasę SmartInterface, która taki interfejs symuluje.

Czym jest dziedziczenie (inheritance) w UML?

Dziedziczenie to relacja, w której klasa potomna dziedziczy wszystko, co związane z klasą nadrzędną. Dodatkowo taka klasa może rozszerzyć swojego rodzica. Jest to relacja analogiczna do implementacji, z tym że dziedziczymy po klasie (oraz interfejsach, które sama implementuje). W przykładzie klasa Teacher dziedziczy po klasie abstrakcyjnej Person.

Diagram przedstawiający dziedziczenie

Dziedziczenie oparte na powyższym diagramie będzie wyglądać następująco:

class AbstractPerson {
    #name;
    #age;

    constructor(name, age) {
        this.#name = name;
        this.#age = age;
    }

    getName() {
        return this.#name;
    }

    setName(name) {
        this.#name = name;
    }

    getAge() {
        return this.#age;
    }

    setAge(age) {
        this.#age = age;
    }

    isAdult() {
        return this.#age >= 18;
    }
}

class Teacher extends AbstractPerson {
    #schoolName;
    #students;

    constructor(name, age, schoolName, students) {
        super(name, age);
        this.#schoolName = schoolName;
        this.#students = students;
    }

    getSchool() {
        return this.#schoolName;
    }
}

const teacher = new Teacher('John', 30, 'MIT', ['John', 'Mary', 'Peter']);
console.log(teacher.getName());

Dziedziczenie może opierać się na każdej klasie, niekoniecznie abstrakcyjnej.

Czym jest asocjacja (association) w UML?

Asocjacja to najluźniejsza relacja związana z interakcją klas. Oznacza, że jeden obiekt chwilowo wykorzystuje inny lub wchodzi z nim w jakąś interakcję. Obiekty mogą istnieć niezależnie od siebie. Spójrz na przykład klasy Teacher, która może nauczać jakiegoś przedmiotu (klasa Subject), wykorzystując metodę teach.

Diagram przedstawiający asocjację

Zobacz przykład zastosowania asocjacji w kodzie:

class Subject {
    #name;
    #level;

    constructor(name, level) {
        this.#name = name;
        this.#level = level;
    }

    getName() {
        return this.#name;
    }

    getLevel() {
        return this.#level;
    }
}

class Teacher {
    #name;

    constructor(name) {
        this.#name = name;
    }

    teach(subject) {
        if (!(subject instanceof Subject)) {
            throw new Error('Invalid subject');
        }

        console.log(
            `I'm teaching ${subject.getName()},
            at level ${subject.getLevel()}`
        );
    }
}

const subject = new Subject('Math', 1);
const teacher = new Teacher('Matt');

teacher.teach(subject);

Teacher nie przechowuje przedmiotu – dostaje go tylko jako argument metody i używa go „w przelocie”. Dlatego to asocjacja, a nie agregacja.

Czym jest agregacja (aggregation) w UML?

Agregacja to rodzaj asocjacji. Różni się od niej tym, że jeden obiekt służy za kontener dla innych obiektów. Dzięki zadeklarowaniu przekazywanego obiektu w konstruktorze mamy możliwość operowania na nim w całym ciele klasy. Mimo to oba obiekty mogą istnieć niezależnie od siebie.

Diagram przedstawiający agregację

Klasa Teacher stanowi tu kontener na obiekty stworzone za pomocą klasy Subject, ale Subject pozostaje osobnym bytem. Nawet jeśli usuniemy nauczyciela, przedmioty, których mógł uczyć, nadal będą istnieć w programie.

class Subject {
    #name;
    #level;

    constructor(name, level) {
        this.#name = name;
        this.#level = level;
    }

    getName() {
        return this.#name;
    }

    getLevel() {
        return this.#level;
    }
}

class Teacher {
    #subjects;

    constructor(subjects) {
        this.#subjects = subjects;
    }

    getSubjects() {
        return this.#subjects;
    }

    addSubject(subject) {
        if (!(subject instanceof Subject)) {
            throw new Error('Invalid subject');
        }

        this.#subjects.push(subject);
    }

    removeSubject(subject) {
        if (!(subject instanceof Subject)) {
            throw new Error('Invalid subject');
        }

        this.#subjects = this.#subjects.filter(s => s !== subject);
    }

    teach(subject) {
        if (!(subject instanceof Subject)) {
            throw new Error('Invalid subject');
        }

        console.log(
            `I'm teaching ${subject.getName()},
            at level ${subject.getLevel()}`
        );
    }
}

const subject = new Subject('Math', 1);
const anotherSubject = new Subject('English', 2);

const teacher = new Teacher([subject]);
teacher.addSubject(anotherSubject);
console.log(teacher.getSubjects());

Konstruktor klasy Teacher przechowuje informacje o przedmiotach (Subject), więc możemy np. usunąć konkretny przedmiot z tablicy występującej w obiekcie teacher.

Czym jest kompozycja (composition) w UML?

Kompozycja to ostatni i najsilniejszy rodzaj relacji między obiektami. Jest specjalnym rodzajem agregacji. Jak sama nazwa wskazuje, dany obiekt „komponuje się” z innych: części nie istnieją bez całości. Usunięcie obiektu nadrzędnego oznacza usunięcie wszystkich jego składników.

Diagram przedstawiający kompozycję

class Classroom {
    #name;
    #capacity;
    #color;

    constructor(name, capacity, color) {
        this.#name = name;
        this.#capacity = capacity;
        this.#color = color;
    }

    getName() {
        return this.#name;
    }

    setName(name) {
        this.#name = name;
    }

    setCapacity(capacity) {
        this.#capacity = capacity;
    }

    paintWalls(color) {
        this.#color = color;
    }
}

class School {
    #classrooms;

    constructor() {
        this.#classrooms = [
            new Classroom('01', 20, 'white'),
            new Classroom('02', 30, 'yellow'),
            new Classroom('03', 10, 'blue')
        ];
    }

    getClassrooms() {
        return this.#classrooms;
    }

    addClassroom(classroom) {
        if (!(classroom instanceof Classroom)) {
            throw new Error('Invalid classroom');
        }

        this.#classrooms.push(classroom);
    }

    renovateClassroom(name, color) {
        const classroom = this.#classrooms.find(c => c.getName() === name);
        if (!classroom) {
            throw new Error('Classroom not found');
        }
        classroom.paintWalls(color);
    }
}

const school = new School();
school.renovateClassroom('01', 'red');
console.log(school);

Nowe obiekty Classroom powstają bezpośrednio w konstruktorze School, a ta klasa stanowi kontener na obiekty. Usunięcie jej spowoduje usunięcie elementów wchodzących w jej skład. Dzięki metodzie renovateClassroom możemy wpływać na obiekty wchodzące w skład kontenera School.

Jaka jest różnica między asocjacją, agregacją i kompozycją?

To najczęściej mylone trzy relacje. Pomocny jest prosty test – zadaj sobie dwa pytania:

  1. Czy A przechowuje B (np. w polu lub tablicy), czy tylko używa go w jednej metodzie? Jeśli tylko używa – to asocjacja.
  2. Czy B może istnieć po usunięciu A? Jeśli tak (przedmiot po zwolnieniu nauczyciela) – to agregacja. Jeśli nie (sala po zburzeniu szkoły) – to kompozycja.

W kodzie często widać to po tym, gdzie powstaje obiekt: gdy dostajesz go z zewnątrz (argument metody lub konstruktora), mamy asocjację albo agregację; gdy klasa tworzy go sama w konstruktorze (new Classroom(...)), to kompozycja.

💬 Rada mentora: Nie ucz się definicji na pamięć, tylko za każdym razem zadaj pytanie: „Co się stanie z częścią, gdy usunę całość?”. Jeśli część znika – kompozycja. Jeśli zostaje – agregacja. Jeśli klasa tylko tymczasowo korzysta z drugiej – asocjacja. Ten jeden test zastępuje zapamiętywanie trzech osobnych definicji i działa zarówno na diagramie, jak i w kodzie.

W codziennej pracy bardzo rzadko rysuje się osobno agregację i zwykłą asocjację – różnica jest subtelna, a zespoły i tak często ją mylą. Jeśli diagram ma służyć rozmowie w zespole, wystarczy asocjacja (strzałka), dziedziczenie i kompozycja (wypełniony romb). Pustym rombem (agregacja) warto oznaczać tylko wtedy, gdy naprawdę chcesz podkreślić, że część żyje niezależnie od całości. Na rozmowie rekrutacyjnej znajomość tej różnicy jest za to często sprawdzana, więc warto umieć ją wyjaśnić własnymi słowami.

Podsumowanie

Poznałeś już cztery filary programowania obiektowego oraz diagramy klas UML. Dzięki temu wiesz:

  • jak tworzyć klasy, interfejsy i klasy abstrakcyjne,
  • jak korzystać z hermetyzacji, polimorfizmu, abstrakcji i dziedziczenia,
  • jak przedstawić model swojego programu za pomocą diagramów klasowych.

Wykorzystaj tę wiedzę – spróbuj napisać obiektowo prosty projekt. Skorzystaj z diagramu UML, by zobrazować logikę swojej aplikacji. To na pewno zrobi wrażenie na Twoim (przyszłym) pracodawcy!

!Diagram przedstawiający wszystkie relacje między klasami

Źródła

Konsultacja i warsztaty devmentor.pl

Masz pytania?