Projektowanie systemów informatycznych

część 4

Sebastian Wyrwał

W poprzednim artykule dokonano przeglądu mechanizmów służących
do podziału systemu informatycznego na moduły w różnych językach
programowania. Jako tło rozważań przyjęto pobieżną historię języków
programowania, ukazując, iż ewolucja języków programowania począwszy od lat
siedemdziesiątych dotyczy takich właśnie mechanizmów. Językiem o
bardzo silnych mechanizmach modularyzacji jest Ada, wywodząca się z języka
Pascal. Następnie wymieniono wady metod strukturalnych. Słabość tych metod
ujawnia się szczególnie przy modyfikacji systemu i różnego rodzaju zmianach
reprezentacji danych. System zaprojektowany i zaimplementowany strukturalnie nie
jest elastyczny. Jeszcze innym problemem jest utrudniona konfiguracja systemu w
trakcie jego działania, polegająca na zmianie sposobu prezentacji danych lub
zmianie stylu interfejsu użytkownika. Równie trudno skatalogować w metodach
strukturalnych pewne pomysły projektowe – tworząc jakby książkę
kucharską przeznaczoną dla projektantów. W końcowej części rozważań
dokonano łagodnego wprowadzenia w główne założenia metodologii obiektowej;
wskazano na to, iż jej pojawienie się jest zgodne z głównymi trendami w inżynierii
oprogramowania – dążeniem do wydzielenia coraz to mniejszych bytów
projektowych i odpowiadających im bytów programowych. Dążymy do tego, aby
owe byty (w relacji projektowy – odpowiadający mu programowy) mogły być
projektowane, implementowane i testowane niezależnie od innych, oraz do
oddzielenia wymagań od sposobu ich realizacji. (Na marginesie mówiąc, to
ostatnie dążenie jest jakby ucieleśnieniem idei separation of concerns,
która jest kluczowa we współczesnej inżynierii oprogramowania).

W niniejszym artykule rozpoczęto omówienie obiektowego języka
projektowania i analizy, na przykładzie szeroko ostatnio stosowanego – także w polskich firmach
– języka Unifiled Modelling Language (UML).
Warto w tym miejscu zwrócić uwagę na ważną sprawę – zastosowanie
tego języka nie ogranicza się do modelowania lub/i projektowania systemów
informatycznych. Może on być stosowany w odniesieniu do różnych systemów.
Zaprezentowano kilka podstawowych diagramów i widoków – diagramy
przypadków użycia, klas oraz interakcji. Zaproponowano pewien uproszczony sposób
postępowania przy projektowaniu, stanowiący zarys metodologii projektowania.
Ukazano kilka powszechnie spotykanych problemów związanych z projektowaniem
przy użyciu języka UML, oraz – na zakończenie – nakreślono
potrzebę podziału systemu informatycznego na warstwy tak, aby zapewnić jego
przenośność.

Wprowadzenie do języka UML

Język ten wywodzi się [2] z języka OMT, OOSE oraz
metodologii Booch’a. Nie jest on związany z konkretnym narzędziem lub językiem
implementacji; ważne jest też to, iż standaryzacja dotyczy języka, a nie
metody projektowania, która może być odmienna dla różnych firm. Język jest
narzędziem zapisu projektu; metoda określa, jak ów opis jest tworzony, czyli
jak muszą zostać wykonane czynności, aby uzyskać projekt danego systemu lub
przedsięwzięcia.

W prezentowanym języku używana jest czytelna notacja
graficzna; natomiast, jeśli chodzi o stopień sformalizowania metod o niego
opartych, to zakwalifikować je należy do metod półformalnych. Tak więc,
stopień ścisłości opisu jest dość duży, nie na tyle jednak, aby możliwe
było dowodzenie poprawności specyfikacji lub jej formalne przekształcenia.

Analiza oraz modelowanie w znacznie większym stopniu dotyczą
informatyzowanej rzeczywistości niż samego systemu. Dobrze skonstruowany
system odwzorowuje rzeczywistość i pozwala na jej „bezbolesną”
reprezentację. Jakże irytująca, choć niestety dość częsta, jest sytuacja,
kiedy w jakiejś instytucji słyszymy „tego nie można zrobić, bo system
nie pozwala…”. Może to być wybieg niechętnego nam urzędnika, może
być również skutek tego, iż analitycy danej sytuacji nie przewidzieli.
Zaistnienie takiej sytuacji świadczy dobitnie, że analiza rzeczywistości
została wykonania źle. Należy tutaj jednak wyraźnie oddzielić omawianą
sytuację od takiej, w której konkretny pracownik nie ma, bo mieć nie
powinien, uprawnień do wykonania danej operacji. Raz jeszcze z całą stanowczością
należy powtórzyć, że sytuacja, w której oprogramowanie
„decyduje” o regułach danego przedsięwzięcia jest patologiczna.

