Czym jest diagram klas#

Diagram klas (class diagram) przedstawia statyczną strukturę modelu: klasyfikatory, ich wybrane cechy oraz relacje między nimi. Pomaga opisać pojęcia dziedziny, odpowiedzialności w projekcie albo część struktury oprogramowania. Nie pokazuje kolejności zdarzeń w czasie; do przedstawiania interakcji lub przebiegu zachowania służą inne diagramy UML.

Klasa opisuje zbiór obiektów o wspólnych cechach i znaczeniu. Obiekt jest konkretną instancją, która w określonym momencie ma wartości cech i może uczestniczyć w relacjach. Diagram klas mówi zatem o typach i dozwolonej strukturze, a nie o jednym konkretnym stanie systemu. Konkretne instancje i ich wartości można pokazać na diagramie obiektów.

Diagram klas bywa używany w analizie, projektowaniu i dokumentowaniu kodu, ale te zastosowania nie są tożsame. Model pojęciowy przedstawia terminy i reguły dziedziny. Model projektowy może doprecyzować interfejsy, typy i odpowiedzialności. Widok implementacyjny może odzwierciedlać konkretne konstrukcje języka programowania. Przed tworzeniem diagramu warto więc określić jego cel oraz to, które szczegóły świadomie pomija.

Zapis klasy#

Klasa jest zwykle rysowana jako prostokąt z nazwą. Prostokąt można podzielić poziomymi liniami na przedziały: nazwę, atrybuty oraz operacje. Przedziały z atrybutami i operacjami są opcjonalne; ich pominięcie nie oznacza, że klasa nie ma takich cech. Może znaczyć, że nie są potrzebne w tym widoku.

Nazwa klasy zapisuje się zwykle wielką literą na początku, np. Zamówienie. W UML nazwa jest częścią modelu, nie tylko podpisem prostokąta. Jeśli pokazujemy nazwę abstrakcyjną, konwencja UML pozwala zapisać ją kursywą; narzędzia tekstowe mogą wymagać osobnej składni formatowania. Nad nazwą można umieścić stereotyp w cudzysłowach kątowych, np. «interface», aby wskazać wyspecjalizowaną rolę elementu. Stereotyp nie jest zwykłym komentarzem ani zamiennikiem wyjaśnienia modelu.

Atrybuty#

Atrybut opisuje właściwość klasyfikatora, której wartości są przypisane do jego instancji albo — jeśli jest statyczna — do samego klasyfikatora. Typowy zapis tekstowy ma postać:

widoczność nazwa: Typ [krotność] = wartośćDomyślna

Elementy w nawiasach są opcjonalne. Przykład -email: String mówi o prywatnym atrybucie email typu String. Atrybut utworzono: DataCzas może być pokazany bez znaku widoczności, jeżeli dla tego widoku nie określono lub pominięto widoczność. Brak oznaczenia nie powinien być automatycznie odczytywany jako private.

UML definiuje symbole widoczności: + publiczną, - prywatną, # chronioną oraz ~ pakietową. Znaczenie praktyczne widoczności pakietowej zależy od sposobu odwzorowania przestrzeni nazw w danym środowisku. Nie należy mechanicznie utożsamiać symboli z regułami każdego języka programowania.

Krotność atrybutu zapisuje się w nawiasach kwadratowych, np. adresy: Adres [0..*]. Wskazuje ona, ile wartości cecha może przyjmować. Gdy cecha wielowartościowa wymaga informacji o uporządkowaniu lub dopuszczaniu duplikatów, właściwości kolekcji można doprecyzować. W prostych diagramach takie szczegóły często się pomija.

Ukośnik przed nazwą, jak w /wartośćBrutto: Kwota, oznacza cechę pochodną, czyli wyliczaną z innych informacji. Znak podkreślenia może oznaczać element statyczny, ale wiele narzędzi wykorzystuje formatowanie graficzne. Jeżeli ważna jest różnica między cechą obiektu a cechą klasy, sprawdź, czy użyta konwencja jest widoczna w eksporcie.

Operacje#

Operacja opisuje nazwane zachowanie dostępne przez klasyfikator. Jej typowy zapis ma postać:

widoczność nazwa(parametr: Typ): TypWyniku

Na przykład +anuluj(powód: Tekst): Boolean przedstawia publiczną operację z parametrem i typem wyniku. Zapis +potwierdź() nie określa typu zwracanego; w zależności od przyjętej konwencji może znaczyć, że typ nie jest istotny lub został pominięty. Nie należy z tego wyciągać wniosku o typie konkretnego języka programowania.

UML rozróżnia operację jako deklarację zachowania od metody jako implementacji tego zachowania. Diagram klas może pokazać operacje bez opisywania algorytmu ich wykonania. Do kolejności kroków, warunków i komunikatów lepiej pasuje diagram aktywności, sekwencji lub inny diagram zachowania.

Przykład: zamówienie w sklepie#

