W artykule opisuję wyłącznie moje doświadczenia. Możliwe, iż ktoś ma zupełnie inne. Z chęcią poznam Twój punkt widzenia w komentarzach. Proszę weź też pod uwagę to, iż lepiej czuję się w mniejszych firmach niż ustrukturyzowanych korporacjach – ma to wpływ na moje zdanie na temat UML’a.
Jak mówi znane powiedzenie „jeden obraz jest wart tysiąca słów”. Takie przypadki zdarzają się także w programowaniu. Często w trakcie projektowania czy rozmawiania na temat fragmentu systemu programistom dużo łatwiej jest się porozumieć rysując. Takie rysunki mogą opisywać ogólną architekturę projektu, sposób podejścia do rozwiązania, kolejność zdarzeń w ramach procesu itd. Dobrze jest mieć wspólny język. W tym przypadku pomocny może być UML.
Czym jest UML
UML to akronim pochodzący od angielskiego określenia Unified Modeling Language. W polskim tłumaczeniu znany jest jako zunifikowany język modelowania. UML to jasno wyspecyfikowany język składający się z kilkunastu diagramów. Diagramy te pozwalają na formalne opisywanie i modelowanie struktur czy procesów.
Czy warto uczyć się UML’a
Odpowiadając na tak postawione pytanie w jednym zdaniu mogę powiedzieć, iż z mojego doświadczenia UML jest istotny i warto znać jego podstawy. Chociażby po to żeby rozszerzyć swój „słownik”, który później możemy użyć w trakcie rozmowy na temat programowania z inną osobą. UML to kolejne narzędzie, które możesz używać w odpowiednich sytuacjach. Rysunek, który usuwa zbędne szczegóły pokazując najbardziej istotne aspekty jest niezastąpiony.
Jednak to tylko część rzeczywistości. UML jest ważny, między innymi z wyżej wspomnianych powodów. Jednak ten sam UML to kobyła. Specyfikacja UML w wersji 2.5.1 zawiera 754 strony! Pracując jako programista od 2007 roku w całej swojej karierze nie spotkałem ani jednej osoby, która fanatycznie przestrzegałaby reguł opisujących UML’a1. Część funkcjonalność UML’a bardzo rzadko albo w ogóle nie jest wykorzystywana w praktyce.
Innymi słowy: tak, warto poznać UML’a, jednak wybiórczo.
Czy UML jest używany w pracy związanej z oprogramowaniem
Na początku muszę powiedzieć Ci trochę o moich doświadczeniach. Po kilku latach pracy zauważyłem, iż nie czuję się dobrze w korporacjach. Projekty, które wykorzystują „ciężkie metodologie” do ich prowadzenia też raczej nie są dla mnie. Mimo pracy jako programista od 2007 roku doświadczyłem wyłącznie niedużej części dużego świata firm IT. Bardzo możliwe, iż w środowisku, którego nie lubię nacisk na „czystego UML’a” jest większy.
Z mojego doświadczenia UML jest wykorzystywany w nieformalny sposób. To tak jak z językiem obcym – najważniejsza jest komunikacja. Możesz robić mnóstwo błędów, jeżeli jednak potrafisz się dogadać z drugą stroną to jesteś w domu. Właśnie komunikacja i umiejętność przekazywania informacji jest tu kluczowa. Innymi słowy jeżeli będziesz znać podstawy najbardziej istotnych diagramów, to ta wiedza powinna być wystarczająca.
Taki punkt widzenia potwierdza też badanie przeprowadzone na grupie programistów, testerów, architektów czy kierowników projektów2. Wynik przeprowadzonej ankiety potwierdza, iż UML używany jest raczej nieformalnie.
Pobierz opracowania zadań z rozmów kwalifikacyjnych
Przygotowałem rozwiązania kilku zadań algorytmicznych z rozmów kwalifikacyjnych. Rozkładam je na czynniki pierwsze i pokazuję różne sposoby ich rozwiązania. Dołącz do grupy ponad 6147 Samouków, którzy jako pierwsi dowiadują się o nowych treściach na blogu, a prześlę je na Twój e-mail.
Narzędzia
UML to diagram, rysunek. Do efektywnej pracy przyda Ci się zestaw narzędzi pozwalający tworzyć te diagramy.
Przede wszystkim polecam tablicę i marker (lub kartkę i długopis). To zdecydowanie najczęściej używane narzędzia przy pracy z nieformalnymi diagramami.
W trakcie pracy nad Samouczkiem, szczególnie pracując na artykułami dotyczącymi wzorców projektowych używam programów UMLet i yED. Są to darmowe programy, które pozwalają na tworzenie niektórych rodzajów diagramów UML. Istotne w nich dla mnie jest to, iż same programy są proste a tworzone diagramy zapisane są w postaci tekstowej (można je eksportować do formatów graficznych). Format tekstowy świetnie nadaje się do zapisania w repozytorium git’a.
Istnieje całkiem sporo narzędzi, które mają dużo większe możliwości, jednak dla komercyjnych zastosowań są płatne.
Najczęściej używane diagramy UML
Wspominałem to już wcześniej, jednak powtórzę to po raz kolejny. Poniżej prezentuję wyłącznie podzbiór diagramów. Skupiam się wyłącznie na tych, które doczekały się swojego praktycznego zastosowania w mojej dotychczasowej pracy komercyjnej. Pomijam diagramy, które wymagane były tylko w trakcie projektów na uczelni. Dodatkowo nie opisuję tu wszystkich możliwości, a jedynie te najczęściej używane.
Poniżej pokazałem diagram UML, który zawiera wszystkie dostępne diagramy. Na zielono oznaczyłem wyłącznie te, które opisuję w tym artykule:
Diagram klas
Diagram klas (ang. class diagram) to chyba najczęściej używany diagram. Służy do pokazania klas i zależności między nimi. Pozwala na szczegółowy opis klas zwracając uwagę na dostępne atrybuty i operacje. Ta szczegółowość pozwala na generowanie kodu na podstawie kompletnego diagramu. W praktyce nigdy nie spotkałem się z takim zastosowaniem. Diagram klas pozwala na „narysowanie” wycinka większego systemu. Jest on jednym z najbardziej rozbudowanych diagramów w notacji UML.
Klasa
Zacznę od pokazania symbolu klasy:
Klasa reprezentowana jest przez prostokąt podzielony na kilka części. W pierwszej z nich znajduje się nazwa klasy. W przykładzie jest to Customer. Następna sekcja zawiera atrybuty, kolejna metody.
Elementy, które są podkreślone oznaczają elementy statyczne. Na przykład atrybut DEFAULT_PROMO_CODE jest statycznym atrybutem klasy. Elementy pisane kursywą są abstrakcyjne (może dotyczyć także samej klasy), na przykład metoda fetchPromoCode jest abstrakcyjna.
Zarówno atrybuty jak i operacje mogą być poprzedzone symbolem. Dopuszczalne są między innymi:
- + – element publiczny,
- # – element „chroniony” (może odpowiadać protected w języku Java),
- - – element prywatny.
Klasa w przykładzie ma cztery atrybuty. Trzy atrybuty instancji i jeden atrybut klasy (statyczny). Atrybuty zapisywane są w formacie nazwa:typ. Ta sama klasa ma trzy metody. Prywatną metoda modifyOrderStats i dwie metody publiczne. Zwróć uwagę na to, iż metody mogą mieć określone typy parametrów i wartości zwracanej.
W podobny sposób oznacza się interfejs. W odróżnieniu od klasy zawiera on tak zwany stereotyp «interface». Na diagramie powyżej NotificationPipe jest interfejsem zawierającym dwie metody. Zauważ, iż w tym przypadku pominąłem symbole określające dostępność metod.
Atrybuty klas mogą być także opisane przez relacje pomiędzy klasami.
Relacje
Pomiędzy klasami mogą występować relacje. Przykładem relacji jest dziedziczenie. Relacje reprezentowane są przez różne symbole. Proszę spójrz na rysunek poniżej, na którym zebrałem możliwe relacje:
Zacznę od lewej kolumny. Pierwsza przerywana strzałka reprezentuje implementację. Jest używana do tego żeby pokazać jaki interfejs jest implementowany przez klasę. Druga oznacza dziedziczenie. W tym przypadku grot wskazuje klasę nadrzędną.
W prawej kolumnie znajdują się strzałki pokazujące relacje pomiędzy klasami inne niż implementacja czy dziedziczenie. Posegregowałem je w rosnąco według tego jak silne są relacje przez nie opisywane.
Relacje ze strzałkami mogą być jednokierunkowe albo dwukierunkowe. W przypadku relacji jednokierunkowej strona bez grota używa strony, na którą pokazuje grot. W przypadku braku grota relacja jest dwukierunkowa. Trochę inaczej sprawa wygląda z rombami. Opiszę to jak poznasz rodzaje relacji.
Najsłabszą relacją pomiędzy klasami jest zależność. Reprezentowana jest przez przerywaną linię. Zależność oznacza, iż jedna klasa w pewnym momencie używa innej, na przykład jako parametr, czy wartość zwracana metody. W przypadku zależności klasa, od której zależymy nie jest zapisana jako atrybut. Przykładem zależności w bibliotece standardowej Javy może być zależność Integer od String, widać ją na przykład w metodzie Integer.valueOf(String).
Kolejnym rodzajem relacji jest asocjacja. W tym przypadku jest to zapis, który może zastąpić atrybut klasy – jeżeli nie chcesz dodawać atrybut w prostokącie reprezentującym klasę możesz użyć asocjacji. Przykładem asocjacji w bibliotece standardowej Javy może być FileInputStream i String. Klasa FileInputStream posiada atrybut typu String reprezentujący ścieżkę do pliku.
Kolejną relacją jest agregacja. Agregacja wprowadza w relacji stronę, która jest „właścicielem”. Jedna klasa agreguje inną. Relacja tego typu oznaczona jest przez ciągłą linię z pustym rombem po stronie właściciela. W bibliotece standardowej tego typu relacja występuje pomiędzy ArrayList a klasą, której instancje przechowuje3.
Ostatnią relacją jest kompozycja. Kompozycja jest bardzo podobna do agregacji. Jest między nimi jedna znacząca różnica. W przypadku kompozycji „właściciel” jest odpowiedzialny za tworzenie (cykl życia) elementów, które grupuje. Przykładem kompozycji w bibliotece standardowej Javy może być implementacja HashMap, która zarządza elementami w kolekcji opakowując je w instancje HashMap.Node, które tworzy.
Proszę spójrz na diagram poniżej (dla czytelności pominąłem w nim atrybuty i operacje). Pokażę Ci na nim przykładowe relacje pomiędzy klasami:
- klasa LargeItem implementuje interfejs Item – implementacja,
- klasy VIP i OrdinaryCustomer dziedziczą po klasie abstrakcyjnej Customer – dziedziczenie,
- klasa OrderCalculator używa klasy Basket – zależność,
- klasa Basket wie o kliencie z którym jest powiązana (klasie Customer), odwrotne stwierdzenie także jest prawdziwe – asocjacja,
- klasa Basket może zawierać wiele instancji klasy Item – agregacja,
- klasa VIP zawiera wiele instancji klasy BonusCode i zarządza ich cyklem życia – kompozycja.
Wiesz już, iż strzałeczka oznacza kierunek relacji. Na przykład asocjacja pomiędzy ItemBundle a Item jest jednokierunkowa. ItemBundle wie o powiązanej klasie Item, Item zaś nie wie nic o ItemBundle. jeżeli strzałeczka nie jest umieszczona oznacza to, iż relacja jest dwukierunkowa – można „przejść” z jednej klasy do drugiej w obu kierunkach4.
Trochę inaczej wygląda sprawa w relacjach agregacji i kompozycji. W tym przypadku romby oznaczają stronę, która agreguje drugą stronę relacji. Na powyższym przykładzie klasa VIP zarządza cyklem życia BonusCode. BonusCode nic nie wie o klasie VIP.
Nowością dla Ciebie jest także komentarz do relacji (contains), który może ją opisywać. Nowe są także oznaczenia pokazujące liczność. W powyższym przykładzie jeden koszyk może zawierać wiele elementów (0..*).
Relacje dwukierunkowe
Wiesz już jak oznaczana jest dwukierunkowa relacja zależności. Na przykładzie wyżej pokazałem ją pomiędzy klasami Customer i Basket. W praktyce występują też bardziej zagmatwane przypadki. Wyobraź sobie klasę reprezentująca książkę – Book. Książka ma autora – Author. Jedna książka może być napisana przez wielu autorów, a jeden autor może napisać wiele książek. To klasyczna relacja „wiele do wielu”.
Często tego typu relacje wprowadzają nową klasę, która reprezentuje samą relację. W tym przypadku byłoby to autorstwo – Authorship. Poniższy diagram pokazuje przykładowe sposoby przedstawienia sytuacji tego typu na diagramie UML5.
W pierwszym przypadku Author przechowuje kolekcję Book i zarządza ich cyklem życia. Book wie o liście swoich autorów.
W drugim przypadku Author przechowuje kolekcję swoich „autorstw”. Podobną kolekcję przechowuje także Book.
Diagram komponentów
Wiesz już, iż diagram klas pozwala zobaczyć powiązania pomiędzy klasami w wąskiej części systemu. Diagram komponentów (ang. component diagram) pozwala spojrzeć na projekt z większej odległości. W diagram komponentów kluczową rolę odgrywają komponenty. Proszę spójrz na przykładowy symbol komponentu:
Jak widzisz komponent to prostokąt ze specyficzną ikonką w prawym górnym rogu. Komponent na rysunku wymaga jeden interfejs i sam dostarcza dwa. Komponent UserManagement wymaga dostępu do interfejsu persistence a sam zapewnia dwa inne register i ban.
Interfejs to kreska z kółkiem (interfejs udostępniany przez komponent) lub kreska z półkolem (interfejs wymagany przez komponent). Relacje pomiędzy komponentami realizowane są poprzez interfejsy. Można powiedzieć, iż komponenty łączy relacja zależności – najsłabsza z typów relacji występująca w diagramie klas.
Czym jest komponent
Wiesz już jak wygląda symbol komponentu i interfejsów. Tylko czym ten komponent adekwatnie jest? Cytując za specyfikacją:
A Component represents a modular part of a system that encapsulates its contents and whose manifestation is replaceable within its environment.
Powyższe zdanie można przetłumaczyć jako: komponent reprezentuje wydzieloną, opakowaną część systemu, której reprezentacja jest wymienna w ramach swojego środowiska.
A teraz raz jeszcze, moimi słowami. Komponent to część systemu, która ma swoje interfejsy. Interfejsy czyli dokładnie określone sposoby komunikacji. Interfejsy służą do komunikacji z pozostałymi komponentami. Każdy z komponentów można zastąpić inną implementacją. Istotne jest to, iż każda implementacja musi spełniać wymagania dotyczące jego interfejsów.
Jak widzisz definicja komponentów jest dość luźna. Do tego worka można wsadzić bardzo dużo rzeczy. Zaczynając od rozbudowanej implementacji w jednej klasie, poprzez ich zestaw znajdujący się w jednym pakiecie/module a na sporej części aplikacji kończąc. Ty jako autor diagramu sam decydujesz o tym do jakiego poziomu komponentów chcesz zejść. Istotne jest to, żeby poziom ten był spójny i prezentował wszystkie komponenty na diagramie „z podobnej odległości”.
Przykładowy diagram komponentów
Proszę spójrz na przykładowy diagram komponentów systemu, który może być odpowiedzialny za rezerwację biletów lotniczych:
Możesz na nim zobaczyć kilka komponentów, które są od siebie zależne. Każdy z nich definiuje interfejsy, które pozwalają komunikować się z innymi komponentami. Dla uproszczenia pominąłem opisowe nazwy interfejsów:
Diagram wdrożenia
Przedstawiłem Ci już diagram klas i diagram komponentów. Wiesz już, iż na system można spojrzeć z różnej odległości zwracając uwagę na coraz mniej szczegółów. Kolejnym stopniem ukrywającym szczegóły może być diagram wdrożenia (ang. deployment diagram).
Każdy działający projekt/aplikacja składa się z dwóch niezbędnych elementów. systemu (ang. software) i sprzętu (ang. hardware). Zauważ, iż żaden z powyżej omówionych diagramów nie poruszał tematyki sprzętu. Tę lukę wypełnia diagram wdrożenia. Diagram wdrożenia służy do odwzorowania zależności pomiędzy oprogramowaniem i/lub sprzętem. To właśnie na diagramie wdrożenia można pokazać sposób w jaki aplikacja/projekt powinien być zainstalowany/wdrożony.
Także tutaj specyfikacja UML pozwala na dużą dowolność jeżeli chodzi o szczegóły. Ty jako autor diagramu decydujesz, czy potrzebna jest dokładna specyfikacja poszczególnych elementów sprzętowych, czy zgrubna informacja w zupełności wystarczy.
Na początku swojej przygody z programowaniem ten diagram nie będzie Ci do niczego potrzebny. W późniejszym czasie bardzo pomoże Ci przy rozmowach na temat sposobu wdrożenia projektu.
Elementy diagramu wdrożenia
Przykład poniżej pokazuje elementy, które możesz spotkać na diagramach wdrożenia:
Kolejno od lewej na rysunku możesz zobaczyć:
- serwer typu n2-highmem-64,
- element o nazwie Nginx, który reprezentuje serwer HTTP,
- element Deployment, który wewnątrz zawiera artefakt o nazwie Artifact.
Zauważ, iż podobnie jak w przypadku diagramu klas wstępują tu stereotypy, które dodają informacje. Mimo tego, iż poszczególne części diagramu reprezentują zupełnie różne rzeczy, UML stosuje jedną graficzną reprezentację. W przypadku tego diagramu zupełnie nie przejmowałbym się sugestiami specyfikacji – w praktyce często spotyka się różnego rodzaju ikonki, które pozwalają lepiej zobrazować poszczególne elementy.
Przykładowy diagram wdrożenia
Proszę spójrz na przykład poniżej, który mógłby być diagramem wdrożenia dla aplikacji pozwalającej na rezerwację biletów:
Na diagramie wyżej możesz zobaczyć kilka oddzielnych klastrów (zestawów maszyn), przeznaczonych do wdrożenia poszczególnych komponentów. Kreski łączące komponenty obrazują powiązania między nimi.
Diagram sekwencji
Trzy poprzednie diagramy dotyczyły relacji pomiędzy elementami. Diagram sekwencji (ang. sequence diagram) jest jednym z tak zwanych diagramów interakcji. Kładzie on nacisk na komunikację, która odbywa się pomiędzy poszczególnymi klasami/obiektami. Diagram sekwencji pokazuje dokładnie sekwencję wykonania metod w poszczególnych obiektach. Diagram ten przydaje się do pokazania przebiegu skomplikowanej komunikacji.
Elementy diagramu sekwencji
Każdy z obiektów reprezentowany jest jako prostokąt połączony z pionową kreską. Ta linia oznacza „linię życia” – czas życia obiektu. Na diagramie może występować także tak zwany aktor. Aktor to człowiek albo system, który może brać udział w komunikacji. Proszę spójrz na przykład:
Wąskie pionowe prostokąty na liniach życia oznaczają czas, w którym dany aktor/obiekt był aktywny. Aktywność była niezbędna do wypełnienia żądania, które dany obiekt wysłał/otrzymał.
Niektóre obiekty mogą żyć krócej niż pozostałe. Koniec życia obiektu zaznaczany jest przez znak X na ich linii życia.
Diagram, który pokazałem powyżej może służyć jako przykład opisujący mechanizm wysyłania wiadomości e-mail. Na początku aktor inicjalizuje proces, Instance 1 obsługuje akcję sendEmail przekazując ją asynchronicznie do Instance 2. Następnie dwukrotnie sprawdza czy wysłanie wiadomości się powiodło, po czym zwraca informację do aktora.
Rodzaje komunikatów
Wiesz już, iż pionowe kreski oznaczają linię życia. Im wyżej na diagramie, tym wcześniej coś się wydarzyło. Poziome kreski oznaczają komunikaty. Jak widzisz istnieje kilka rodzajów komunikatów:
Strzałki w lewej kolumnie oznaczają komunikaty synchroniczne. Strzałka z ciągłą liną oznacza wysłanie komunikatu, strzałka z przerywaną linią otrzymanie odpowiedzi. W prawej kolumnie pokazałem strzałkę reprezentującą asynchroniczne wysłanie komunikatu.
Dodatkowe materiały do nauki
Jak wspomniałem na początku artykułu nie było moim zamiarem wyczerpanie tematu. Celowo skupiłem się wyłącznie na diagramach, które moim zdaniem są najczęściej używane. Ponadto pominąłem sporą część możliwości, których nie używałem w praktyce. Właśnie te diagramy były dla mnie najbardziej przydatne w sesjach przy tablicach z kolegami z pracy. jeżeli jednak temat UML Cię zainteresował zapraszam Cię do zapoznania się z zestawem materiałów dodatkowych. Zacznę od materiałów oficjalnych:
- Oficjalna strona UML’a,
- Specyfikacja UML 2.5.1 – jest niezastąpiona jeżeli potrzebujesz zajrzeć do źródła i chcesz poznać wszystkie szczegóły, w innym przypadku gorąco nie polecam.
Dodatkowo mam dla Ciebie artykuł podsumowujący badanie na temat użycia diagramów w praktyce.
Uczelnie techniczne często mają osobne kursy poświęcone tematyce UML’a. Czasami jest też tak, iż UML zajmuje część wykładu dotyczącego na przykład inżynierii oprogramowania. Przygotowałem dla Ciebie zestaw odnośników do materiałów przygotowanych na uczelniach:
- Fragment wykładu z UW dotyczący UML’a część I,
- Fragment wykładu z UW dotyczący UML’a część II,
- Opis diagramu klas w materiałach dla studentów AGH,
- Opis diagramu komponentów w materiałach dla studentów AGH,
- Opis diagramu wdrożenia w materiałach dla studentów AGH,
- Opis diagramu sekwencji w materiałach dla studentów AGH,
Na koniec zestawienie linków do artykułów na Wikipedii:
- Artykuł o UML na polskiej Wikipedii,
- Artykuł o UML na angielskiej Wikipedii,
- Diagram klas na angielskiej Wikipedii,
- Diagram klas na polskiej Wikipedii,
- Diagram komponentów na angielskiej Wikipedii,
- Diagram wdrożenia na angielskiej Wikipedii,
- Diagram sekwencji na angielskiej Wikipedii.
Podsumowanie
Znasz już mój punkt widzenia dotyczący UML’a. Wiesz, iż moim zdaniem warto znać podstawy tego języka. Mogą Ci się one przydać w codziennej pracy. jeżeli lubisz pracować w bardziej formalnym środowisku może się okazać, iż UML będzie niezastąpiony. Znasz kilka rodzajów diagramów, które mogą być przydatne. Znasz także darmowe narzędzia, które pozwalają na tworzenie diagramów UML.
Mam nadzieję, iż artykuł przypadł Ci do gustu. Proszę daj znać w komentarzach co sądzisz o UML’u. Czy Twoim zdaniem znajomość tego języka przydaje się w codziennej pracy? A może to już tylko zaszłość, która powoli odchodzi do lamusa? Jestem interesujący Twoje opinii.
Dodatkowo, jak zwykle, proszę Cię o podzielenie się odnośnikiem do artykułu ze swoimi znajomymi. W ten sposób pomożesz mi dotrzeć do nowych Czytelników, za co z góry Ci dziękuję. jeżeli nie chcesz pomiąć kolejnych artykułów proszę zapisz się do samouczkowego newslettera i polub Samouczka na Facebook’u. To tyle na dzisiaj, trzymaj się i do następnego razu!
-
Sam też nie mogę ich fanatycznie przestrzegać – nie znam tej specyfikacji wystarczająco dokładnie. ↩
-
Swoją drogą badanie było przeprowadzone na dość małej grupie kontrolnej. W związku z tym jest ryzyko, iż wyniki nie są w pełni miarodajne. ↩
-
Tak na prawdę ArrayList zawiera tablicę instancji typu Object, to dzięki typom generycznym na zewnątrz widoczna jest inna klasa. ↩
-
Można powiedzieć, iż to swego rodzaju uproszczenie. Tak naprawdę to można „przejść” z instancji jednej klasy do drugiej i odwrotnie. ↩
-
Do tego dochodzi jeszcze modelowanie relacji tego typu w relacyjnych bazach danych, jednak to jest już zupełnie inna para kaloszy i temat na osobny artykuł ;). ↩