Dlatego tak istotne jest, aby język modelowania
(projektowania) był jak najbardziej elastyczny, niezależny od dziedziny, w której
istnieje system, i aby pozwalał na modelowanie owej rzeczywistości, a nie
tylko samego systemu. Równie ważnym aspektem języka jest jego rozszerzalność
– rozumiana jako możliwość dodawania nowych bytów do samego języka
tak, aby było możliwe dostosowanie go do konkretnego zastosowania. Należy to
rozumieć w ten sposób, że język z jednej strony powinien, w obrębie swych
standardowych mechanizmów, pozwalać na łatwe zobrazowanie (zamodelowanie)
dowolnego systemu (rzeczywistości). Z drugiej zaś, powinien być rozszerzalny
tak, aby można było wprowadzić do niego nowe byty (a raczej przedefiniować
stare). Umożliwia to zastosowanie koncepcji zbliżonych do tych opartych o
koncepcje rodzin. Wybiegając w naszych rozważaniach naprzód przyjmijmy, iż z
notacji graficznej możliwe jest przejście do notacji tekstowej1 oraz, że mamy
do dyspozycji narzędzie generujące kod (w języku programowania) aplikacji.
Zdefiniowanie nowych bytów w języku modelowania oraz odpowiadających im bytów
w języku programowania może być rozumiane, jako zdefiniowanie rodziny produktów.
Specyfikacja konkretnego systemu i jego wygenerowanie odpowiada wtedy stworzeniu
członka tej rodziny. Nie jest to rozwiązanie omawiane, wg wiedzy autora, w
literaturze; jest ono ponadto ukierunkowane zupełnie w inną stronę niż
kierunek przewidywany przez autorów mechanizmu rozszerzania (ang. Extension
mechanizm). Należy wziąć jednak pod uwagę takie rozszerzanie języka, aby można
było zastosować idee oparte o koncepcję rodzin.

Model, jego widoki i diagramy

Omawiany język pozwala na zobrazowanie różnych aspektów
systemu, które z kolei są przydatne dla różnych grup osób związanych z
wytwarzaniem oprogramowania. Stanowi to dość istotny postęp w stosunku do
powszechnie znanych metod strukturalnych, w których modeluje się procesy i
dane, ale nie można, lub trudno jest modelować np. wymagania użytkownika w
stosunku do systemu, wyrażone w oderwaniu od szczegółów realizacji (wyrażonych
tam w postaci procesów lub danych). Oczywiście można to łatwo
„regulować” stopniem abstrakcji opisu, przechodząc na najwyższym
z poziomów do jednego procesu. Nie jest to jednak ewidentnie to samo, co
opisanie funkcjonalności w oderwaniu od jej realizacji. Język UML jest
czterowarstwowy: meta metamodel, metamodel, model, obiekty użytkownika. W
rezultacie, opis języka jest zrealizowany z użyciem jego samego na poziomie
metamodelu. Model z kolei opisuje projektowany system. Poziom obiektów użytkownika
opisuje konkretny stan (w danej chwili) systemu. Pozostawiając na razie
teoretykom szczegółowe rozważania na temat metamodelu, można powiedzieć, iż
np. klasa jest obiektem metaklasy a relacja jest obiektem metarelacji.

Model systemu składa się z różnych widoków – każdy
z nich skupia się na pewnych aspektach systemu i ignoruje inne [2]. Jest więc
przydatny dla innych grup osób uczestniczących w przedsięwzięciu
informatycznym. Można powiedzieć, iż omawiany język – oprócz funkcji
notacyjnej w odniesieniu projektu systemu – ma również funkcję
komunikacyjną. Istnieją bowiem widoki, które są używane wspólnie przez różne
grupy osób biorących udział w projekcie.

W toku dalszych rozważań słowo „model” będzie
oznaczać model w trzeciej warstwie, który odpowiada statycznemu opisowi
systemu użytkownika, lub model w czwartej warstwie, który odpowiada stanowi
systemu w danej chwili w zależności od kontekstu.


Rys. 1. Struktura projektu

Na rysunku 1 przedstawiono strukturę projektu. Model
odpowiada projektowanemu systemowi i składa się z kilku widoków. Każdy z
widoków może składać się z pewnej liczby diagramów. Widać tutaj typowo
drzewiastą strukturę. Oczywiście liczba rodzajów diagramów jest skończona –
rodzaje diagramów zostały przedstawione w tabeli 1. W tabeli 2
przedstawiono natomiast rodzaje widoków i ich powiązanie z diagramami oraz
osobami realizującymi przedsięwzięcie.

Tabela 1. Diagramy

Nazwa
diagramu
Opisuje
Przypadków użycia
(Use case diagram)
Funkcjonalność (pod)systemu widziana z zewnątrz przez użytkownika;
może też opisywać funkcjonalność klasy.
Klas
(Class diagram)
Statyczną strukturę systemu
Obiektów
(Object diagram)
Strukturę systemu w danej chwili
Behewioralne Stanów
(State diagrams)
Zachowanie obiektów danej klasy, lub całego systemu
Aktywności
(Activity diagram)
Realizację przypadków użycia
Realizację operacji
Realizację interakcji
Intereakcji
(Interaction Diagrams)
Sekwencji
(Sequence diagrams)
Interakcję między obiektami
Współpracy
(Collaboration diagrams)
jw. Ale inna notacja
Implementacyjne Komponentów Fizyczna struktura kodu
Rozmieszczenia Fizyczna struktura systemu

Tabela 2. Widoki

