Podstawy i diagram klas UML

10 march 2022
Jakub Rojek Jakub Rojek
Źródło: własne (na podstawie diagrams.net)
Categories: Programming, IT fundamentals

Są takie tematy związane z inżynierią oprogramowania, które są wręcz klasyką tej dziedziny i zawsze muszą wystąpić we wszystkich programach oraz agendach. Do tej grupy zdecydowanie należy bohater dzisiejszego tekstu, a więc notacja UML, która bardziej jest znana pod terminem "diagramy UML". Te z kolei niektórzy utożsamiają z opisem klas i relacji pomiędzy nimi, ale to akurat jest błąd. Zaczniemy od jego sprostowania.

Co to jest UML?

UML (ang. Unified Modeling Language) jest ustandaryzowaną notacją używaną w modelowaniu oprogramowania i przy okazji potocznym określeniem na szereg diagramów opisujących konstrukcję oraz zachowanie projektowanego systemu IT. Wiele koncepcji znanych z UML jest (czasami nieświadomie) wykorzystywanych w potocznych schematach stosowanych przy tworzeniu oprogramowania.

UML w wersji 2.0 i wyżej definiuje kilkanaście typów diagramów podzielonych na dwie główne kategorie:

  • diagramy strukturalne - ich głównym zadaniem jest prezentacja struktury bytów oraz ich relacji względem siebie. Jest to najczęściej wykorzystywany typ diagramu, wręcz utożsamiany z UML-em, którego bardzo popularnym reprezentantem jest diagram klas.
  • diagramy behawioralne - skupiają się na zachowaniu obiektów, funkcjonalności systemu i przepływie informacji (ostatni aspekt czasami wyróżniany jest jako osobna kategoria diagramów - interakcji). Najczęściej spotykanymi przykładami są diagramy przypadków użycia, czynności, stanów czy sekwencji.

Czy UML rzeczywiście jest używany?

To dość częste pytanie, które pojawia się przy klasykach inżynierii oprogramowania - czy dana technika ma wartość praktyczną i istotnie stosuje się ją w prawdziwych projektach, czy jest to jedynie coś wykorzystywanego przez zapalonych formalistów i profesorów na uczelniach? W przypadku UML-a odpowiedź brzmi - coś pomiędzy, ale nawet w takim wypadku warto go znać.

Faktem jest, że w przypadku większości projektów formalne diagramy nie są wykorzystywane lub są w bardzo okrojonej wersji, głównie ze względu na czas spędzany nad ich przygotowaniem. Oczywiście, w średniej wielkości przedsięwzięciach projektuje się w ten sposób elementy systemu, choć bardziej na ogólniejszym poziomie abstrakcji. Natomiast są firmy, w których UML-a jak najbardziej się stosuje, ale w szczególności dotyczy to bardzo dużych systemów, często o krytycznym przeznaczeniu, w których błąd na etapie projektowania i niezauważenie jakiegoś zagrożenia ma znacznie większe znaczenie niż normalnie.

Jednak, niezależnie od tego, czy będziemy stosować UML-a w praktyce, czy nie, uważam, że i tak warto go poznać. Wróćmy do przywołanych już diagramów na ogólniejszym poziomie szczegółowości - zwykle nie używa się przy nich sformalizowanej notacji. Jednak osoba znająca UML będzie wręcz nieświadomie stosowała niektóre jego elementy, które będą pomagały w odczycie. A niezależnie od tego, jakiej notacji używamy i w jakim stopniu, przyświeca im jeden cel - mają jasno i klarownie ukazywać pewną koncepcję, którą mają szybko i bezboleśnie pojąć inni uczestnicy projektu. To nie jest tak, że jedynie diagramy czysto UML-owe są dobrze rozumiane w środowisku IT - to tylko standard, który w tym pomaga, ale nie jest obowiązkowy.

Przypomnijmy sobie obiektowość

