3. Modelowanie dynamiki systemu

Każdy modelowany system podlega zmianom. Obiekty składające się na system wchodzą we wzajemne interakcje, wykonują określone akcje oraz zmieniają swój stan. Przez model dynamiki rozumiemy więc przedstawianie przedmiotu modelowania od strony istotnych zmian jakim on podlega.

3.1. Model przypadków użycia

Częstą potrzebą podczas modelowania systemu oprogramowania jest potrzeba zidentyfikowania elementarnych jednostek funkcjonalności systemu z punktu widzenia jego użytkowników. Aby zidentyfikować funkcjonalność systemu z punktu widzenia jego użytkowników powinniśmy w pierwszej kolejności zdefiniować tychże użytkowników. W języku UML do tego celu wykorzystujemy aktorów. Aktor definiuje klasę obiektów spoza modelowanego systemu.

Podstawową notacją aktora w języku UML jest ikona człowieka narysowana prostymi kreskami, co ilustruje rysunek 3.1 (ikona po lewej stronie). Częstą praktyką jest nadawanie stereotypu «system» aktorom definiującym zewnętrzne systemy informatyczne (ikona w środku rysunku). Nadanie stereotypu może być również związane ze zmianą kształtu ikony.

Rysunek 3.1: Warianty notacji aktora

Możliwe jest stosowanie relacji generalizacji między aktorami.  Przykład zastosowania tych relacji widzimy na rysunku 3.2. Dziedziczeniu podlegają głównie relacje aktorów z przypadkami użycia.

Rysunek 3.2: Relacje generalizacji dla aktorów

Dla aktorów i przy ich udziale realizowane są wspomniane na początku „elementarne jednostki funkcjonalności”, czyli – przypadki użycia systemu. Przypadek użycia definiujemy jako klasę zachowań modelowanego systemu (tzw. podmiotu), prowadzących do osiągnięcia obserwowalnego, istotnego rezultatu dla jakiegoś aktora (lub aktorów).

Podstawową notacją dla przypadków użycia w języku UML jest ikona elipsy, co ilustruje rysunek 3.3. Nazwa przypadku użycia może być umieszczona pod lub wewnątrz elipsy. Jako ciekawostkę można przytoczyć wariant notacji, który stosuje ikonę klasy.

Rysunek 3.3: Warianty notacji przypadku użycia

Każdy przypadek użycia powinien mieć jasno określony cel, który powinien wynikać z jego nazwy. Ważne jest, aby nazwy były pisane w jednolitej formie. Można na przykład przyjąć formę polecenia (np. „Zarejestruj pojazd”, „Dodaj użytkownika”, „Pokaż …”, „Obsłuż …”).

Sposobem na realizację celu określonego w nazwie przypadku użycia jest wymiana komunikatów miedzy aktorem (lub aktorami) a system. Komunikaty te przeplatane są także akcjami systemu, których skutki są następnie widoczne dla aktorów. Pierwszy komunikat pochodzi zazwyczaj od aktora, który uruchamia daną funkcjonalność. Przypadek użycia powinien definiować wszystkie możliwe alternatywne zachowania systemu prowadzące do danego celu. Te alternatywy mogą prowadzić do zadanego celu inną drogą niż podstawowy sposób zachowania. Mogą również kończyć się niepowodzeniem – cel nie zostanie osiągnięty mimo podjęcia próby jego osiągnięcia.

Na diagramach przypadków użycia umieszczamy aktorów i przypadki użycia wraz z łączącymi je relacjami. Na rysunku 3.4 widzimy diagram zawierający przykłady takich relacji. Najczęściej spotykaną jest relacja asocjacji między aktorem i przypadkiem użycia. Relacja ta oznacza możliwość uczestnictwa danego aktora w powiązanym z nim przypadku użycia. W ramach takiego uczestnictwa, aktor może być tzw. aktorem głównym. Oznacza to, że aktor uruchamia dany przypadek użycia i jest on realizowany na jego rzecz. Udział aktora jako aktora głównego możemy zaznaczyć poprzez zastosowanie asocjacji nawigowalnej, skierowanej do przypadku użycia.

Rysunek 3.4: Przykładowy diagram przypadków użycia

