Przejdź do głównej treści

Asocjacje na diagramach klas w języku UML

Klasy, jakie tworzymy na diagramach klas, łączymy ze sobą za pomocą asocjacji. W ten sposób umieszczamy na modelu wiele kluczowych informacji. Zobaczmy, jak tworzyć asocjacje poprawnie i elegancko.

W naszym cyklu nie przywiązywaliśmy dotąd zbyt dużej uwagi asocjacjom pomiędzy klasami. Tymczasem to właśnie asocjacje (ang. associations) zawierają kluczowe informacje o wzajemnych relacjach pomiędzy obiektami, nadając w tern sposób sens całemu modelowi.

Podstawowe informacje o asocjacji

Modelując asocjację między klasami, możemy je opatrzyć wieloma dodatkowymi informacjami. Najczęściej asocjację opisuje się podając:

  • nazwę asocjacji,
  • nazwy ról, jakie pełnią powiązane obiekty,
  • liczebności powiązanych obiektów.

Na rysunku 1. jest pokazana asocjacja zawierająca wszystkie wymienione informacje. Asocjacja ta nosi nazwę posiada. Trójkątna strzałka przy nazwie posiada oznacza kierunek czytania tej nazwy (osoba posiada pojazd).

Obiekt klasy Pojazd uczestniczy w asocjacji w roli własność, zaś obiekt klasy Osoba -- w roli posiadacz. Każdy pojazd może być własnością od jednej do wielu osób, zaś każda osoba może posiadać od zera do wielu samochodów.

Asocjacja między klasami

Rys. 1. Asocjacja między klasami

Żadna z tych informacji nie jest wymagana. Asocjacja bez jakiegokolwiek opisu będzie więc najzupełniej poprawna. W praktyce często pomija się nazwę asocjacji oraz nazwy ról, ponieważ zwykle są one oczywiste i wynikają z kontekstu. Jeśli pominiemy liczebności, wówczas przyjmą one domyślne wartości 1. Dobrą praktyką jest jednak traktowanie liczebności tak, jakby były wymagane, i podawanie ich nawet wówczas, gdy są równe 1. Sygnalizujemy w ten sposób (sobie i innym użytkownikom diagramu), że przeanalizowaliśmy daną asocjację i świadomie określiliśmy w niej liczebność jako 1. Brak liczebności oznacza wówczas, że dana asocjacja nie została jeszcze przeanalizowana i liczebności obiektów uczestniczących w tej asocjacji dopiero zostaną określone.

Ciekawe przykłady asocjacji

Asocjacje możemy modelować pomiędzy dowolnymi klasami. Nic nie stoi więc na przeszkodzie, aby pewna klasa była połączona asocjacją z samą sobą. Oznacza to, że połączone mogą być dwa (zwykle różne) obiekty tej klasy. W ten sposób najczęściej modeluje się wszelkiego rodzaju hierarchiczne, drzewiaste struktury, dla których nie można z góry określić maksymalnego poziomu zagnieżdżenia, np. strukturę folderów czy hierarchię komórek organizacyjnych w firmie. Jeden koniec asocjacji oznacza wówczas obiekt nadrzędny, zaś drugi - obiekty podrzędne. Przykład takiej asocjacji,. modelującej drzewo folderów w systemie plików, jest przedstawiony na rysunku 2.

Asocjacja klasy z samą sobą

Rys. 2. Asocjacja klasy z samą sobą

Najzupełniej poprawne jest także łączenie dwóch klas przy pomocy więcej niż jednej asocjacji. W przykładzie z rysunku 3. każda rezerwacja hotelowa musi mieć określonego dokładnie jednego klienta głównego oraz dowolną liczbę osób towarzyszących. Z drugiej strony, jeden klient może być dla kilku rezerwacji klientem głównym, a jednocześnie w kontekście innych rezerwacji pełnić rolę osoby towarzyszącej. Jest to zresztą jeden ze sposobów modelowania ról pełnionych przez obiekt (w tym wypadku przez klienta), o których pisaliśmy w artykule "Modelowanie ról na diagramach klas w języku UML".

Klasy połączone dwiema asocjacjami

Rys. 3. Klasy połączone dwiema asocjacjami

Kierunek nawigacji

