wtorek, 19 marca 2013

Diagram klas


Diagram klas jest jednym z najczęściej stosowanych diagramów UML. Opisuje aspekty statyczne systemu. Przedstawia deklaratywne elementy dziedziny problemowej oraz związki między nimi. Elementy dziedziny problemowej modelowane są za pomocą klas i interfejsów, a kooperacja między nimi za pomocą związków. W ten sposób powstaje model struktury systemu, będący podstawą dla jego konstrukcji. Kompletny model struktury złożonego systemu może składać się z wielu diagramów klas. Grupowanie klas w ramach jednego diagramu jest zależne od decyzji analityka lub projektanta. Jeden diagram klas może np. zawierać elementy systemu biorące udział w realizacji danego przypadku użycia.

Ze względu na pożądany poziom szczegółowości modelu istnieją dwa poziomy tworzenia diagramu klas:
  • poziom konceptualny i
  • poziom implementacyjny.
Poziom konceptualny zawiera podstawowe elementy z dziedziny problemowej. Stosuje łatwo zrozumiałe nazewnictwo klas, atrybutów i operacji. Diagram implementacyjny jest bardziej skomplikowany i zawiera elementy niezbędne dla prawidłowej specyfikacji modelu, a później implementacji. Zawiera typu danych, zobowiązania klas, statyczność, widoczność, klasy asocjacyjne, zależności, itp. Klasy na poziomie implementacyjnym można opcjonalnie oznaczyć stereotypem <<implementation class>>.

Klasa


Klasa jest uogólnieniem zbioru obiektów, które mają takie same atrybuty, operacje, związki i znaczenie. Innymi słowy, klasa stanowi wzorzec, na podstawie którego tworzone są obiekty. Każda klasa zawiera zestaw informacji istotnych z punktu widzenia kontekstu systemu. 

Z klasą związane są przede wszystkim takie pojęcia jak atrybuty i operacje. Atrybut określa właściwość obiektu przyjmującą wartość danego typu. Zestaw atrybutów stanowi charakterystykę statyczną obiektów wywodzących się z danej klasy. Operacja jest funkcją realizowaną przez obiekt. Zestaw operacji definiuje zachowanie obiektów w systemie. Operacja ma sygnaturę, która określa jej dopuszczalne parametry, sposób wywołania i zwracania wyników działania. Operacje klasy działają na pojedynczych obiektach lub zbiorach obiektów. W odróżnieniu do atrybutów, zwykle tworzona jest jedna instancja operacji klasy, przechowywana w klasie i będąca wspólna dla wszystkich obiektów tej klasy. W szczególnych przypadkach, kiedy dany atrybut lub operacja może istnieć niezależnie od obiektu, często lepiej jest zamodelować nową klasę (z własnym zestawem atrybutów i operacji), przedstawiającą kontekst rozpatrywanego atrybutu lub operacji, np. klasa Adres lub SterowanieKomunikacją.

Domyślnie graficzny opis klasy jest przedstawiony jako prostokąt podzielony na trzy sekcje:
  • nazwa klasy (reprezentująca zbiór obiektów),
  • zestaw atrybutów (opisujący cechy statyczne obiektów wywodzących się z klasy),
  • zestaw operacji (opisujący zachowanie obiektów wywodzących się z klasy).
W zależności od tego jaki etap analizy/projektowania systemu jest realizowany oraz jaki zakres „widoczności” diagram klas powinien prezentować, prostokąt reprezentujący klasę na diagramie może zawierać:
  • samą nazwę klasy (interesuje nas tylko wyszczególnienie elementów modelowanej dziedziny problemowej);
  • nazwę klasy oraz sekcję zestawu atrybutów (oprócz wyszczególnienia elementów dziedziny problemowej interesują nas także ich cechy statyczne);
  • nazwę klasy oraz zestaw operacji (oprócz wyszczególnienia elementów dziedziny problemowej interesują nas również ich zachowania);
  • nazwę klasy oraz zestaw atrybutów i zestaw operacji (interesuje nas zarówno wyszczególnienie elementów dziedziny problemowej jak i ich cechy statyczne i zachowania).
Poza tym, sama nazwa klasy umieszczona w prostokącie bez podziału na sekcje może oznaczać, że atrybuty i operacje zostały wyspecyfikowane, ale nie są w sposób jawny umieszczone na diagramie (nie są istotne z punktu widzenia przeznaczenia takiego diagramu klas). Klasa może być również przedstawiona jako prostokąt z trzeba sekcjami, z których wyspecyfikowana jest jedynie nazwa klasy. W przypadku, gdy liczba atrybutów lub operacji jest znacząco duża, ich wyliczenie w odpowiednich sekcjach można przerwać wielokropkiem. Oznacza to, że w klasie znajdują się jeszcze inne atrybuty lub operacje oprócz tych wymienionych w odpowiednich sekcjach. 