Ponieważ w diagramach strukturalnych (które są najpopularniejszą formą UML-a) korzysta się z zasad obiektowości, warto przypomnieć sobie, co się kryje pod tym pojęciem. Oczywiście, programowanie obiektowe (w skrócie OOP) jest formą kodowania (tzw. paradygmatem), w której program organizuje się w klasy (składające się z pól i metod), z których powstają obiekty. To ma na celu bardziej intuicyjne przeniesienie świata rzeczywistego na model oprogramowania. Jednak obiektowość to nie tylko klasy, pola i metody - to także wiele innych technik je wykorzystujących, z których korzysta się naturalnie... o ile się je zna. Dlatego warto sobie przywołać cechy, które nazywamy czasem filarami obiektowości - są to:

  • Enkapsulacja (hermetyczność) - niektóre elementy klasy powinny być zabezpieczone przez bezpośrednie wywołanie z innych obszarów programu, aby zachować kontrolę modyfikacji i dostępu do danych. Ta zasada głównie dotyczy pól (zmiennych). Przykładem pokazującym sens tej reguły może być klasa Biblioteka, której zasoby (książki) powinny być dostępne jedynie poprzez specjalne metody (symulujące Bibliotekarza - osobę obsługującą czytelnika), a nie bezpośrednio (co odpowiadałoby sytuacji, w której czytelnik może wejść do biblioteki, wziąć samemu książkę i wyjść).
  • Dziedziczenie - definicje pewnych bytów mogą być uszczegóławiane (specjalizowane) poprzez zdefiniowanie innych bytów dziedziczących z nich. Dzięki temu, klasy podrzędne nie muszą na nowo definiować pewnych metod, a jedynie mogą korzystać z tych, które zostały wprowadzone dla klasy nadrzędnej i najwyżej je poprawiać (o czym poniżej).
  • Polimorfizm - obiekty mogą być przechowywane jako instancje tej samej, ogólnej klasy, ale jednocześnie być instancją różnych, szczegółowych klas, dzięki czemu zyskują nowe właściwości bądź zachowują się charakterystycznie dla klasy podrzędnej. Przykładowo, możemy mieć kolekcję z dwoma obiektami klasy Żołnierz i oba z nich mają do dyspozycji metodę strzelaj(), jednak jeden z tych obiektów będzie tak naprawdę instancją klasy Snajper (dziedziczącej z Żołnierza), u którego wywołanie strzelania będzie owocowało nieco innym efektem.
  • Abstrakcyjność - właściwość, dzięki której byty w programie mogą się komunikować i działać bez wiedzy o szczegółach implementacji innych bytów. W językach programowania jest to zasada realizowana głównie przez klasy i metody abstrakcyjne, ale także interfejsy. Powyżej mieliśmy przykład - możemy wywołać metodę strzelaj() na danym obiekcie klasy Żołnierz i niekoniecznie musimy być świadomi, jakie są szczegóły implementacji, czyli jak dokładnie przebiega strzelanie.

Diagram klas

Przejdźmy do opisu zdecydowanie najpopularniejszego rodzaju diagramu kojarzonego (a czasem nawet - niesłusznie - utożsamianego) z UML-em - diagramu klas. Jego przeznaczenie jest dość intuicyjne - czasami zachodzi potrzeba zaprojektowania i dyskusji na temat struktury klas tworzonego oprogramowania, aby uniknąć późniejszej nadmiernej refaktoryzacji lub tzw. złych zapachów. Dodatkowo, tworząc model, możemy już "na sucho" zastanowić się nad pewnymi wyborami i ocenić, czy będą niosły ze sobą korzyści, czy wręcz przeciwnie.

Diagram klas służy do projektowania struktury klas i ich relacji między sobą. Tutaj warto też wspomnieć, że nie należy mylić go z diagramem związków encji (ang. Entity-Relationship Diagram/Model - ER), który z kolei służy do projektowania encji i często jest podstawą do projektowania schematów relacyjnych baz danych. Mimo wielu podobieństw, obie notacje mocno różnią się chociażby oznaczeniami relacji pomiędzy bytami ("widełki" w ER, "romby" w UML).

Klasę opisujemy w następujący sposób, rozdzielając trzy główne sekcje - nazwę klasy, pola i metody:

Schemat definiowania klasy w UML

Zanim przejdziemy dalej, mała wskazówka techniczna - ponieważ często zaczynamy projektować tego typu diagramy na papierze, warto wyrobić sobie nawyk kreślenia jedynie górnej i lewej krawędzi prostokąta. Jak widać, definicje elementów mogą być bardzo długie (warto przenosić je do kolejnych linii) oraz może być ich dużo, w związku z czym zazwyczaj warto zamknąć prostokąt dopiero po wpisaniu wszystkich pozycji.