Poniższy diagram jest modelem projektowym o umiarkowanym poziomie szczegółowości. Wyróżnia podstawowe klasy, wybrane atrybuty, operację dodawania pozycji i relacje istotne dla zamówienia. Nie próbuje pokazać wszystkich statusów, podatków, płatności, adresów, reguł promocji ani klas technicznych.

Klient składa zamówienia; zamówienie składa się z co najmniej jednej pozycji, a każda pozycja wskazuje jeden produkt.
Uproszczony model klas zamówienia w sklepie internetowym.

Każdy obiekt Zamówienie jest w tym modelu powiązany z dokładnie jednym klientem, a klient może mieć zero lub wiele zamówień. Zamówienie składa się z co najmniej jednej pozycji. Wypełniony romb przy zamówieniu oznacza kompozycję: model traktuje pozycje jako części konkretnego zamówienia, a nie jako samodzielne elementy współdzielone między zamówieniami. Jeżeli reguły domeny dopuszczają zachowanie pozycji po usunięciu zamówienia lub jej niezależne współdzielenie, kompozycja może być niewłaściwa i należy rozważyć zwykłą asocjację.

Każda pozycja dotyczy dokładnie jednego produktu, natomiast produkt może wystąpić w wielu pozycjach. Strzałka przy tej asocjacji jest tu użyta jako wskazanie nawigowalności od pozycji do produktu; sama nie oznacza dziedziczenia. Diagram nie stwierdza, czy produkt musi pozostać dostępny po utworzeniu zamówienia ani skąd pochodzi historyczna cena. Te reguły wymagałyby dodatkowego modelu lub objaśnienia.

Operacja dodajPozycję ma typ wyniku PozycjaZamówienia. Atrybuty opisują tylko wybrany fragment stanu. Zapis UUID, typów tekstowych i kwot jest abstrakcyjny; nie określa konkretnej biblioteki, formatu zapisu ani sposobu przechowywania w bazie.

Relacje między klasami#

Relacje określają, jak elementy modelu są ze sobą powiązane. Najczęściej spotykane na diagramie klas to asocjacja, uogólnienie, realizacja i zależność.

Asocjacja#

Asocjacja opisuje możliwe powiązania między instancjami klasyfikatorów. Zwykłą asocjację binarną rysuje się linią ciągłą. Można dodać nazwę relacji, nazwy ról na końcach, krotności, ograniczenia, kwalifikatory i nawigowalność. Nie wszystkie oznaczenia są potrzebne w każdym widoku.

Nazwa relacji pomaga odczytać model jako zdanie. Nazwa roli określa, jaką funkcję pełni koniec relacji z perspektywy przeciwnego klasyfikatora. Krotność przy danym końcu mówi, ile instancji po tej stronie może być powiązanych z pojedynczą instancją po stronie przeciwnej. W razie wątpliwości odczytaj oba kierunki na głos i sprawdź je z regułami domeny.

Asocjacja może być skierowana przy pomocy oznaczenia nawigowalności, ale brak grotu nie powinien być bez wyjaśnienia traktowany jako jednoznaczny zakaz dostępu w kodzie. Szczegóły własności końców i nawigowalności należą do modelu UML; różne narzędzia mogą prezentować je w odmienny sposób albo pomijać szczegóły w uproszczonym widoku.

Uogólnienie, czyli specjalizacja#

Uogólnienie (generalization) łączy klasyfikator bardziej szczegółowy z bardziej ogólnym. Rysuje się je linią ciągłą z pustym trójkątem skierowanym ku elementowi ogólniejszemu. Relację można czytać: „każdy X jest również Y”. Jeżeli twierdzenie to nie jest prawdziwe dla wszystkich instancji klasy szczegółowej, relacja dziedziczenia jest prawdopodobnie złym wyborem.

Uogólnienie służy do modelowania wspólnych cech i specjalizacji, a nie dowolnego użycia lub chwilowego powiązania obiektów. Nie myl go z asocjacją (powiązaniem instancji), zależnością (użyciem) ani realizacją interfejsu. Szczegóły wielodziedziczenia, kompletności zbioru specjalizacji i rozłączności można opisywać ograniczeniami, gdy są istotne dla modelu.

Realizacja interfejsu#

Interfejs definiuje kontrakt, który mogą realizować klasyfikatory. W typowym zapisie klasa realizująca interfejs jest połączona z nim linią przerywaną zakończoną pustym trójkątem wskazującym interfejs. Wariant „lizaka” pokazuje interfejs jako kółko połączone z elementem, który go udostępnia. Interfejs może deklarować operacje, a także inne cechy zgodne z jego semantyką.

Realizacja nie oznacza, że interfejs jest klasą bazową z pełną implementacją. Konkretny język może odwzorowywać kontrakt w określony sposób, ale model UML nie musi odtwarzać wszystkich ograniczeń tego języka.

Zależność#

Zależność jest zwykle rysowana linią przerywaną z otwartym grotem skierowanym ku elementowi, od którego zależy klient zależności. Wskazuje ogólną relację użycia, w której zmiana dostawcy może wpłynąć na klienta. Sama zależność nie opisuje trwałego powiązania obiektów ani nie zastępuje asocjacji. Szczegółowy sens zależności można doprecyzować stereotypem lub notatką.