Notacja dopuszcza także stosowanie większej liczby sekcji w klasie. Dodatkowe sekcje mogą być związane z np. zobowiązaniami lub wyjątkami. Stosowanie większej liczby sekcji jest uzależnione przede wszystkim od poziomu szczegółowości jaki diagram ma przedstawiać. Należy jednak pamiętać, że może to negatywnie wpływać na przejrzystość i czytelność modelu.

Jak już wspomniano, specyfikacja klasy może być wzbogacona również o zobowiązania. Jest to technika opcjonalna i stosowana raczej pomocniczo w celu dostarczenia wysokopoziomowego opisu zachowania klas. Na tej podstawie nie jest wymagane koncentrowanie się na poszczególnych atrybutach i operacjach klasy – odbiorca modelu na podstawie zobowiązań może określić przeznaczenie danej klasy. Zobowiązania są pewnego rodzaju kontraktem klasy, jej odpowiedzialnością. Definiuje się je przeważnie na podstawie wyników modelowania biznesowego lub na poziomie analizy wymagań. Zobowiązania najczęściej wykorzystuje się w celu wypracowania koncepcji realizacji scenariuszy przypadków użycia. Zobowiązania są umieszczane w klasie jako dodatkowa sekcja. Klasa, która składa się tylko z sekcji zobowiązań i listą współpracujących klas, nazywana jest kartą CRC (Class-Responsibility-Collaboration card).