Musimy też przywołać sobie jedną, bardzo ważną rzecz - to nie jest tak, że UML-owy model klasy odpowiada jeden do jednego implementacji tej klasy w prawdziwym kodzie. To tylko notacja i model - przykładowo, może się zdarzyć, że coś, co jest prezentowane jako pole, tak naprawdę w faktycznym oprogramowaniu będzie metodą (lub nawet zbiorem metod). Wszystko zależy od możliwości konkretnego języka programowania, technologii i przyjętych konwencji, a także późniejszych pomysłów. Można więc powiedzieć, że model UML jest faktycznie modelem, wzorcem, ale jednocześnie tylko wskazówką (choć bardzo konkretną) dla programistów realizujących konkretny projekt.

Omówmy sobie teraz pokrótce każdą część takiej klasy. Warto wspomnieć, że każdą (poza nazwą) możemy pominąć. Wyjątkiem jest pominięcie sekcji pól przy obecności sekcji metod - wówczas należy zostawić pusty prostokąt (będzie to widoczne w tym artykule na przykładzie przy typach relacji).

Sekcja z nazwą klasy

W tej części znajduje się, oczywiście, nazwa klasy lub interfejsu. I właśnie między innymi do tego ostatniego służy fragment znajdujący się pomiędzy dwoma nawiasami kątowymi - tutaj pojawia się dodatkowe oznaczenie klasyfikujące dany byt. Może być to interfejs, typ wyliczeniowy (enum) a może coś innego. I właśnie - tutaj ponownie objawia nam się wątek opisywania klas niekoniecznie trzymając się samego standardu UML, ale też rozszerzając go o wewnętrzne notacje, zrozumiałe i pomocne dla danego zespołu.

Sekcja z polami

Jak nietrudno się domyślić, wpisywane tutaj są pola klasy, czyli jej zmienne. Każda z nim rozpoczyna się od modyfikatora widoczności, który określa zasięg elementu. Są to cechy znane z większości obiektowych języków programowania i tak: + oznacza element publiczny (public), - element prywatny (private), a # chroniony, czyli widoczny dla klas podrzędnych (protected). Czasami stosowany jest również ~ prezentujący element publiczny, ale widoczny tylko w obrębie danego pakietu. Z uwagi na zasadę enkapsulacji, w przypadku pól prawdopodobnie najczęściej wykorzystywane będą modyfikatory prywatne oraz chronione.

Następnie pojawia się, oczywiście, nazwa pola, natomiast później - po dwukropku - konkretny typ. Może to być zarówno typ prosty (np. string), jak i złożony (nazwa innej klasy). Dodatkowo, jeżeli opisywany element dotyczy kolekcji (tablicy, listy itp.), wówczas do typu dodawane są nawiasy kwadratowe (np. int[]).

Przy tej okazji warto też wspomnieć o innych elementach, które można stosować w opisie klas:

  • elementy statyczne oznaczamy podkreśleniem całego wiersza,
  • jeśli zostanie użyty slash ("/" - o tym, jak rozróżnić ukośne znaki, pisaliśmy tutaj), to mamy do czynienia z tzw. atrybutem wyliczanym, który oznacza zastosowanie nie pola, ale konkretnej metody, zwracającej obliczoną wartość. Najczęściej dopisujemy do tego notatkę poprzez dodatkowy prostokąt połączony z danym elementem linią przerywaną.
  • atrybuty tylko do odczytu mają dopisek {readOnly},
  • można oznaczać stałe poprzez przypisanie na końcu konkretnej wartości po znaku równości,
  • jeśli jedna klasa zawiera drugą (o relacjach będzie za moment), to zazwyczaj nie dopisuje się dodatkowego pola do definicji klas, aby diagram niepotrzebnie się nie rozrastał.

Jak widać, możliwości jest wiele i niektóre mogą być bardzo przydatne podczas późniejszej implementacji.

Sekcja z metodami

W tym przypadku notacja jest bardzo podobna jak w przypadku pól (uwzględniając np. zasady związane z modyfikatorami widoczności), jednak dochodzi nowy element - lista parametrów. Opisuje ona argumenty (oddzielone przecinkami) przekazywane do danej metody, zgodnie z notacją nazwaArgumentu: typ. Także w tym przypadku typ może być zarówno prosty, jak i złożony.