Rodzaj widoku Co opisuje? Diagramy Dla kogo
Przypadków użycia
(Use case view)
Funkcjonalność systemu, widzianą z zewnątrz. Ma on wpływ
na wszystkie inne widoki.
Przypadków użycia
Aktywności
Słowne opisy scenariuszy realizacji przypadków użycia
Klienci, analitycy, projektanci, programiści oraz testerzy
Logiczny
(Logical view)
Sposób, w jaki realizowana jest owa funkcjonalność; zarówno
statyczną jak i dynamiczną strukturę systemu.
Klas
Obiektów
Stanów
Sekwencji
Współpracy
(aktywności)
Projektanci, programiści, testerzy
Komponentowy
(Komponent view)
Moduły i ich powiązania. Komponentów Programiści, administratorzy repozytoriów kodu
Współbieżności Procesy, procesory, współbieżności Stanu
Sekwencji
Współpracy
(Aktywności)
Programiści, osoby odpowiedzialne za integrację systemu
Rozmieszczenia
(Deployment view)
Fizyczne rozmieszczenie systemu na urządzeniach Rozmieszczenia Programiści, testerzy, osoby odpowiedzialne za integrację
systemu, wdrożeniowcy.

Należy być oczywiście świadomym faktu, że nie w każdym
projekcie wysokie rodzaje widoków i diagramów muszą być wykorzystane. Najczęściej
wykorzystuje się diagramy:

  • Przypadków użycia (PU)
  • Klas
  • Interakcji.

Diagramy stanów są wykorzystywane w odniesieniu do klas, których
obiekty mogą znajdować się w różnych stanach. Dobrym przykładem mogą tu
być analizatory leksykalne, oprogramowanie związane z symulacją, czy
sterowaniem. Diagramy te nie znajdą raczej zastosowania w typowych systemach
bazodanowych. Diagramy aktywności mogą służyć do opisu realizacji przypadków
użycia.

Jak widać język UML pozwala na spójny opis systemu
informatycznego, rzeczywistości lub przedsięwzięcia. Może być on
zastosowany na etapie: analizy wymagań, analizy, modelowania, projektowania,
implementacji oraz testowania. Nie zapewnia on jednak żadnych mechanizmów związanych
z zarządzaniem projektem programistycznym, służących np. do przydziału
zasobów lub planowania prac; nie ma też diagramów DFD.

Diagramy i widoki przypadków użycia

Przypadki użycia opisują funkcjonalność systemu widzianą
z zewnątrz przez użytkownika; znacznie rzadziej, choć jest to możliwe, używa
się ich do opisu funkcjonalności klasy. System (podsystem, klasa) jest
traktowany jak czarna skrzynka. Użytkownikiem może być zarówno człowiek jak
i inny system. Użytkownik taki nosi w terminologii PU nazwę aktora – istotne jest, iż to właśnie on inicjuje interakcję z systemem. Odnosząc się
do projektowania systemów informatycznych trzeba zaznaczyć, że aktor nie musi
wcale korzystać bezpośrednio z systemu (siedząc przed terminalem) – może
robić to, np. za pośrednictwem pracownika. Tak więc, aktorem w systemie do
obsługi przychodni lekarskiej będzie na pewno pacjent, chociaż
najprawdopodobniej nigdy nie „dotknie” on żadnego komputera.


Rys. 2. Prosty diagram PU

Na rysunku 2 przedstawiono prosty diagram PU; jedynym aktorem
na nim jest Pacjent – może on zarejestrować się w systemie,
dlatego jest w relacji asocjacji z PU Rejestracja. Chociaż nie jest to
uwzględnione na rysunku2, to PU istnieje w obrębie systemu, który można
rysować jako prostokąt. Należy pamiętać, iż zarówno aktor jak i przypadek
użycia są klasami (a mówiąc ściśle klasyfikatorami). Zarówno pomiędzy
aktorami jak i przypadkami użycia mogą zachodzić typowe dla klas relacje – np. generalizacji.

Modelując wymagania stawiane systemowi należy pamiętać,
że diagramy PU pozwalają wyłącznie na wyspecyfikowanie wymagań
funkcjonalnych. Należy też zdawać sobie sprawę, że często uznanie, czy
„coś” jest przypadkiem użycia, czy też nie, zależy od ziarnistości
modelu. Jeżeli projektujemy system operacyjny, a właściwie jego API to wyświetlanie
pojedynczego znaku może być przypadkiem użycia tego systemu – operacja
taka nie będzie na pewno PU w systemie obsługi przychodni lekarskiej.

Istnieją dwa, określone przez stereotypy, rodzaje relacji
generalizacji zachodzące pomiędzy PU: rozszerzanie (<<extend>>)
oraz włączenie (<<include>>). Jeżeli Rejestracja włącza Wprowadzanie
danych osobowych
, tak jak pokazano to na rysunku 3, to bazowy PU, (czyli Rejestracja)
zawsze i dokładnie jeden raz użyje włączanego PU (czyli Wprowadzanie
danych osobowych)
. Należy to rozumieć w ten sposób, że przy rejestracji
pacjenta zawsze konieczne jest wprowadzenie (jego) danych osobowych. Zachowanie
zdefiniowane PU Wprowadzanie danych osobowych jest wstawiane w PU Rejestracja.


Rys. 3. Generalizacja ze stereotypem <<include>>

Scenariusz dla PU Rejestracja mógłby być następujący:

Wyświetl informację: rejestracja nowego pacjenta w systemie
include Wprowadzenie danych osobowych
Zdefiniuj lekarza prowadzącego
Stwórz pustą historię choroby

Scenariusz dla PU Wprowadzenie danych osobowych mógłby być następujący