Aktor może również pełnić rolę poboczną (pomocniczą) w relacji z przypadkiem użycia. Oznacza to, że aktor zaczyna brać udział dopiero w dalszych krokach scenariuszy danego przypadku użycia. Uczestnictwo aktora pomocniczego możemy zaznaczyć poprzez zastosowanie nawigowalności asocjacji skierowanej w stronę tegoż aktora.

Oprócz relacji między aktorami i przypadkami użycia często stosowane są relacje między przypadkami użycia. Język UML standardowo definiuje dwie relacje zależności – relację «include» i  relację «extend». Relacja «include» („wstawienie”) oznacza bezwarunkowe włączenie w treść scenariuszy jednego przypadku użycia, treści scenariuszy innego. Na rysunku 3.4 widzimy jedną taką relację, oznaczoną przerywaną strzałką z odpowiednim stereotypem.

Druga z nich to relacja «extend» („rozszerzenie”). Oznacza ona możliwość wplecenia treści scenariuszy przypadku użycia rozszerzającego w treść scenariuszy przypadku użycia rozszerzanego. Możliwość wplecenia może podlegać określonym warunkom, które powinny być dołączone do specyfikacji danej relacji «extend». W najprostszym przypadku, wplecenie warunkowane jest przez wybraniem opcji uruchamiającej rozszerzający przypadek użycia podczas wykonywania rozszerzanego przypadku użycia.

Semantyka relacji «include» i «extend» oparta jest na bezwarunkowym lub warunkowym wstawianiu treści jednych przypadków użycia w treść innych. Zrozumienie tej semantyki często sprawia kłopoty. Z tego powodu powstała propozycja alternatywy - relacji «invoke». Relacja ta ma semantykę podobną do semantyki wywołania procedury. Zawsze jest skierowana od przypadku wywołującego do przypadku wywoływanego. Wywołanie może być bezwarunkowe lub możemy dla niego określić warunek. Rysunek 3.5 pokazuje fragment zawierający aktora i przypadki użycia z rysunku 3.4, gdzie zamiast standardowych relacji zastosowano relacje «invoke».

Rysunek 3.5: Wykorzystanie relacji «invoke»

Podobnie jak dla aktorów, również między przypadkami użycia możliwe jest stosowanie relacji generalizacji. Oznacza ona, że przypadek użycia specjalizowany dziedziczy cechy przypadku użycia ogólnego. Rysunek 6.5 dostarcza nam odpowiedniego przykładu.

6.2. Model czynności

Częstą potrzebą podczas modelowania systemów jest przedstawienie jakiegoś procesu w postaci schematu blokowego. Oznacza to konieczność zbudowania modelu składającego się z akcji oraz przepływów sterowania między akcjami, czyli modelu czynności. Zadaniem modelu czynności jest połączenie takich akcji w sieć (graf) opisującą kolejność ich wykonywania.

Rysunek 3.6 przedstawia przykładowy diagram, który posłuży nam do wyjaśnienia notacji modelu czynności. Diagramy czynności stanowią grafy, przy czym wyróżniamy w nich kilka rodzajów węzłów. Najbardziej typowym węzłem są akcje, oznaczane ikoną prostokąta z zaokrąglonymi rogami. Wewnątrz ikony umieszczona jest treść akcji, która powinna stanowić nazwę elementarnej operacji wykonywanej w ramach opisywanego procesu (np. „dokonanie płatności zaliczki”). Akcje mogą posiadać wypustki (ang. Pin) oznaczane małymi kwadratami umieszczonymi na krawędziach akcji. Wypustki służą definiowaniu danych przepływających między akcjami i oznaczane są zazwyczaj nazwą tych danych (np. „dowód rejestracyjny”). Do oznaczania decyzji służą węzły decyzyjne (ang. Decision Node) oraz węzły scalenia (ang. Merge Node). Obydwa rodzaje węzłów oznaczane są ikoną rombu (diamentu). Obok ikony węzła decyzyjnego można umieścić treść podejmowanej decyzji, często w formie pytania (np. „jaka dostępność?”). Na diagramach czynności możemy również zaznaczać ciągi akcji wykonywanych równolegle. Do tego celu wykorzystujemy belki synchronizacji. Belka rozwidlenia (ang. Fork) pozwala rozdzielić sterowanie między kilka równoległych przepływów akcji. Z kolei belka złączenia (ang. Join) pozwala połączyć równoległe przepływy akcji w jeden. Belki synchronizacji rysujemy jako poziome lub pionowe grube kreski. Każdy diagram czynności powinien również zawierać węzeł początkowy (duża czarna kropka) oraz węzły końcowe (mała czarna kropka z obwódką).