Również w przypadku metod mamy do czynienia z możliwością zastosowania kilku dodatkowych oznaczeń, pozwalających lepiej oddać zamysł projektanta na diagramie.

  • metody abstrakcyjne (także samą klasę) możemy zidentyfikować po zastosowaniu kursywy (pochylonego tekstu),
  • konstruktory i destruktory możemy umieścić na diagramie, ale jest to zalecane tylko w momencie, kiedy konstruktor przyjmuje jakieś parametry, a w przypadku destruktorów - jeżeli oprócz zwykłego czyszczenia pamięci wykonuje jakąś logikę. W innym przypadku nie ma potrzeby dodatkowego "zaśmiecania" diagramu. Należy zwrócić uwagę, iż w przypadku tych specjalnych metod nie jest umieszczany typ zwracany.
  • jeżeli istnieje taka potrzeba, przed parametrami w metodach można umieszczać dodatkowe modyfikatory oznaczające charakter tego parametru. Mowa tutaj o in (parametr wejściowy, domyślny), out (parametr wyjściowy) oraz inout (parametr wejściowo-wyjściowy). Przykład: run(in dist : float) : boolean.

Resztę zasad możemy zapożyczyć z pól, gdyż są stosowane także tutaj (np. metody statyczne).

Relacje

Nie samymi klasami człowiek żyje - bardzo istotne są także relacje pomiędzy nimi. Jest to temat złożony i sprawiający dużo problemów osobom, które nie znają notacji lub mylą mu się i pewnych oznaczeń dokonuje odwrotnie.

Przede wszystkim, warto pokazać, w jaki sposób można zamodelować relację dziedziczenia klas lub implementowania interfejsów. W obu przypadkach czyni się to za pomocą strzałki z grotem w postaci niewypełnionego trójkąta. Jest to istotne, ponieważ w innym przypadku mielibyśmy do czynienia ze zwykłą zależnością pomiędzy klasami. Jak widać na załączonych przykładach, w przypadku wielu klas podrzędnych można połączyć strzałki do jednej, choć nie jest to wymagane.

Sposób prezentacji dziedziczenia i implementacji interfejsu

Natomiast z interfejsami sprawa wygląda ciut inaczej - w tym przypadku nie jest to stricte dziedziczenie, ale implementacja. Aby to wyróżnić, stosuje się bardzo podobną strzałkę, ale linia jest przerywana. Zobaczcie powyższe przykłady.

To była ta łatwa część. Teraz przejdziemy do zwykłych relacji pomiędzy klasami i najpierw pokażemy sobie, w jaki sposób, za pomocą notek przy strzałkach, możemy poinformować programistę, czy ma do czynienia np. z kolekcją.

Krotności przy relacjach w diagramie klas

Relacje mogą być kierunkowe lub nie. Oznacza to, że obiekty jednej klasy mogą być w relacji do drugiej (np. zawierać ją - o tym nieco później), ale niekoniecznie działa to w drugą stronę. Tak naprawdę dziedziczenie jest relacją kierunkową - to klasa podrzędna "korzysta" z rodzica, a nie odwrotnie. Oznaczenie jest banalnie proste - wystarczy przedstawić grot po jednej stronie. W powyższym przykładzie relacja pomiędzy Book a TableOfContents jest kierunkowa (pierwsza posiada drugą), natomiast pomiędzy TocItem i Chapter powiązanie jest dwustronne.

Czasami chcemy zaznaczyć, że obiekty jednej klasy mogą łączyć się z wieloma obiektami (lub jakąś konkretną liczbą) innej. Tutaj mamy do dyspozycji krotności, z którymi wiąże się... najwięcej błędów popełnianych przez studentów podczas zajęć. Bardzo istotne jest bowiem umiejscowienie danej krotności oraz zapamiętanie jednego faktu - krotność m oznacza, że na jeden obiekt danej klasy przypada m obiektów drugiej klasy. Jeśli natomiast chodzi o umiejscowienie, to jeśli klasa A może zawierać ileś elementów klasy B, to ta krotność znajduje się przy klasie B. Odwołując się do przykładu - obiekt klasy TableOfContents może zawierać od jednego do wielu obiektów klasy TocItem. Jeszcze ciekawiej jest w relacjach dwukierunkowych - tutaj widzimy, że TocItem zawsze wskazuje na jeden obiekt Chapter, ale każdy obiekt Chapter może (ale nie musi) mieć jeden obiekt klasy TocItem. Dostępne (a raczej najpopularniejsze) typy krotności to:

  • 1 - domyślna krotność (nie trzeba jej pisać, choć w przykładzie raz to zrobiliśmy), oznacza, że posiadany jest dokładnie jeden obiekt.
  • 0..1 - opcjonalność, oznacza, że posiadany może być najwyżej jeden obiekt (ale może nie być przypisany żaden).
  • 1..* - obowiązkowa kolekcja, oznacza, że obiekt posiada niepustą kolekcję innych obiektów (co najmniej jednoelementową).
  • * - opcjonalna kolekcja, oznacza, że obiekt posiada kolekcję innych obiektów (może nie posiadać żadnego). Równoważne 0..*.