Pobierz nazwisko
Pobierz imię
Pobierz PESEL
Pobierz adres
Pobierz NIP
Pobierz numer telefonu
Pobierz numer telefonu komórkowego

Jak widać, zachowanie zdefiniowane w PU Wprowadzanie
danych osobowych
jest wstawiane (druk wytłuszczony) w zachowanie
zdefiniowane w PU Rejestracja. Nic nie stoi na przeszkodzie, aby PU Wprowadzanie
danych osobowych
był włączany (lub wchodził w inne relacje) z innymi
przypadkami użycia. Jest to jak najbardziej sensowne, ponieważ niezależnie
czy dokonuje się rejestracji nowego pacjenta w systemie, czy zatrudnienia
nowego lekarza lub laboranta, od tego trzeba pobrać pewne podstawowe (i wspólne)
dane osobowe i tak.

Relacja rozszerzania ma zupełnie inną semantykę. Pacjent
podczas wizyty u lekarza może zostać skierowany na badanie krwi, choć oczywiście
nie każda wizyta wiąże się z takim dodatkowym badaniem. Na rysunku 4
przedstawiono właśnie taką sytuację. Podczas badania lekarskiego okaże się,
być może, że trzeba wykonać badanie krwi. To ostatnie jest, więc badaniem
dodatkowym, które zleca lekarz zależnie od potrzeb.


Rys. 4. Użycie generalizacji ze stereotypem <<extend>>

Między aktorami zachodzą zwykłe relacje np. Lekarz
specjalista
jest szczególnym przypadkiem lekarza, co pokazuje rysunek 5.


Rys. 5. Przykładowa relacja generalizacji pomiędzy aktorami

Użycie diagramów przypadków użycia, pomimo ich bardzo
prostej notacji, sprawia wielu osobom bardzo duże trudności. Osoby takie próbują
używać PU do opisu pewnych aktywności zapominając o tym, iż każdy
przypadek użycia reprezentuje skończoną funkcjonalność systemu, taką jak
przykładowo rejestracja pacjenta; w żadnym wypadku nie jest to jednak wpisanie
nazwiska pacjenta. Czasami jest to dość trudne do rozstrzygnięcia – pomocne może być zauważenie, iż interakcja z PU powoduje pewien rezultat. Jeśli
zarejestruje się pacjenta w systemie po interakcji, jego dane są w tym
systemie. Wpisanie nazwiska do formatki nie powoduje takiego rezultatu. Praktyka
pokazuje, iż specyfikacja przypadków użycia może być najtrudniejszym etapem
projektowania złożonego, wielomodułowego systemu informatycznego. Dyskusje na
temat, czy coś jest przypadkiem użycia, czy też nie, mogą być bardzo zażarte
i trwać wiele godzin.

Diagramy i widoki klas

Diagramy klas przedstawiają statyczną strukturę systemu. Z
jednej strony można powiedzieć, iż w systemie występują takie klasy, jakie
występują w opisie rzeczywistości – z drugiej, że takie, które są
potrzebne do uzyskania funkcjonalności opisanej w diagramie PU. Oba pozornie
sprzeczne podejścia mają pewien punkt styczny, w którym ilość klas jest właściwa.
Oczywiście, na poziomie projektowania, konieczne jest wprowadzenie pewnych klas
pomocniczych. Nie jest to jednak sprzeczne ze stwierdzeniem, iż te klasy z
opisu rzeczywistości powinny „przetrwać” redukcje, które są
niezbędne do realizacji PU. Z każdym przypadkiem użycia można powiązać
pewien zbiór klas niezbędny do jego realizacji.

Posiłkując się definicją z [2], można powiedzieć, że
diagram klas jest grafem składającym się z klasyfikatorów3 (m. in.
klas, typów) i ich relacji. Diagram taki może poza klasami zawierać
interfejsy (ang. Interfaces), pakiety (ang. Packages), relacje (ang.
Relationships), obiekty (ang. Objects) i powiązania (ang. Links). Warto w tym
miejscu zauważyć, iż wg takiej definicji diagram obiektów jest rodzajem
diagramu klas. Jest on jednak w praktyce rzadko używany. W powszechnie występujących
projektach, na diagramie klas występują klasy i relacje pomiędzy nimi oraz
interfejsy. W terminologii języka UML, klasa jest opisem obiektów mających
podobne właściwości. Jak widać, pojęcie klasy jest bardziej ogólne niż
np. w języku JAVA lub C++ i nie jest ściśle związane z definicją typu.
Gdyby tak nie było, to w języku UML nie można byłoby modelować i projektować
systemów implementowanych np. w języku Smalltalk. Ponadto klasa może (na
pewnym poziomie abstrakcji) reprezentować cały system. Przykład klasy
znajduje się na rysunku 6. Nazwa klasy powinna być rzeczownikiem, nie zawierać
przedrostków ani przyrostków i w miarę możliwości opisywać znaczenie
klasy. Nie powinno się stosować nazw takich jak mój samochód, xxy1
itd.


Rys. 6. Klasa Lekarz

Na rysunku 7 znajdują się dwie klasy (Lekarz i Pacjent)
będące w niepustej4 relacji asocjacji. Poniższą relację należy rozumieć w
ten sposób, że pacjenta prowadzi dokładnie jeden lekarz (w naszych realiach
tzw. lekarz rodzinny). Lekarz może prowadzić dowolną liczbę pacjentów.