Rysunek 3.6: Przykładowy diagram czynności

Między węzłami umieszczamy przepływy sterowania (ang. Control Flow) lub przepływy obiektów (ang. Object Flow). Obydwa rodzaje przepływów mają taką samą notację strzałki. Obok strzałki możemy umieścić warunek (ang. Guard), który zapisujemy jako tekst umieszczony w nawiasach kwadratowych (np. „[samochód dostępny]”). Przepływ sterowania oznacza proste przejście między węzłami, natomiast przepływ obiektów dodatkowo oznacza przesłanie między akcjami określonego zestawu danych. Przepływy obiektów umieszcza się najczęściej między wypustkami akcji. W ten sposób możliwe jest łatwe określenie danych przesyłanych takimi przepływami.

Działanie opisywane diagramem czynności kończy się w momencie dotarcia do węzła końcowego. W tym momencie wszystkie pozostałe akcje są przerywane (jeśli działają) i działanie całego systemu opisywanego diagramem jest przerywane. Istnieją również specjalne węzły końcowe, tzw. węzły końca przepływu (ang. Flow Final Node). Dotarcie do takiego węzła nie kończy działania całego systemu, a jedynie danej gałęzi. Węzły końca przepływu reprezentowane są za pomocą okręgu ze znakiem „X” w środku.

Bardziej skomplikowane sieci akcji możemy dzielić na mniejsze elementy. W tym celu, możemy definiować tzw. węzły czynności (ang. Activity Node). Czynność jest podobna do akcji w tym sensie, że definiuje jakąś operację wykonywaną przez obiekty danego systemu. Różnica polega na tym, że czynność nie jest operacją elementarną, ale może składać się z wielu akcji.

3.3. Model maszyny stanów

Bardzo podobnym w swojej notacji do modelu czynności jest model maszyny stanów. Podstawowa różnica jest taka, że węzły w diagramach maszyny stanów definiują stany, a nie akcje.

Odpowiedni diagram jest przedstawiony na rysunku 3.7. Stany (ang. State) reprezentowane są za pomocą ikony prostokąta z zaokrąglonymi rogami. Każdy stan określa pewną stabilną konfigurację systemu (np. obiektu), która może trwać w czasie. Między stanami prowadzimy przejścia (ang. Transition), oznaczane strzałkami. Przejścia mogą być opisane przez podanie tzw. wyzwalacza (ang. Trigger). Wyzwalacz odpowiada konkretnemu zdarzeniu, które powoduje zmianę stanu.

Rysunek 3.7: Przykładowy diagram maszyny stanów

Wyzwalacz może być dodatkowo wyposażony w warunek, zgodnie ze standardową notacją warunków w języku UML (napis w nawiasach kwadratowych). W przypadku wystąpienia zdarzenia określonego wyzwalaczem, dodatkowo badany jest warunek. Przejście między stanami następuje jedynie w razie spełnienia tego warunku.

Na diagramie maszyny stanów występują również tzw. pseudostany. Najczęściej używanymi pseudostanami są pseudostan początkowy oraz pseudostan terminalny. Pseudostan początkowy (ang. Initial Pseudostate) oznacza stan, w którym rozpoczyna się działanie maszyny stanów (np. stan obiektu zaraz po jego utworzeniu). Na diagramie, taki pseudostan połączony jest zawsze jednym przejściem z jednym stanem właściwym. Analogicznie, pseudostan terminalny (ang. Terminal Pseudostate) oznacza możliwe stany, w których znajduje się maszyna stanów (np. obiekt) przed jej skasowaniem (zakończeniem działania). Do pseudostanu terminalnego może prowadzić wiele przejść ze stanów właściwych.