Możemy jeszcze skonkretyzować daną relację poprzez zdefiniowanie jej etykiety lub nazwy - w tym przypadku widzimy, że TocItem zawiera link do Chapter (a zatem tym razem jesteśmy po stronie klasy inicjującej powiązanie). Często w praktyce przekłada się to na nazwę pola lub identyfikację powiązań, jeśli występuje ich więcej niż jedno pomiędzy daną parą klas. Możliwe jest też stworzenie relacji klasy do samej siebie - wówczas strzałka wychodzi i wchodzi do tego samego bytu.

Nadążacie? Bo przed nami jeszcze typy relacji.

Typy relacji w diagramie klas

Są ich cztery. Zależność to najsłabszy typ, który pokazuje, iż jeden byt w jakiś sposób wykorzystuje drugi lub zmiana w nim wpływa na ten drugi. Może być to istotne z punktu widzenia późniejszego planowania kolejności prac i obliczania różnych metryk, podczas których spogląda się właśnie na to, czy zmiany w jakiejś klasie mogą spowodować konieczność weryfikacji innych elementów projektu. Zależność zaznaczana jest linią przerywaną i zwykle posiada odpowiednią etykietę pokazującą jej charakter. Przykładowe etykiety to: <<call>> (wywołuje), <<use>> (używa, konieczna do implementacji), <<instantiate>> (jest instancją), <<realize>> (realizuje, np. interfejs) oraz <> (tworzy). Jak widać, jest to forma, którą mogliśmy już zobaczyć przy implementacji interfejsu.

W powyższym przykładzie w tej relacji jest Wing, które w co najmniej jednym miejscu wykorzystuje obiekt klasy Color, ale tylko to - nie wiąże się z nim na dłużej.

Z kolei asocjacja jest silniejsza i częściej wykorzystywana. Zazwyczaj używa się jej do wskazania referencji (posiadania) obiektów jednej klasy przez obiekty drugiej klasy. Należy jednak pamiętać, że obie klasy są w tej relacji równorzędne - usunięcie obiektów z jednej nie wpływa na obiekty drugiej, gdyż są niezależne od siebie. Jest to sposób na coś, o czym wspomnieliśmy już wcześniej - opisanie pól danej klasy, które nie są o typach prostych, ale obiektami innej klasy - wówczas nie dopisuje się ich ponownie w sekcji pól. Asocjacja jak najbardziej może być dwukierunkowa, choć zazwyczaj używa się jednokierunkowej.

W przykładzie jest to relacja pomiędzy Airplane a Pilot - każdy samolot posiada co najmniej jednego pilota, ale nie jest to posiadanie na wyłączność - obie klasy są równorzędne.

Stajemy się coraz bardziej zachłanni - agregacja (tzw. agregacja częściowa) to silne powiązanie, w którym obiekty jednej klasy nie tylko posiadają obiekty drugiej, ale też robią to na zasadzie klasy nadrzędnej (nie w rozumieniu dziedziczenia). Innymi słowy - obiekty klasy podrzędnej nie mają "sensu" istnieć samodzielnie, gdyż są jedynie składnikiem obiektów klasy nadrzędnej. Nie są jednak na wyłączność jednego obiektu nadrzędnego (czyli część może być częścią różnych całości). Zwykle też obiekty klasy nadrzędnej nie zarządzają (nie tworzą i nie usuwają) obiektów klas podrzędnych. Oznaczeniem agregacji jest niewypełniony romb przy klasie nadrzędnej.

W przykładzie widzimy to w relacji Pilot i PilotUniform. Mundur nie ma wielkiego znaczenia praktycznego bez kogoś, kto go ubierze, ale jednocześnie nie jest na wyłączność jednego pilota. No cóż, oszczędności dotykają też czasem linii lotniczych.

I wreszcie najsilniejszy typ relacji - kompozycja (zwana też agregacją silną lub całkowitą). Jest to rozszerzenie agregacji - ponownie mamy relację klasy nadrzędnej i podrzędnej (koncepcja "całość i część"), ale w tym przypadku są one ze sobą nierozerwalnie związane. Część nie może istnieć bez całości i odwrotnie. Dodatkowo, obiekty podrzędne zwykle są zarządzane przez obiekty klasy nadrzędnej. Żeby tego było mało, części są na wyłączność jednej całości. Oznaczeniem kompozycji jest wypełniony romb przy klasie nadrzędnej.