Rys. 7. Dwie klasy w relacji asocjacji

Nic nie stoi na przeszkodzie, aby pomiędzy dwoma klasami
istniało jednocześnie kilka relacji, co przedstawia rysunek 8.


Rys. 8. Dwie relacje pomiędzy tymi samymi klasami

Znaczenie powyższego diagramu jest takie, jak poprzedniego z
tym, że oprócz jednego lekarza prowadzącego, pacjent może mieć dowolnie
wielu lekarzy konsultantów (w naszej rzeczywistości lekarzy specjalistów).
Pomiędzy klasami może również zachodzić relacja generalizacji – Specjalista
(odpowiadający lekarzowi specjaliście) jest szczególnym przypadkiem lekarza.
Dziedziczenie takie oznacza, że wszędzie tam, gdzie może wystąpić Lekarz,
może wystąpić również Specjalista. Ten ostatni modyfikuje w pewien
sposób znaczenie tego pierwszego.


Rys. 9. Relacja generalizacji

Następną z relacji jest relacja agregacji – jest ona
mocniejsza od relacji asocjacji. Istotne jest to, że jeden byt posiada
(„ma”) inne, lub składa się z nich, czego przykład pokazano na
rysunku 10 – Konsylium składa się pewnej liczby lekarzy. Nie byłoby
jednak błędem narysowanie tutaj zwykłej relacji asocjacji.


Rys. 10. Relacja agregacji

Zapis klas na takim poziomie jest wystarczający przy
modelowaniu – pozwala bowiem uchwycić strukturę systemu. Nie jest on
jednak wystarczający przy projektowaniu. Zazwyczaj diagram klas sporządzony na
etapie modelowania uszczegóławia się, wprowadzając atrybuty, operacje oraz
metody. Aby bardziej formalnie przedstawić5 charakterystykę klasy i choć na
chwilę odejść od tematyki medycznej, poniżej zaprezentowano nieco
uproszczony fragment metamodelu, który opisuje właściwości klasy (za [2], w
tłumaczeniu na język polski):


Rys. 11. Klasa w metamodelu

Z diagramu na rysunku 11 wynika, że klasa może mieć dowolną
liczbę atrybutów, metod i operacji. Dany atrybut (metoda, operacja) powiązany
jest dokładnie z jedną klasą. Ograniczenie to nie oznacza oczywiście, że w
kilku klasach nie mogą występować atrybuty lub metody (operacje) o takich
samych nazwach. Pomimo takiej samej nazwy są to bowiem różne atrybuty. Nie ma
po prostu atrybutu (metody, operacji) wspólnego (współdzielonego) przez dwie
lub więcej klas. Każda asocjacja ma dwa lub więcej końców, a każdy z nich
jest powiązany dokładnie z jedną klasą. Zapis taki, jak na diagramie z
rysunku 11 nie mówi nic o notacji atrybutów lub metod. Dla porządku należy
jeszcze wspomnieć, iż metoda jest implementacją operacji; odnosząc to języka
C++ lub Java – operacja jest metodą abstrakcyjną. Klasa
„wzbogacona” o atrybuty została przedstawiona na rysunku 12.


Rys. 12. Klasa z atrybutami