3.4. Model sekwencji

Przedstawione wyżej modele czynności reprezentują procesy jako ciągi akcji wykonywanych przez różne obiekty. Często jednak przydatne jest przedstawienie procesu jako sekwencji komunikatów wymienianych między obiektami. Do tego celu możemy wykorzystać modele sekwencji.

Podstawowymi elementami diagramów sekwencji są linie życia (ang. Lifeline) oraz komunikaty (ang. Message), co ilustruje rysunek 3.8. Linia życia reprezentuje działanie jednego obiektu. Rysujemy ją jako pionową przerywaną linię zakończoną u góry ikoną obiektu. Między liniami życia umieszczamy komunikaty, które oznaczane są jako strzałki. Kolejność przesyłania komunikatów wynika z kolejności ich umieszczenia na diagramie, który czytamy z góry na dół. Przesłanie komunikatu w jednego obiektu do drugiego powoduje wykonanie jakiejś operacji przez obiekt docelowy. Wykonanie operacji zaznaczamy tzw. belką wykonania (ang. Execution Specificaton). Belka taka rysowana jest jako wąski prostokąt nałożony na linię życia obiektu, do którego kierowany jest komunikat. Po otrzymaniu takiego komunikatu, obiekt ten podejmuje działanie zaznaczone odpowiednią belką wykonania.

Rysunek 3.8: Podstawowe elementy notacji diagramów sekwencji

Zasadniczo wyróżniamy trzy rodzaje komunikatów. Komunikaty synchroniczne (ang. Synchronous Message) oznaczane są strzałką z wypełnionym grotem. Komunikaty takie najczęściej powiązane są z komunikatami zwrotnymi, oznaczanymi strzałką z pustym grotem, pisaną linią przerywaną. Znaczenie komunikatów synchronicznych i zwrotnych wyjaśnia diagram po lewej stronie rysunku 3.8. Wysłanie komunikatu synchronicznego „włącz()” powoduje przekazanie sterowania z obiektu „mój:Samochód” do obiektu „benzynowy:Silnik”, co symbolizuje żeton „1”. Drugi z tych obiektów podejmuje działanie („włącza się”), a pierwszy przechodzi w stan oczekiwania. Po wykonaniu operacji przez silnik, sterowanie (żeton „1”) wraca do samochodu. Oznaczane to jest komunikatem zwrotnym „włącz():OK”. Po zwróceniu mu sterowania, samochód może podjąć dalsze działania, w tym np. przesłać kolejne komunikaty. Jak widzimy, dwa obiekty synchronizują swoje działania, gdyż jeden czeka na zakończenie działania drugiego.

Trzecim rodzajem komunikatów są komunikaty asynchroniczne (ang. Asynchronous Message), które oznaczamy strzałką z pustym grotem. Znaczenie komunikatów asynchronicznych wyjaśnia diagram po prawej stronie rysunku 3.8. Wysłanie komunikatu „ogrzej(liczba)” powoduje powstanie dwóch ścieżek sterowania (wątków), oznaczonych żetonami „1” i „2”. Obydwie ścieżki działają równolegle, co oznacza, że obydwa obiekty mogą wykonywać swoje operacje w tym samym czasie. W przeciwieństwie do sytuacji z komunikatem synchronicznym, tutaj obiekt „mój:Samochód” nie czeka na zakończenie działania operacji „ogrzej()”. W naszym przykładzie, obiekt „kierowcy:Siedzenie” uruchamia operację „włącz_grzałkę()”, a w tym samym czasie obiekt „mój:Samochód” uruchamia operację „rozpoznaj_klucz()”. Przy okazji widzimy notację komunikatów kierowanych przez obiekty do samych siebie. Ważną cechą komunikatów asynchronicznych jest brak związanych z nimi komunikatów zwrotnych.