Agregacja i kompozycja#

Agregacja współdzielona używa pustego rombu po stronie całości. Jej semantyka w UML jest celowo słabo określona i zależy od modelu; nie powinna być używana jako automatyczny synonim „zawiera”. Kompozycja używa wypełnionego rombu i opisuje silniejszą relację całość–część. W modelu jedna część kompozytu może być w danym czasie częścią najwyżej jednego kompozytu. Reguły tworzenia, usuwania i współdzielenia trzeba rozumieć zgodnie z cyklem życia modelowanych elementów.

Jeśli nie potrafisz wskazać znaczenia rombu i konsekwencji dla części, użyj zwykłej asocjacji. Wiele relacji „ma” nie wymaga agregacji ani kompozycji.

Klasa, interfejs, typ i obiekt#

Klasa jest jednym z klasyfikatorów w UML, ale nie każdy klasyfikator jest klasą. Interfejs określa kontrakt, typ danych opisuje wartości określonego rodzaju, a komponent jest klasyfikatorem przeznaczonym do modelowania modularnych części systemu. W diagramie klas mogą pojawiać się różne klasyfikatory, zależnie od celu diagramu.

Obiekt jest instancją, a nie klasą. Zapis obiektu często podkreśla podkreśloną nazwę instancji, np. zamówienieA: Zamówienie, wraz z wartościami cech. Taki widok pokazuje konkretny przykład struktury; diagram klas opisuje dopuszczalne typy i relacje. Więcej szczegółów dotyczących instancji znajduje się w artykule o diagramie obiektów.

Jak tworzyć diagram klas krok po kroku#

  1. Określ pytanie, odbiorcę i poziom abstrakcji. Zapisz, czy modelujesz pojęcia, projekt, czy strukturę implementacji.
  2. Wybierz pojęcia lub klasyfikatory potrzebne do odpowiedzi. Nie zamieniaj każdego rzeczownika z opisu na klasę.
  3. Nadaj nazwy jednoznaczne w danej dziedzinie i uzgodnij wspólne słownictwo.
  4. Dodaj relacje, które wynikają z wymagań lub decyzji projektowych. Wybierz właściwy typ relacji.
  5. Określ krotności przy końcach asocjacji na podstawie reguł, a nie intuicji.
  6. Dodaj tylko te atrybuty i operacje, które są potrzebne do celu diagramu.
  7. Pokaż ograniczenia lub założenia, których nie da się wywnioskować z samych symboli.
  8. Odczytaj model jako zestaw zdań i sprawdź go z osobą znającą problem.
  9. Sprawdź czytelność grafiki i zgodność wszystkich elementów z opisem.

Ten proces jest iteracyjny. Nowa informacja może zmienić klasy, relacje, krotności lub podział widoków.

Typowe błędy#

  • Rysowanie całego systemu naraz. Podziel model na widoki odpowiadające różnym pytaniom.
  • Zamiana każdej linii w ten sam rodzaj relacji. Wybierz asocjację, uogólnienie, realizację lub zależność zgodnie ze znaczeniem.
  • Brak krotności lub wpisywanie jej z przyzwyczajenia. Potwierdź regułę w wymaganiach i czytaj oznaczenie przy właściwym końcu.
  • Używanie dziedziczenia dla relacji „ma” albo „korzysta z”. Sprawdź, czy prawdziwe jest zdanie „każda instancja X jest Y”.
  • Nadużywanie rombów. Agregację lub kompozycję stosuj tylko wtedy, gdy ich semantyka jest istotna i uzasadniona.
  • Traktowanie widocznego diagramu jako pełnego kodu. Diagram może pomijać cechy i szczegóły implementacyjne.
  • Mieszanie pojęć domenowych z tabelami i klasami technicznymi. Oznacz perspektywę modelu i oddziel widoki, gdy odpowiadają na różne pytania.
  • Wpisywanie wszystkich metod i pól. Pokaż informacje potrzebne odbiorcy; pozostałe można pominąć.
  • Przyjmowanie, że renderer potwierdził poprawność. Narzędzie sprawdza zapis, ale sens relacji i zgodność z wymaganiami trzeba zweryfikować.

Co diagram klas pokazuje, a czego nie#

Diagram klas może precyzyjnie pokazać wybrane klasyfikatory, cechy, relacje, krotności i ograniczenia. Może być użyteczny jako model pojęć, dokument projektowy, widok kodu albo narzędzie do omawiania odpowiedzialności. Jego dokładne znaczenie zależy od widocznych elementów, zastosowanej notacji i opisanego celu.

Sam diagram klas nie pokazuje kolejności wykonywania zachowania, czasu, przepływu sterowania, pełnego cyklu życia ani szczegółów algorytmu. Nie musi też wyliczać wszystkich elementów modelu lub kodu. Gdy potrzebne są te informacje, uzupełnij go odpowiednim diagramem zachowania lub tekstem, zamiast przeciążać jeden widok.