Wszystkie atrybuty klasy Osoba są typu string.
Atrybut PESEL jest publiczny (+ dostępny z zewnątrz klasy bez żadnych
ograniczeń), adres oraz NIP są prywatne – nie są więc
dostępne z zewnątrz klasy. Atrybut IlośćOsob6 jest prywatny ()
i statyczny ($). Oznacza to, że jest on wspólny dla wszystkich obiektów
danej klasy. Pozostałe atrybuty są chronione (#) – są one dostępne
w klasach dziedziczących z klasy Osoba. Warto zauważyć, że zbiór atrybutów
(za wyjątkiem atrybutu iloscOsob) wynika bezpośrednio z opisu
rzeczywistości systemu. Widoczność atrybutów została zróżnicowana
bardziej dla pokazania notacji, niż z wyraźnej potrzeby. Dla prostej klasy Osoba,
określenie atrybutów jest trywialne – sprawa komplikuje się bardziej, gdy
projekt „zbliża” się do implementacji i zachodzi konieczność
określenia atrybutów związanych z rozwiązaniami technicznymi. Wymaga to
sporej wyobraźni i precyzyjnego myślenia.

Gdyby specyfikacja systemu została zakończona na tym
etapie, to poza inną notacją nie odbiegałaby ona semantycznie od diagramów
ERD. Jak już wielokrotnie powiedziano, w metodach i językach obiektowych
istotne jest to, iż byty nie są bierne – mogą wchodzić ze sobą w
interakcje. Aby było to możliwe, muszą one mieć operacje lub/i metody, przy
pomocy których przejawiają swe zachowanie. Specyfikacja metod nie jest
trywialna – zbiór metod publicznych musi zapewniać współpracę pomiędzy
obiektami, konieczną do realizacji PU; od publicznych metod (i publicznych
atrybutów) zależą inne klasy. Początkowo nie specyfikuje się sygnatur
metod, a tylko ich nazwy, uszczegóławiając projekt w kolejnych iteracjach. Często
metody określa się dopiero przy opracowywaniu diagramów interakcji. Przykładową
kolejność czynności przy tworzeniu diagramów klas zebrano w tabeli 3.

Tabela 3. Kolejność czynności w opracowywaniu diagramów
klas

Nr Rodzaj czynności Wprowadzenie
1 Modelowanie Klas z opisu rzeczywistości (bez atrybutów, metod oraz
operacji)
2 Relacji pomiędzy klasami
3 Metod publicznych
4 Projektowanie Klas pomocniczych
(słowniki itd.)
5 Projektowanie bliskie implementacji Sygnatur metod publicznych
(chronionych)
oraz wprowadzenie publicznych (chronionych) atrybutów
6 Bardzo dokładne projektowanie Prywatnych atrybutów i metod oraz zmiennych lokalnych w
metodach.

Oczywiście opracowywanie diagramów klas jest czynnością
iteracyjną i jest często przeplatane opracowywaniem diagramów interakcji.
Projektując stos, łatwo można przewidzieć zbiór metod: włóż oraz zdejmij.
Projektując złożony system o kilkuwarstwowej architekturze z graficznym
interfejsem użytkownika, natrafia się z reguły na wiele problemów związanych
z określeniem zestawu metod w każdej z klas. Sytuacja, kiedy metody i atrybuty
wprowadza się w trakcie implementacji, jest ewidentnym przykładem „łatania”
sytemu, lub – mówiąc bardziej naukowo – przykładem projektowania
odkrywczego i stanowi zaprzeczenie istoty projektowania. Oczywiście „łatanie”
jest bardziej dopuszczalne w obrębie składowych prywatnych. Nie powinno ono
mieć miejsca w odniesieniu do składowych publicznych.

Projektowanie może być bardzo dokładne i obejmować określenie
nazw wszystkich atrybutów, zmiennych lokalnych w metodach itp. Można jednak
ograniczyć się przy dokładnym projektowaniu do składowych publicznych i
chronionych, ponieważ te pierwsze tworzą interfejs klasy, a drugie mogą być
używane w klasach wyprowadzonych z danej klasy (i w pakiecie, w którym jest
klasa – w języku Java), pozostawiając dospecyfikowanie składowych
prywatnych programistom. Jest to zresztą zgodne z ideą metod obiektowych – najpierw tworzy się szkielet systemu złożony ze współpracujących
ze sobą klas (a właściwie obiektów), a następnie projektuje, implementuje i
testuje „wnętrze” każdej z nich, niezależnie od innych. Przy
takim stwierdzeniu widać, iż przesadnie dokładne projektowanie składowych
prywatnych może być postrzegane jako zaprzeczenie najważniejszej własności
metod obiektowych. Trzeba dodać, że na diagramie klas nie można bezpośrednio
wyspecyfikować lokalnych zmiennych metod – można to jednak uczynić na
diagramach stanu.

Klasa z atrybutami i metodami została przedstawiona na
rysunku 13. Przykładowe metody są publiczne (+), pierwsza z nich jest
bezargumentowa i zwraca wartość typu String, druga ma jeden argument
typu String i zwraca wartość typu int. Przy użyciu tych dwóch
metod klasa ta wchodzi w interakcje z innymi klasami.


Rys. 13. Klasa z atrybutami i metodami

Wracając do metamodelu, a ściśle jego pakietu Core,
w którym wyspecyfikowano szkielet języka oraz pakietu Common Behavior należy
zauważyć, że Metoda (ang. Method) i Operacja (ang. Opertation)
nie są jedynymi przejawami zachowania (ang. Behevior). Oprócz nich z BehehiralFeature
dziedziczą jeszcze: sygnał (ang. Signal) – który oznacza asynchroniczną
komunikację pomiędzy instancjami i pośrednio wyjątek (ang. Exception). Zwykłe
wywołanie metody jest synchroniczne – wykonanie metody wywołującej jest
wstrzymane do momentu powrotu z wywołanej metody. Operacja może mieć zero lub
więcej implementacji (metod), a każda metoda jest implementacją dokładnie
jednej operacji. Zbiór operacji jest interfejsem, nie należy go jednak
automatycznie utożsamiać z interfejsem klasy rozumianym jako zbiór składowych
publicznych tej klasy. Interfejs można jednak rozumieć jako zbiór usług
oferowanych przez klasę. Przy założeniu, że klasa musi implementować owe usługi
(metody) lub być abstrakcyjna, prowadzi to do zbliżenia tych pojęć. Wyraźniejszego
oddzielenia wymaga to w językach programowania (np. w języku Java), czyli
wtedy gdy interfejs jest słowem kluczowym języka i może być używany (w
pewnych sytuacjach) w oderwaniu od obiektu klasy. Problemem, który występuje
często przy projektowaniu diagramów klas jest unikanie dziedziczenia w
projektowaniu i powiązane z nim często wprowadzanie zbyt dużej liczby klas.
Przykładem może być wprowadzenie jednej klasy reprezentującej linię ciągłą
na wydruku, a innej dla linii przerywanej. Jeśli nie ma szczególnych powodów,
aby postąpić inaczej, należy zdefiniować jedną klasę Linia i w jej
konstruktorze ustalać styl i długość. Wprowadzanie zbyt dużej liczby bytów
w projekcie może być symptomem małej dojrzałości i doświadczenia
projektanta, przy równoczesnej dość dużej jego sprawności technicznej.
Sytuacja ta występuje często, gdy projekt wykonują programiści. Aby
wprowadzić nowy byt (np. klasę) muszą istnieć ku temu wyraźne, ściśle
określone powody. Projekt składający się ze 100 klas jest dużym projektem;
jeśli liczba klas rośnie zbyt szybko, należy zastanowić się, czy nie należy
jej zredukować.

Diagramy interakcji

Diagramy te opisują zachowanie się systemu a więc opisują
jego dynamiczne właściwości i dzielą się na diagramy sekwencji i współpracy.
Diagram sekwencji może być jednoznacznie przekształcony w diagram współpracy.
Przekształcenie takie jest możliwe również w stronę przeciwną. Istotne
jest to, iż interakcje rysuje się pomiędzy obiektami, a nie pomiędzy klasami
– omawiane diagramy są zatem pewną odmianą diagramów obiektowych. Na
poziomie metamodelu istnieją różne rodzaje interakcji pomiędzy instancjami
(obiektami) – duże znaczenie praktyczne mają komunikaty (ang. Messages),
z których dziedziczą: wywołanie operacji (ang. Invoking an Operation), wysłanie
sygnału (ang. Raising a signal) oraz stworzenie lub zniszczenie instancji
(obiektu). Komunikat [1] jest jednokierunkową komunikacją pomiędzy dwoma
obiektami, z których jeden pełni rolę nadawcy a drugi odbiorcy7. Komunikaty są
wysyłane poprzez łączące obiekty powiązania (ang. link), które odpowiadają
asocjacjom pomiędzy klasami; odbywa się to w ten sposób, że pomiędzy
obiektami klas będących w relacji asocjacji może zachodzić powiązanie.
Komunikaty mogą być więc przesyłane wyłącznie pomiędzy tymi obiektami
klas, między którymi zachodzi relacja asocjacji (lub agregacji). Nie jest to
jednak warunek wystarczający. Diagramowi z rysunku 7 może odpowiadać
sytuacja, w której lekarz Kowalski8 ma trzech pacjentów: Iksińskiego, Kwiatkowskiego oraz Nowaka9 co pokazano na rysunku 14.


Rys. 14. Przykładowy diagram obiektowy dla rysunku 7

W takiej sytuacji obiekt Kowalski może przesłać
komunikaty (np. wywołać operację) do obiektów Nowak, Iksiński,
Kwiatkowski
. Nie może on przesłać natomiast komunikatu do pacjenta Zielinski.
Pomiędzy tymi obiektami nie ma powiązania. Oczywiście powiązanie istnieje w
konkretnej chwili, dlatego niemożność wysłania komunikatu występuje również
w konkretnej chwili. Jeżeli pomiędzy klasami nie zachodzi relacja asocjacji
(lub agregacji, ewentualnie dziedziczenia) powiązanie nie będzie istniało
nigdy – nie ma więc możliwości (bezpośredniego) wysłania komunikatu.
Mówiąc językiem potocznym – relacja asocjacji oznacza, iż jeden obiekt
wie (a raczej może wiedzieć) o drugim.

Na rysunku 15 przedstawiono przykładowy, bardzo prosty
diagram sekwencji, przedstawiający rejestrację pacjenta Iksiński do
lekarza Kowalski. Rejestracji dokonuje rejestratorka medyczna, która po
dokonaniu rejestracji dopisuje pacjenta do listy pacjentów na dany dzień.


Rys. 15. Przykładowy diagram sekwencji

Oczywiście, aby taka interakcja była możliwa, obiekt
Iksiński
(Pacjent) musi być powiązany z obiektem Ola (pracownik
biurowy), który z kolei jest powiązany z lekarzem Kowalski.

Analizując metamodel można dojść do wniosku, że ilość
różnych rodzajów interakcji między obiektami przekracza ilość konkretnych
mechanizmów występujących w jednym konkretnym języku programowania. Różnorodność
ta jest jednak niezbędna, aby umożliwić zapis projektu w oderwaniu od jakiegoś
określonego języka implementacji. Najczęściej spotykaną odmianą komunikatu
jest wywołanie operacji, odpowiadające wywołaniu metody w językach takich
jak C++ lub Java. Istotą takiej komunikacji jest jej synchroniczność. Trzeba
jeszcze podkreślić, iż w terminologii UML wywołuje się operację a nie
metodę.

UML a projektowanie – wprowadzenie

Powyżej przedstawiono jedynie zarys języka UML, który będzie
stopniowo rozszerzany. Podstawowe diagramy pozwalają już na zapis prostego
projektu oraz na analizę metamodelu, który powinien być wykładnią w razie
zaistnienia jakichkolwiek wątpliwości. Trzeba jednak z całą stanowczością
podkreślić, że sama definicja języka nie mówi nic na temat projektowania.
Jej znajomość jest niezbędna w takim stopniu, aby móc poprawnie zapisać
projekt. Dalsze, kończące ten odcinek, rozważania będą stanowić pewnego
rodzaju przerwę w prezentacji języka UML. Są one jednak konieczne, chociażby
po to, aby ukazać różnicę pomiędzy metodą projektowania i pewnymi założeniami
dotyczącymi projektu, a sposobem ich zapisu.

Projektowanie systemu, przy założeniu, że wymagania określone
w postaci dokumentu słownego zostały zweryfikowane rozpoczyna się od
zdefiniowania funkcjonalności systemu, – czyli widoku przypadków użycia.
Diagram taki prezentuje się użytkownikowi, aby go zweryfikować. Po
naniesieniu poprawek i modyfikacjach, (wielokrotnych) należy przejść do
znalezienia klas. Zazwyczaj wychodzi się tutaj od bytów występujących w
opisie rzeczywistości systemu. Diagram(y) klas modyfikuje się, rozpoczynając
w pewnym momencie tworzyć niejako równolegle do nich diagramy sekwencji. Cały
czas projekt (a właściwie model) nie obejmuje żadnych bytów związanych z
realizacją rozwiązania. Należy również pamiętać, iż opisany proces jest
interacyjny. Przejście do projektu wymaga wprowadzenia klas słownikowych,
pomocniczych oraz związanych z przyjętym rozwiązaniem. Jednej klasie na
etapie modelowania może odpowiadać kilka klas w projekcie. Przy takim przejściu
należy zacząć zdawać sobie sprawę ze struktury sytemu. Dobrym pomysłem
jest rozdzielenie klas interfejsowych od sterujących i klas wynikających z
opisu rzeczywistości. Powstanie wtedy trójwarstwowa architektura:

  1. Klasy interfejsowe
  2. Klasy sterujące
  3. Klasy z opisu rzeczywistości

Klasy interfejsowe są odpowiedzialne za interakcję z użytkownikiem,
klasy sterujące za działanie systemu – za ich pośrednictwem klasy
interfejsowe współpracują z klasami z opisu rzeczywistości. Klasy wymienione
w punkcie pierwszym są bardzo silnie związane nie tylko z językiem
implementacji, ale również z bibliotekami programistycznymi służącymi do
realizacji interfejsu użytkownika. Przy podejściu takim należy być
konsekwentnym nie pozwalając, aby klasy z opisu rzeczywistości wchodziły w
bezpośrednią interakcję z użytkownikiem. Podejście takie ma szereg zalet.
Wprowadza do kodu porządek, implementacją interfejsu użytkownika może zająć
się osoba (osoby), która umie to robić, – co pozwala na ujednolicenie
wyglądu tego ostatniego. Znacznie bardziej istotne jest to, że system,
zaprojektowany w ten sposób, jest przenośny. Jeżeli zajdzie potrzeba
opracowania wersji dla innego systemu operacyjnego i środowiska sprzętowego
wystarczy od nowa napisać klasy interfejsowe i zmodyfikować sterujące, bez
naruszania „wnętrza” systemu. W rozwiązaniu takim można iść
nieco dalej tworząc warstwę pośrednią pomiędzy klasami sterującymi a
interfejsowymi tak, aby móc przenieść system bez jakiejkolwiek nawet
modyfikacji klas sterujących.

Podsumowując niniejszy odcinek można powiedzieć, iż główną
zaletą języka UML jest możliwość graficznego przedstawienia różnych
aspektów modelowanego sytemu połączona z bardzo dużą elastycznością tego
języka. Sam język nie zastąpi jednak umiejętności precyzyjnego myślenia
przy projektowaniu, czy wreszcie samej umiejętności projektowania. W firmach
komercyjnych używa się zazwyczaj niewielkiego wycinka języka i to w sposób,
niestety, dość intuicyjny. Bardzo złudna bywa prostota notacji języka; wiele
osób stosuje różne konstrukcje wg własnego uznania. Autor jest zdania, iż
powinno się stosować te sposoby, które dobrze się zna. Dowolność i miejsce
na własne pomysły są dopuszczalne w metodzie samego projektowania, ale nie w
sposobie zapisu. Z drugiej strony metamodel, który jest tematem licznych prac
naukowych, powinien być analizowany w pewnym zakresie również przez
„zwykłych” projektantów i programistów używających tego języka.
Język UML można porównać do rysunku technicznego, w konstrukcji maszyn.
Najlepszy nawet kreślarz nie jest w stanie konstruować bardzo skomplikowanych
mechanizmów bez znajomości zasad projektowania. Przy takim odniesieniu widać,
że zastosowanie języka UML nie spowoduje, iż projekt zostanie wygenerowany
automatycznie. Dalsze rozważania będą więc dotyczyć projektowania oraz będą
w miarę potrzeby wprowadzać nowe konstrukcje języka UML.

c.d.n.

Literatura

  • [1] J. Rumbaugh, I. Jacobson, G. Booch The Unified Modelling Language Reference Manual Addison-Wesley 1999
  • [2] OMG Unified Modeling Language Spcyfication OMG January 1999 (UML 1.3al)

1 Założenie takie jest w pełni usprawiedliwione – istnieje język OCL, który pozwala na zapis „tekstowy” diagramów.

2 Ze względu na możliwości narzędzia.

3 Pojęcie to zostanie omówione później.

4 Autor spotkał się kiedyś z paradoksalną sytuacją, w której modelowano
relacje puste, czyli takie, które nie zachodzą.

5 Jest to możliwe dopiero teraz, dlatego iż do zrozumienia
takiego zapisu potrzebna jest znajomość notacji.

6 Wprowadzony tu nieco sztucznie.

7 Nic nie stoi oczywiście na przeszkodzie, aby nadawca i
odbiorca były w szczególności tymi samymi obiektami.

8 Zakładamy milcząco, iż identyfikatorem jest nazwisko.

9 Ewentualna zbieżność nazwisk jest przypadkowa.