Nie da się ukryć, że obiekt klasy Wing nie dość, że nie ma sensu bez posiadającego go obiektu Airplane, to trudno się też spodziewać, aby jedno skrzydło było współdzielone pomiędzy większą liczbą samolotów. To typowa kompozycja.

Przy okazji rysowania relacji warto zwrócić uwagę jeszcze na dwa aspekty techniczne, ułatwiające późniejszą analizę diagramu. Po pierwsze, jak ognia należy unikać przecinania się linii. Co prawda, jest to dopuszczalne, ale jeśli to tylko możliwe, warto "nadłożyć drogi", a nie tworzyć tzw. skrzyżowania. Nie tylko ułatwia to czytanie projektu, ale także zapobiega ewentualnym błędom wynikającym ze złego zrozumienia nakładających się linii. Po drugie, warto wszystkie połączenia rysować używając kątów prostych. Ponownie - nie jest to obowiązek, ale dzięki temu diagram wygląda estetyczniej i bardziej technicznie.

Narzędzia

Z pewnością zadajecie sobie pytanie, które narzędzie najlepiej się sprawdzi do rysowania diagramów UML (pomijając, oczywiście, kartkę i ołówek). Na rynku dostępnych jest wiele programów i każdy ma swoje preferencje - niektórzy wskażą np. Visual Paradigm. Ja wychodzę z założenia, że aplikacja powinna być jak najprostsza, najszybsza i sprawiająca najmniej problemów. Dlatego wskażę niekoniecznie najlepsze programy, ale te, których używam lub używałem i mogę je polecić.

Przez bardzo długi czas wykorzystywałem proste, javowe narzędzie UMLet. Nie jest już ono rozwijane, ale to nie zmienia faktu, że warto mu się przyjrzeć. Zawiera komponenty do wszystkich (lub prawie wszystkich) diagramów UML, w tym do klas. Jego wadą może być nieintuicyjny na początku interfejs oraz konieczność specjalnego kodowania np. relacji. Jednak ta niedogodność jest jednocześnie jego zaletą - po nauczeniu się możliwości UMLeta możemy bardzo szybko tworzyć nawet skomplikowane diagramy. Zdaję sobie jednak sprawę, że nie jest to aplikacja idealna - tym niemniej, warto się jej przyjrzeć, bo a nuż Wam się spodoba.

UMLeta zarzuciłem na rzecz aplikacji webowej i desktopowej diagrams.net (dawne draw.io). Zrobiłem to szczególnie dlatego, że to narzędzie umożliwia rysowanie nie tylko diagramów UML, ale też wielu innych - dużo częściej zdarza mi się przygotowywać wszelkie diagramy kontekstu, projektować komponenty, pokazywać zależności pomiędzy całymi systemami, a duża baza grafik tutaj mi to umożliwia. Niekiedy aplikacja potrafi dać się we znaki (szczególnie podczas przenoszenia elementów oraz linii), jednak mogę ją śmiało polecić.

Podsumowanie

Ten artykuł miał za zadania wprowadzić Was w UML-a i wyjaśnić, czym właściwie jest. Musimy być świadomi tego, że to pewien standard, który dotyczy wielu aspektów projektowania oprogramowania. Jednak nie da się ukryć, że najczęściej wykorzystywane są diagramy klas, które również kompleksowo omówiliśmy. Należy pamiętać, że nadrzędną zasadą jest to, aby model był klarowny dla wszystkich - jeśli z jakiegoś powodu zastosowanie UML-a wydaje się zbyt pracochłonne lub nie jestesmy pewni, czy zrobimy to dobrze, warto kierować się po prostu sumieniem i sprawić, aby odbiorcy diagramu mieli jak najmniej wątpliwości.

Pozdrawiam i dziękuję - Jakub Rojek.

We write not only blog articles, but also applications and documentation for our clients. See who we have worked with so far.

About author

Jakub Rojek

Lead programmer and co-owner of Wilda Software, with many years of experience in software creation and development, but also in writing texts for various blogs. A trained analyst and IT systems architect. At the same time he is a graduate of Poznan University of Technology and teaches at this university. In his free time, he enjoys playing video games (mainly card games), reading books, watching american football and e-sport, discovering heavier music, and pointing out other people's language mistakes.

Jakub Rojek