Jedną z cech opisujących atrybuty i operacje klasy jest ich widoczność. Widoczność określa dostęp do atrybutów i operacji wewnątrz jednej klasy przez obiekty innych klas. Wyróżnione są cztery poziomy widoczności:
  • publiczny (+) – dostęp do atrybutu lub operacji mają obiekty wszystkich klas;
  • prywatny (-) – dostęp do atrybutu lub operacji mają obiekty danej klasy;
  • chroniony (#) – dostęp do atrybutu lub operacji mają obiekty klas dziedziczących z danej klasy;
  • pakietowy (~) – dostęp do atrybutu lub operacji mają obiekty klas pochodzących z tego samego pakietu.
Dobrym zwyczajem jest nadawanie domyślnie atrybutom widoczności prywatnej. Dostęp do tych atrybutów jest wtedy regulowany specjalnymi publicznymi operacjami. Zmniejsza się wtedy ryzyko utraty kontroli nad niepowołanym dostępem do atrybutów.

Atrybuty i operacje klasy mogą być indywidualne dla każdego obiektu danej klasy lub też stałe (takie same, niezmienne) we wszystkich obiektach klasy. Te pierwsze są nazywane atrybutami lub operacjami egzemplarzowymi. Drugi przypadek oznacza atrybuty lub operacje statyczne. Statyczność oznacza, że np. zmiana wartości atrybutu w jednym obiekcie jest automatycznie wprowadzana w innych obiektach tej samej klasy. 

Nazwy klas, atrybutów i operacji są różne w przypadku diagramów klas na poziomie konceptualnym i implementacyjnym. Diagram klas na poziomie konceptualnym powinien zawierać nazewnictwo proste i jasne w zrozumieniu. Zarówno klasy jak i jej atrybuty oraz operacje są definiowane za pomocą wyrażeń rzeczownikowych, np.:
  • klasa: Zamówienie Klienta;
  • atrybut: Numer Zamówienia;
  • operacja: Dodaj Produkt Do Zamówienia.
W przypadku poziomu implementacyjnego nazwy te są ciągami znaków bez spacji. Poszczególne wyrazy są oddzielane wielką literą kolejnego wyrazu. Nazwy operacji i atrybutów zaczynają się małą literą, nazwy klas wielką. Nazwy operacji zawierają dodatkowo nawiasy, w których podawane mogą być ich parametry oddzielone przecinkami. I tak mamy np.:
  • klasa: ZamówienieKlienta
  • atrybut: numerZamówienia;
  • operacja: dodajProduktDoZamówienia().

Specyfikując atrybuty i operacje możemy również podać ich typy. W przypadku atrybutów są to typy danych jakie atrybuty te przechowują. W przypadku operacji oznacza to typ danych jaki operacja zwraca po zakończeniu swojego działania. Typy atrybutów i operacji umieszczane są po samej nazwie i poprzedzane dwukropkiem, np. nrZamówienia : Integer. Parametry operacji także są opisywane typami danych. Standardowymi typami danych są typy stosowane przez większość popularnych języków programowania. Są to np. Boolean, Integer, Double, Long, Decimal, String, Char, itp. Poza tym atrybutem może być obiekt innej klasy. Wtedy typem danych jest klasa, której instancją jest przechowywany obiekt.

Opcjonalnie w przypadku atrybutów mogą być stosowane liczebności i ograniczenia. Liczebność atrybutu jest podawana w nawiasach kwadratowych po typie danych (np. suma : Integer [0..*]). Ograniczenia mogą opisywać domyślną wartość atrybutu (np. skladka : Double [1..5] = 100) lub jego właściwość ze słowem kluczowym w nawiasie klamrowym. Poniżej kilka przykładów właściwości:
  • {ordered} – uporządkowany;
  • {unordered} – nieuporządkowany;
  • {unique} – nie powtarza się;
  • {readOnly} – tylko do odczytu.
Właściwości mogą również być umieszczane przy liczebności dotyczącej zależności, precyzując ich znaczenie. Wtedy, przy liczebności na jednym końcu zależności dodaje się również w nawiasie klamrowym odpowiednie słowa kluczowe, tak jak np.:
  • *{list} – dowolna liczba kolejnych, mogących się powtarzać elementów;
  • 1..*{ordered} – jeden lub więcej kolejnych unikatowych elementów;
  • 1..6{bag} – losowe powtarzające się elementy w zakresie od 1 do 6. 
Istnieje także takie pojęcie jak atrybut pochodny (derrived attribute). Atrybut pochodny poprzedzony jest znakiem „/”, a jego wartość jest uzyskiwana z formuły obliczeniowej korzystającej z innych atrybutów. Np. /wartośćPozycjiZamowienia jest iloczynem liczby produktów jednego rodzaju i ceny za ten produkt.

Klasy, które nie mogą mieć swoich instancji nazywane są klasami abstrakcyjnymi. Klasa abstrakcyjna pełni rolę uogólnienia (generalizacji) klas będących na niższych poziomach w hierarchii dziedziczenia. Celem stosowania klasy abstrakcyjnej jest stworzenie zestawu atrybutów i operacji służących tylko do dziedziczenia. Klasa abstrakcyjna różni się od interfejsu tym, że może posiadać implementację swoich operacji, a interfejs nie. Nazwy klas abstrakcyjnych pisane są zazwyczaj kursywą lub stosowany jest przy nazwie klasy stereotyp <<abstract>>.


Interfejs


Interfejs jest zestawem operacji stanowiących usługi oferowane przez klasę lub komponent. Zawiera deklaracje operacji bez specyfikacji implementacji. Ma to na celu zidentyfikowanie wspólnych zachowań różnych klas, które są realizowane w różny sposób. Interfejs oznacza się przez użycie stereotypu <<interface>>. Interfejsy nie mogą posiadać instancji. Możliwe jest tylko jego implementowanie, czyli nadpisywanie operacji interfejsu przez inne klasy. Jeden interfejs może być implementowany przez wiele klas. Tym samym jedna klasa może implementować wiele interfejsów. Notacyjnie jest to rozwiązanie podobnie jak w przypadku dziedziczenia, tylko że w tym przypadku linia strzałki skierowanej od klasy implementującej do interfejsu jest przerywana. UML 2.0 wprowadza dodatkowo dwa rodzaje interfejsów zwanych łącznikami. Pierwszy z nich jest interfejsem dostarczającym (provided interface) i wskazuje, że może dostarczyć interfejs innym klasom. Drugi jest interfejsem wymaganym (required) i oznacza interfejs wymagany do funkcjonowania obiektów klasy. W obu tych przypadkach używana jest notacja gniazd (ball-socket notation). Notacja ta ukrywa szczegółową budowę interfejsu. Podkreśla natomiast informację na temat udostępnianych i wymaganych przez daną klasę interfejsów. 




Związki


Klasy na diagramie klas połączone są związkami. Powszechnie stosowane są cztery rodzaje związków:
  • asocjacje,
  • generalizacje,
  • zależności,
  • realizacje.

Asocjacja opisuje powiązania między obiektami klas. Wyróżnia się asocjacje binarce i n-arne. Najczęściej stosuje się asocjacje binarne, które łączą bezpośrednio dwie klasy. Asocjacje n-arne łaczą więcej niż dwie klasy. Każda asocjacja może być dokładniej opisana przez zestaw cech, tj. nazwę, role powiązanych klas, nawigację, liczebność, agregację. Nazwa asocjacji odzwierciedla istotę związku. Przy nazwie asocjacji można opcjonalnie dodać kierunek jej odczytu. Nazwa asocjacji może być także scharakteryzowana poprzez role klas uczestniczących w asocjacji.



W związku binarnym, rola jest powinnością pełnioną przez jedną klasę wobec drugiej klasy. W związku n-arnym role można przypisać każdej z powiązanych klas. Rola spełniana przez daną klasę jest umieszczania bezpośrednio przy tej klasie. 

Każda asocjacja domyślnie jest dwukierunkowa. To znaczy, że komunikacja między obiektami połączonych klas jest dwustronna. W przypadkach, gdy istnieje potrzeba wskazania kierunku asocjacji, stosuje się nawigację jednokierunkową. Zastosowanie asocjacji dwukierunkowej nie wyklucza komunikowania się klas w obu kierunkach – może to być zrealizowane przez asocjacje z innymi klasami.


UML 2.0 dopuszcza stosowanie wyłączenia nawigacji (navigation exclusion). Chodzi tutaj o jawne zablokowanie przepływu komunikacji w określoną stronę. Przedstawia się to za pomocą krzyżyka po stronie końca asocjacji, w którego kierunku domyślnie przebiegałaby nawigacja. Dopuszczalna jest także (chociaż rzadko stosowana) asocjacja ze strzałkami w obu kierunkach. Oznacza ona, że komunikacja na pewno przebiega w obie strony. Ten sposób prezentowania asocjacji jest tożsamy ze standardową formą asocjacji dwukierunkowej (bez strzałek na obu końcach).



Dopuszczalna liczba obiektów biorących udział w związku jest określona przez liczebność. Liczebność określa maksymalną liczbę obiektów jednej klasy, które mogą być w związku z jednym obiektem drugiej klasy. Liczebność lokuje się przy końcach asocjacji. Rozważmy asocjację pomiędzy klasami A i B. Liczebność x umieszczona przy końcu asocjacji bliżej klasy B oznacza, że na jeden obiekt klasy A może przypadać do x (lub zakres wyrażony przez x) obiektów klasy B. Poniżej możliwe oznaczenia liczebności asocjacji:
  • 1 – dokładnie jeden;
  • 1..* - od jednego do wielu;
  • 0..1 – od zera do jednego;
  • * - wiele (od zera do nieskończoności);
  • 0..* - jak wyżej;
  • n – dokładnie n (n>1), np. 21;
  • 1..n – od jednego do n (n>1), np. 1..8;
  • 0..n – od zera do n (n>0), np. 0..5;
  • n..m – od n do m (m>n>1), np. 4..13;
  • n..* - od n do nieskończoności (n>1), np. 3..*;
  • n,m,o..p,q – liczebność złożona, np. 1,4,7..9,12
Liczebność jest wyrażana wartościami ze zbioru liczb naturalnych (nie mogą być to np. liczby ujemne lub ułamki). 

Związek między klasami może być dokładniej opisany za pomocą klasy asocjacyjnej. Jest to klasa, która dokumentuje związek między klasami, wskazuje na powiązane ze sobą obiekty i charakteryzuje ten związek poprzez swoje atrybuty i operacje. Klasa asocjacyjna ma dostęp do obiektów uczestniczących w relacji. Nie przechowuje informacji związanych z klasami uczestniczącymi w asocjacji. Każda asocjacja może mieć przypisaną maksymalnie jedną instancję klasy asocjacyjnej. Tak więc, klasa asocjacyjna jest pewnego rodzaju interpretacją asocjacji między klasami. Stosowanie tego rodzaju klas znacząco ułatwia zapanowanie nad asocjacjami n-arnymi. Graficznie element ten jest prezentowany jako klasa połączona linią przerywaną z asocjacją.

Kiedy powiązane ze sobą klasy mogą pełnić wobec siebie kilka różnych ról, mamy do czynienia z asocjacją wielokrotną. Liczba asocjacji między klasami zależy wtedy od liczby ról. Tak więc, dwie klasy mogą być połączone ze sobą wieloma asocjacjami, które charakteryzują różne sposoby interpretacji relacji między nimi.


W przypadku, gdy obiekty tej samej klasy pełnią wobec siebie jakieś role, stosuje się asocjację zwrotną. Asocjacja zwrotna wiążę klasę z samą sobą.


Kolejną cechą asocjacji jest kwalifikacja, która umożliwia wyszukiwanie obiektów związanych z daną asocjacją. Kwalifikacji dokonuje się za pomocą kwalifikatora, czyli atrybutu lub listy atrybutów, których wartości porządkują zbiór obiektów tej klasy występujących w danej asocjacji. Innymi słowy, kwalifikacja pozwala wskazać, który atrybut jednej z klas służy do zapewnienia unikatowego związku. Atrybut ten jest kwalifikatorem tego związku.





Szczególnym rodzajem asocjacji jest agregacja. Agregacja odzwierciedla związek typu „całość – część”. Istnieją dwa rodzaje agregacji:
  • agregację częściową (nazywaną po prostu agregacją) i
  • agregację całkowitą – kompozycję.
Agregacja całkowita jest oznaczana wypełnionym rombem, agregacja częściowa – pustym. W agregacji uczestniczą dwa elementy – agregat, czyli obiekt stanowiący „całość” i obiekt składowy, będący „częścią” całości. Obiekt klasy reprezentującej „całość” posiada jako swoje komponenty obiekty „części”. Wartościami atrybutów obiektu zagregowanego mogą być obiekty będące w relacji jako „części”.

Przy agregacji całkowitej obiekty częściowe nie mogą funkcjonować samodzielnie i niezależnie. Usunięcie agregatu powoduje automatyczne usunięcie jego części. Ponadto, obiekty składowe mogą być kreowane tylko po utworzeniu elementu agregującego. W agregacji częściowej usunięcie agregatu nie skutkuje usunięciem jego części. W tym przypadku obiekty współdzielone mogą funkcjonować samodzielnie, niezależnie od agregatu.


Kolejnym związkiem stosowanym na diagramie klas jest związek generalizacji. Generalizacja łączy element ogólny – klasę nadrzędną – z elementem bardziej szczegółowym – klasą podrzędną. Klasa podrzędna jest zgodna z klasą nadrzędną, tzn. posiada te same atrybuty i operacje. Mówi się wtedy o dziedziczeniu właściwości strukturalnych i behawioralnych. Ponadto, klasa podrzędna może nadpisywać te właściwości, a także definiować nowe. Obiekt wywodzący się z klasy podrzędnej może być użyty wszędzie tam, gdzie obiekt klasy nadrzędnej. Obiekt klasy podrzędnej zawsze może zastąpić obiekt klasy nadrzędnej. Nie działa to jednak w drugą stronę. W przypadku, gdy klasa ma tylko jedną klasę nadrzędną mówi się o dziedziczeniu pojedynczym. W przypadku wielu klas nadrzędnych jest to dziedziczenie wielokrotne. Związek generalizacji modeluje się za pomocą strzałki zakończonej zamkniętym, pustym grotem o kierunku od klasy podrzędnej do klasy nadrzędnej. 


Zależność definiuje związek dwóch elementów, w którym zmiana zachodząca w jednym z nich (niezależnym – źródłowym) pociąga za sobą zmianę w drugim (zależnym – docelowym). Typ związku zależności może być określony stereotypem, takim jak np.:
  • <<call>> - źródło wywołuje cel;
  • <<create>> - źródło tworzy obiekt docelowy;
  • <<deriver>> - źródło można wyznaczyć na podstawie celu;
  • <<import>> - zawartość publiczna celu zostaje dołączona do obszaru nazw źródła;
  • <<instance>> - źródło tworzy egzemplarz klasy;
  • <<permit>> - cel pozwala źródłu na dostęp do swoich cech prywatnych;
  • <<realize>> - źródło jest implementacją specyfikacji lub interfejsem definiowanym przez cel;
  • <<refine>> - źródło jest na doskonalszym poziomie abstrakcji niż cel;
  • <<substitute>> - źródło jest substytutem celu;
  • <<trace>> - cel jest historycznie przodkiem źródła;
  • <<use>> - źródło wymaga celu do swojego funkcjonowania.
Związek zależności jest modelowany za pomocą strzałki z linią przerywaną zakończoną otwartym grotem z kierunkiem od klasy zależnej od klasy niezależnej.


Klasy na diagramie klas mogą brać również udział w związkach realizacji – jedna strona związku wskazuje klasę określającą kontrakt, a druga klasę zapewniającą wywiązanie się z niego. Realizacja często wskazuje na związek pomiędzy klasą a interfejsem – interfejs zapewnia realizację usługi klasy. Na diagramie realizacja jest oznaczona jako strzałka z linią przerywaną zakończoną zamkniętym, pustym grotem, skierowana w kierunku od „kontraktu” do jego realizacji. Przy związku dodawany jest także stereotyp <<realize>>. W przypadku notacji gniazdowej (socket-ball) „kontrakt” jest powiązany linią ciągłą z interfejsem dostarczającym.





Brak komentarzy:

Prześlij komentarz