Na diagramach sekwencji możemy również definiować cykl życia obiektów. Obiekty mogą być tworzone i niszczone w wyniku przesyłania komunikatów od innych obiektów. Przykład takich komunikatów widzimy na rysunku 6.9. Pierwszy z komunikatów jest komunikatem utworzenia (ang. Create Message). Oznaczamy go strzałką przerywaną prowadzącą od jakiejś linii życia do ikony obiektu rozpoczynającej inną linię życia. Ikona obiektu jest rysowana nas poziomie komunikatu, co ma symbolizować utworzenie obiektu w wyniku tego komunikatu. Zakończenie życia (zniszczenie) obiektu zaznaczamy znakiem „X” umieszczonym na końcu linii życia. Najczęściej, obiekt kończy życie w wyniku otrzymania komunikatu, co ilustruje rysunek 3.9 (komunikat „skasuj”).

Rysunek 3.9: Tworzenie i kasowanie obiektów na diagramie sekwencji

Na diagramach sekwencji możemy modelować przebiegi alternatywne oraz pętle. Używamy wtedy tzw. fragmentów łączonych (ang. Combined Fragment) wyrażanych za pomocą prostokątnych ramek obejmujących odpowiednie komunikaty. Typ ramki określony jest w polu znajdującym się w lewym górnym rogu ramki. Najczęściej stosowane są ramki typu „alt”, „opt” i „loop”. Po nazwie typu ramki można umieścić jej nazwę.

Przykłady ramek „alt” i „loop” widzimy na rysunku 3.10. Najbardziej zewnętrzna jest ramka „alt” o nazwie „uruchomienie”. Ramka taka podzielona jest przerywanymi poziomymi liniami na kilka części. Każda część oznaczona jest warunkiem umieszczonym w nawiasach kwadratowych. Działanie ramki polega na tym, że wykonywane są komunikaty zawarte w obszarze, dla którego spełniony jest warunek.

Ostatnią (tutaj – najbardziej wewnętrzną) ramką na rysunku 3.10 jest ramka „loop” (pętla) o nazwie „ogrzewanie siedzeń”. Ramka taka oznacza wykonanie zawartej w niej sekwencji komunikatów dopóki spełniony jest określony warunek.

Rysunek 3.10: Przykładowy diagram sekwencji z ramkami „alt” i „loop”

W poprzednich przykładach, diagramy sekwencji były tworzone na poziomie modelu klas. Często jednak korzystne jest pokazanie dynamiki wymiany komunikatów między komponentami, m.in. za pośrednictwem ich interfejsów. Odpowiedni przykład widzimy na rysunku 3.11. Etykiety komunikatów odpowiadają operacjom interfejsów przedstawionych na diagramie definiującym komponent „PulaSamochodow”. Istotnym elementem notacyjnym jest linia życia interfejsu „IPulaSamochodow”, która jest „zagnieżdżona” w linii życia obiektu komponentu „PulaSamochodow”.

Rysunek 3.11: Realizacja przypadku użycia „Pokazanie listy samochodów”

Charakterystycznym elementem na rysunku 3.11 jest ramka typu „ref” (referencja) z etykietą „Dodanie nowego samochodu”. Jest to tzw. użycie interakcji (ang. Interaction Use). Ramka ta oznacza odniesienie do innego diagramu interakcji (tu: sekwencji). Znaczenie tej ramki referencji jest takie, że cały diagram z rysunku 3.12 jest niejako wstawiany w miejsce ramki.

Rysunek 3.12: Realizacja przypadku użycia „Dodanie nowego samochodu”

 Rysunek 3.12 ilustruje zastosowanie ramki „opt” (opcja). Jest to niejako wariant ramki „alt” zawierający tylko jeden warunek. Zawartość ramki wykonywana jest tylko w przypadku spełnienia warunku. W przeciwnym razie cała zawartość ramki jest pomijania w wykonaniu danej sekwencji.

Analizując diagram na rysunku 3.12, jak również wszystkie diagramy poprzednie, warto zwrócić uwagę na ciągłość przepływu sterowania. W typowych zastosowaniach, komunikaty powinny tworzyć spójną sekwencję, gdzie kolejne komunikaty wynikają bezpośrednio z poprzednich. Ponadto, komunikaty nie powinny pojawiać się „znikąd”, ale mieć swoje źródło w belce wykonania, która wynika z poprzednich komunikatów w sekwencji. Inaczej mówiąc, komunikaty nie powinny występować w miejscach, w których dany obiekt nie otrzymał wcześniej sterowania.