Asocjacjom na diagramach klas można - oprócz wymienionych na wstępie podstawowych informacji - przypisać także kilka bardziej zaawansowanych, rzadko używanych, ale niekiedy przydatnych własności, precyzujących ich zachowanie. Można na przykład określić kierunki nawigacji po asocjacji (ang. navigability). Asocjacje na rysunkach 1-3 mają nieokreślone kierunki nawigacji, co oznacza, że nie wskazujemy typowego kierunku poruszania się po asocjacji. Na rysunku 4. jest przedstawiony przykład asocjacji, w której kierunki nawigacji są określone. Strzałka po stronie klasy OddziałNFZ oznacza, że po asocjacji można nawigować od klasy Pacjent do klasy OddziałNFZ. Innymi słowy, mając obiekt klasy Pacjent, można łatwo określić, w których oddziałach NFZ jest on ubezpieczony. Natomiast krzyżyk po stronie klasy Pacjent wskazuje, że od klasy OddziałNFZ do klasy Pacjent po asocjacji nie można nawigować. Oznacza to, że odnalezienie pacjentów ubezpieczonych w danym oddziale NFZ jest niemożliwe lub nieefektywne.

Asocjacja z określonymi kierunkami nawigacji

Rys. 4. Asocjacja z określonymi kierunkami nawigacji

UML dopuszcza wszelkie kombinacje tych oznaczeń. Można więc tworzyć asocjacje, w których nawigacja jest dopuszczalna w obu kierunkach. Asocjacje, w których nawigacja w żadnym kierunku nie jest dopuszczalna, także są formalnie poprawne, choć ich sens praktyczny jest raczej niewielki.

Czy warto stosować oznaczenia kierunku nawigacji na diagramach klas? W większości przypadków są one raczej zbędne i niepotrzebnie zaciemniają diagram. W praktyce warto używać tych oznaczeń tylko dla asocjacji jednokierunkowych. Są one wtedy niejako wskazówkami dla programistów, podpowiadającymi, jaki jest typowy kierunek korzystania z asocjacji. Wskazówki takie pozwalają programistom zoptymalizować model danych w taki sposób, aby korzystanie z asocjacji w określonym kierunku było efektywne.

Zbiory, wielozbiory i porządek

Zgodnie z modelem z rysunku 4., pacjent może być ubezpieczony w wielu oddziałach NFZ. Danego pacjenta z danym oddziałem NFZ może jednak łączyć co najwyżej jeden egzemplarz asocjacji. Inaczej mówiąc, dla konkretnego obiektu klasy Pacjent, powiązane z nim obiekty klasy OddziałNFZ  tworzą zbiór (w sensie teoriomnogościowym), a więc nie mogą się powtarzać. Zatem, w danym oddziale NFZ pacjent nie może być ubezpieczony więcej niż raz. Jest to dość naturalne ograniczenie, sprawdzające się w większości asocjacji pomiędzy klasami. Jest to opcja domyślna, więc często używamy jej nieświadomi tego, że mamy do dyspozycji także inne możliwości.

Załóżmy, że uznajemy za dopuszczalną sytuację, że pacjent jest ubezpieczony kilka razy w tym samym oddziale NFZ (bo np. opłaca składki z kilku tytułów ubezpieczenia). Aby to uwzględnić na modelu, wystarczy oznaczyć naszą asocjację po stronie klasy OddziałNFZ etykietą {bag} (zob. rysunek 5). Będzie to oznaczało, że obiekty klasy OddziałNFZ powiązane z pewnym obiektem klasy Pacjent tworzą tzw. wielozbiór (ang. bag), a więc mogą się powtarzać. Inaczej mówiąc, pomiędzy pewnym obiektem klasy Pacjent a pewnym obiektem klasy OddziałNFZ może wystąpić kilka egzemplarzy asocjacji. Opcja ta jest używana rzadko, choć przyda nam się bardzo w kolejnym odcinku Akademii UML, w którym szczegółowo omówimy klasy asocjacyjne.

Asocjacja z wielozbiorem na jednym z końców

Rys. 5. Asocjacja z wielozbiorem na jednym z końców

Domyślnie obiekty powiązane z danym obiektem nie są uporządkowane (stanowią - jak powiedzieliśmy - zbiór). Załóżmy jednak, że każdy pacjent ubezpieczony w więcej niż jednym oddziale NFZ musi określić preferowaną kolejność korzystania ze swoich ubezpieczeń. Oznacza to, że oddziały NFZ przypisane do pacjenta powinny być uporządkowane. Oznaczamy to napisem {ordered} po stronie klasy OddziałNFZ. Oczywiście tak określona kolejność oddziałów NFZ może być dla każdego pacjenta inna.

Wreszcie jeśli chcemy, aby obiekty powiązane były uporządkowane i jednocześnie mogły się powtarzać, musimy oznaczyć koniec asocjacji napisem {sequence}. Jest to połączenie opcji {bag} i {ordered}. W naszym przykładzie użycie napisu {sequence} oznaczałoby, że każdy pacjent może być ubezpieczony kilka razy w jednym oddziale i dodatkowo musi określić kolejność korzystania ze swoich ubezpieczeń.

Artykuł został opublikowany w Software Developer's Journal nr 10/2009.