Diagram klas w UML - potrzebna pomoc w zrozumieniu powiązań.

0

Cześć wszystkim,
Stworzyłem sobie ostatnio w Javie system do głosowania, działający poprzez sieć lokalną i teraz potrzebuję udokumentować w języku UML jego architekturę. Posiedziałem trochę w temacie i skleciłem diagram klas jednej części systemu, czyli samego serwera, który to wrzucam w załączniku do wglądu. Kwestia wygląda tak, że nie jestem w stanie określić, czy to, co udało mi się zrobić, jest poprawne. Nie mam pewności co do połączeń między poszczególnymi klasami. Czy ktoś z Was, znających się lepiej na tym języku modelowania, jest w stanie sprawdzić ten diagram i wytknąć mi błędy? Mam dylemat w których sytuacjach użyć np. agregacji zamiast kompozycji, itp.

Opis wymaganych połączeń:

(MainWindow - ApplicationManager) - okno główne tworzy obiekt ApplicationManagera i ma do niego dostęp, ale komunikacja w odwrotną stronę jest niemożliwa. Dostęp do ApplicationManagera mają też inne obiekty (widać na diagramie).

(ApplicationManager - QuestionWindow) - ApplicationManager tworzy okno QuestionWindow do dodawania pytania, a okno odsyła mu treść pytania podaną przez użytkownika po kliknięciu guzika po czym się zamyka, czyli następuje komunikacja w obu kierunkach,

(ApplicationManager - Server) - ApplicationManager tworzy obiekt Serwera, komunikacja w obu kierunkach jest możliwa (ApplicationManager ma dostęp do Servera i odwrotnie),

(Server - Discovery) - Server tworzy obiekt Discovery, komunikacja tylko w jedną stronę (Server ma dostęp do Discovery),

(Server - Connection) - Serwer tworzy kolejne obiekty Connection, ale ich nie magazynuje, komunikacja tylko w jedną stronę (Server ma dostęp do aktualnie utworzonego obiektu Connection, później obiekt ten jest nadpisywany kolejnym),

(ApplicationManager - Connection), (ApplicationManager - Question), (ApplicationManager - Voter) - ApplicationManager posiada listy, na których znajdują się obiekty wymienionych klas, komunikacja jednokierunkowa w przypadku Question (ApplicationManager ma dostęp do Question), dla Connection i Voter - dwukierunkowa,

(Connection - Voter) - obiekt klasy Connection posiada referencję do obiektu klasy Voter, komunikacja w jedną stronę (Connection ma dostęp do Votera),

(Connection - EncryptionManager) i (Connection - ResponseHandler) - obiekt klasy Connection tworzy dwa pozostałe i ma do nich dostęp (referencje), więc komunikacja w jedną stronę,

(Connection - RequestHandler) - obiekt klasy Connection tworzy obiekt RequestHandler, przy czym komunikacja jest dwustronna (Connection może się dobrać do RequestHandlera i odwrotnie),

(ApplicationManager - RequestHandler) - komunikacja dwukierunkowa, polegająca tylko na wzajemnym używaniu metod,

(RequestHandler - Voter) - RequestHandler tworzy obiekt klasy Voter, komunikacja w jedną stronę (RequestHandler ma dostęp do Votera, ale nie odwrotnie).

Inne połączenia możliwe są tylko pośrednio, przechodząc przez dodatkowe obiekty np. z RequestHandlera do Question należy przejść przez ApplicationManager (użyć jakiejś jego metody).

Informacje dodatkowe:
Główne okno MainWindow wraz z ApplicationManagerem może sobie po uruchomieniu działać bez istnienia pozostałych obiektów (liczba pytań, połączeń i głosujących może być zerowa, serwer może być nieuruchomiony, a okno dodawania pytania może jeszcze nie istnieć).

0

Dokumentację tworzysz przeważnie dla innych. Ty startujesz od diagramu klas przestawiającego implementację, czyli tak jakby od końca ;-) Tak jakbyś opisywał samochód zaczynając od budowy wtryskiwaczy. Nie każdemu taka informacja jest potrzebna i ludzie przeważanie zatrzymują się na jakimś poziomie szczegółowości i nic co jest poniżej ich nie interesuje.

Jeśli decydujesz się na UMLa to powinienenś wyjaśnić:

  1. Pojęcia, którym się posługujesz (czyli domenę, np. Question, Voter jak się mają do siebie te pojęcia i czy są jeszcze jakieś inne, tu nie jest istotne jak to zaimplementujesz, bo że będziesz technicznie potrzebował Connection czy jakiegoś Window nie jest z perspektywy postawienia problemu istotne, problem możesz rozwiązywać na różne sposoby) - model domenowy (taki mocno uproszczony diagram klas)

  2. Co system umożliwia, czyli przypadki użycia (aktorzy -> kto inicjuje komunikację z systemem)

  3. Z jakich komponentów system się składa (diagram komponentów)

  4. Jak działa obsługa (konwersacja) z systemem i jak się ma to do wewnętrznych komponentów systemu (diagram sekwencji)

  5. Jak wygląda cykl życia dla Question, Voter etc. (o ile masz jakiś złożony diagram stanów i jakie są możliwe przejścia, z jednego stanu do innego, co się wtedy dzieje).

  6. Projekt techniczny rozwiązania - diagram klas (tam ładujesz te wszystkie swoje wymyślone klasy, ConnectionPoole, Managery etc.)

Co do agregacji/kompozycji to są asocjacje, które pokazują czy elementy mają mocno powiązany cykl życia (kompozycja) czy są niezależne (agregacja).
Kompozycja - klasyczny przypadek Budynek vs Pokój (kasujemy Budynek znika i Pokój w nim się znajdujący), Książka - Rozdział, Strona - Akapit etc.
Agregacja - Rezerwacja - Pokój (Kasujemy rezerwację, Pokój nie znika)

Oczywiście przykłady wyrwane z kontekstu i można dyskutować "Pokój" (wirtualny), "Las" (krzyży) itd.

0
yarel napisał(a):

Co do agregacji/kompozycji to są asocjacje, które pokazują czy elementy mają mocno powiązany cykl życia (kompozycja) czy są niezależne (agregacja).
Kompozycja - klasyczny przypadek Budynek vs Pokój (kasujemy Budynek znika i Pokój w nim się znajdujący), Książka - Rozdział, Strona - Akapit etc.
Agregacja - Rezerwacja - Pokój (Kasujemy rezerwację, Pokój nie znika)

Właśnie mam trudności ze zrozumieniem tych relacji między klasami. Z tego co napisałeś wynika, że na moim diagramie ApplicationManager powinien się z większością elementów łączyć poprzez kompozycję, bo skoro jego nie będzie, to pozostałe obiekty takie jak: Voter, Connection, Question, Server, QuestionWindow nie mają prawa bytu. Zupełnie tak samo jest z połączeniem MainWindow z ApplicationManagerem - stworzenie okna tworzy również obiekt ApplicationManager, a zamknięcie programu (czyli tego głównego okna MainWindow) sprawia, że cała reszta znów nie ma prawa bytu. Czy dobrze to rozumiem? Kiedy w takim razie używać agregacji, asocjacji albo zwykłych zależności?

0

Hmm, nie wiem czy da się to opisać w skrócie, bo im dalej w las tym więcej drzew :)

UML to nie tylko diagramy, ale przede wszystkim model, zaś diagram to tylko wizualizacja modelu (lub jego części).

W UML masz takie pojęcie jak Klasyfikator (Classifier), które pozwala grupować instancje ze względu na cechy (Features) dotyczące struktury (Structural Features), czy zachowania (Behavioral Features). Jedną z cech dotycząca struktury jest "Property", które ma atrybut typu AggregationKind. Na chwilę stop.

Teraz, czym jest Asocjacja w UMLu? Jest uszczegółowieniem Classifier (dziedziczy z Klasyfikatora UMLowego, czyli wszystkie te pojęcia/abstrakcje, które modelujesz, np. Connection/ApplicationManager/MainWindow są klasyfikatorami z punktu widzenia UMLa i posiadają cechy klasyfikatorów, znów z punktu widzenia UML). Asocjacja jest też uszczegółowieniem Relationship (która wyraża jakiś rodzaj relacji/związku między różnymi Elementami).

Asocjacja łączy >=2 klasyfikatory i wyraża fakt, że między instancjami tych klasyfikatorów może pojawić się powiązanie tzw. link (inaczej instancja tej asocjacji). Agregacja i kompozycja to takie szczególne przypadki asocjacji łączącej 2 klasyfikatory.

No dobra, ale co to znaczy w praktyce?

Asocjacja może wyrażać fakt, że MainWindow tworzy/używa/potrzebuje/zarządza/co_sobie_wymyślisz ApplicationManagerem.

Teraz kompozycja/agregacja. Wracamy do AggregationKind, które dotyczy "strukturalnych cech klasyfikatora"... Jak masz asocjację i na jej końcu jakiś klasyfikator, to może ustawić dla tego klasyfikatora rodzaj agregacji (wspomniana cecha "Property")

Za specyfikacją
"AggregationKind:
• none - Indicates that the property has no aggregation.
• shared - Indicates that the property has a shared aggregation.
• composite - Indicates that the property is aggregated compositely, i.e., the composite object has responsibility for the existence and storage of the composed objects (parts).
"

Specyfikacja mówi tak: "Precise semantics of shared aggregation varies by application area and modeler."

Po ludzku:

  • kompozycja, to też agregacja
  • dla agregacji specyfikacja nie definiuje co to znaczy i zostawia to projektantowi. W praktyce jak masz jakiś dokument opisujący model, to krótko opisujesz notację z której korzystasz i co rozumiesz przez agregację, np. "kolekcja/kontener obiektów, gdzie cykl życia kontenera i obiektów nie jest ściśle powiązany tzn. zniszczenie/usunięcie kontenera nie powoduje zniszczenia/usunięcia obiektów"
  • dla kompozycji specyfikacja mówi, że "kontener" odpowiada za przechowywanie i tworzenie/usuwanie komponentu/części/slave/child
0

Bardzo dziękuję za cenne informacje. W przypadku opracowanego przeze mnie systemu istnieją jednak takie sytuacje, w których trudno mi określić jaką relację wstawić między dwie klasy (a właściwie obiekty tych klas). Mogę podać przykład oparty o klasy z diagramu (Server, Connection oraz ApplicationManager).

Kwestia wygląda tak, że:

  1. Server akceptuje połączenia klienckie, tworząc kolejne obiekty Connection (skoro je tworzy, to już jest wskazówka na kompozycję pomiędzy nimi, ale zaczekajmy jeszcze),
  2. Po utworzeniu obiektu Connection przez Server, obiekt ten dodawany jest na listę posiadaną przez ApplicationManager (skoro ApplicationManager je przechowuje, to znów jest wskazówka na kompozycję pomiędzy nim a obiektami Connection). Dodatkowo, to ApplicationManager również odpowiedzialny jest za ich usuwanie (a ściślej - posiada publiczne metody do czyszczenia listy połączeń),
  3. Server po dodaniu jednego obiektu Connection do listy posiadanej przez ApplicationManager zapomina o nim w chwili, gdy tworzy kolejny obiekt Connection (można powiedzieć, że Server ma dostęp tylko do ostatnio utworzonego przez siebie obiektu Connection, nie przechowuje zatem ich wszystkich, ani ich nie usuwa, więc z tego punktu widzenia kompozycja między nimi odpada).

Teraz pytanie: jakie relacje powinny znaleźć się na diagramie pomiędzy klasami Server i Connection oraz pomiędzy klasami ApplicationManager i Connection, aby model był poprawny?

Moje rozumienie podsuwa pomysł, aby między klasami Server i Connection znalazła się tylko zwykła zależność z adnotacją <<create>> (tak jak to zaznaczyłem na diagramie), ponieważ Server tylko tworzy obiekty klasy Connection. Jeśli zaś chodzi o relację między ApplicationManagerem i Connection, to doszedłem do wniosku, iż ze względu na powiązanie czasu życia Connection z ApplicationManagerem, powinienem tam wstawić kompozycję (czyli zmienić to, co jest aktualnie na diagramie). Czy ktoś jest w stanie powiedzieć mi, czy takie rozwiązanie jest poprawne?

0
MATORAX napisał(a):

Bardzo dziękuję za cenne informacje. W przypadku opracowanego przeze mnie systemu istnieją jednak takie sytuacje, w których trudno mi określić jaką relację wstawić między dwie klasy (a właściwie obiekty tych klas). Mogę podać przykład oparty o klasy z diagramu (Server, Connection oraz ApplicationManager).

Kwestia wygląda tak, że:

  1. Server akceptuje połączenia klienckie, tworząc kolejne obiekty Connection (skoro je tworzy, to już jest wskazówka na kompozycję pomiędzy nimi, ale zaczekajmy jeszcze),
  2. Po utworzeniu obiektu Connection przez Server, obiekt ten dodawany jest na listę posiadaną przez ApplicationManager (skoro ApplicationManager je przechowuje, to znów jest wskazówka na kompozycję pomiędzy nim a obiektami Connection). Dodatkowo, to ApplicationManager również odpowiedzialny jest za ich usuwanie (a ściślej - posiada publiczne metody do czyszczenia listy połączeń),

Server nie przechowuje Connection, a specyfikacja UML w przypadku kompozycji nakłada odpowiedzialność za przechowywanie "części", tworzenie/usuwanie. Połączyłbym po prostu Server z connection za pomocą asocjacji, ze stereotypem <<create>>, czyli tak jak zrobiłeś.

ApplicationManager jest kontenerem na Connection, ale ich nie tworzy, więc kompozycja odpada. Pozostaje agregacja, jako, że lista tych połączeń jest czyszczona, ale połączenie nie musi być zamknięte? Nie wiem dlaczego upierasz się na to, żeby ApplicationManager miał dostęp do połączeń? Czy umieszczenie ich blisko Servera nie jest możliwe? Wtedy miałbyś kompozycję. Dodatkowo, dlaczego Connection decyduje o tym jaki będzie Request/Response handler? Nie powinna to być odpowiedzialność ApplicationManagera? (np. tworzy te handlery i przy tworzeniu serwera mówi, że Server ma korzystać z tych handlerów przy obsłudze tego co śmiga po Connection? I jeszcze dlaczego Connection ma mieć dostęp do warstwy "biznesowej", tj. tego Votera ? Connection raczej jest typowo technicznym elementem.

  1. Server po dodaniu jednego obiektu Connection do listy posiadanej przez ApplicationManager zapomina o nim w chwili, gdy tworzy kolejny obiekt Connection (można powiedzieć, że Server ma dostęp tylko do ostatnio utworzonego przez siebie obiektu Connection, nie przechowuje zatem ich wszystkich, ani ich nie usuwa, więc z tego punktu widzenia kompozycja między nimi odpada).

Teraz pytanie: jakie relacje powinny znaleźć się na diagramie pomiędzy klasami Server i Connection oraz pomiędzy klasami ApplicationManager i Connection, aby model był poprawny?

Server -- Connection, asocjacja, <<create>>
ApplicationManager -- Connection, agregacja

Moje rozumienie podsuwa pomysł, aby między klasami Server i Connection znalazła się tylko zwykła zależność z adnotacją <<create>> (tak jak to zaznaczyłem na diagramie), ponieważ Server tylko tworzy obiekty klasy Connection. Jeśli zaś chodzi o relację między ApplicationManagerem i Connection, to doszedłem do wniosku, iż ze względu na powiązanie czasu życia Connection z ApplicationManagerem, powinienem tam wstawić kompozycję (czyli zmienić to, co jest aktualnie na diagramie). Czy ktoś jest w stanie powiedzieć mi, czy takie rozwiązanie jest poprawne?

Tu tylko możesz zapytać, czy opis jest spójny z diagramami. O poprawności rozwiązania można gdybać, o ile rozumie się problem i widzi proponowane rozwiązanie. Ty opisałeś problem bardzo lakonicznie "system do głosowania, działający poprzez sieć lokalną". Dla Ciebie jest to oczywiste, bo masz to wszystko w głowie, ale inni nie mają dostępu do tej Twojej "wizji myślowej" :-) Pozwoliłem sobie na kilka uwag/pytań, ale nie uważam, że to co zrobiłeś jest złe/niepoprawne, bo może mieć jakieś uzasadnienie, a ja po prostu nie rozumiem problemu, który rozwiązujesz.

0
yarel napisał(a):

Nie wiem dlaczego upierasz się na to, żeby ApplicationManager miał dostęp do połączeń? Czy umieszczenie ich blisko Servera nie jest możliwe? Wtedy miałbyś kompozycję.

Wszystkie elementy, do których dostęp mają inne umieściłem w ApplicationManagerze, aby móc zastosować lock ordering przy synchronizacji wątków (nie ukrywam, że do tej pory nie poznałem żadnej innej, prostej metody unikania zakleszczeń). Dzięki takiej strukturze łatwiej mi mieć to wszystko pod kontrolą - i to chyba jedyny powód.

yarel napisał(a):

Dodatkowo, dlaczego Connection decyduje o tym jaki będzie Request/Response handler? Nie powinna to być odpowiedzialność ApplicationManagera? (np. tworzy te handlery i przy tworzeniu serwera mówi, że Server ma korzystać z tych handlerów przy obsłudze tego co śmiga po Connection?

Zamysł był taki, że każdy głosujący ma po stronie serwera własne obiekty obsługujące sieciowe operacje wejścia/wyjścia, czyli RequestHandler i ResponseHandler. Z tego względu podpięte są pod Connection. Gdyby w obrębie całego programu mógł istnieć tylko jeden RequestHandler i jeden ResponseHandler, to z pewnością bym go umieścił gdzieś po stronie ApplicationManagera, albo nawet samego Servera.

yarel napisał(a):

I jeszcze dlaczego Connection ma mieć dostęp do warstwy "biznesowej", tj. tego Votera ? Connection raczej jest typowo technicznym elementem.

Voter posiada atrybut, który identyfikuje urządzenie, z którego się podłączył. Jeśli po utracie połączenia głosujący podłączy się do serwera ponownie z tego samego urządzenia, to na podstawie tego związku Votera z Connection zamykam stare połączenie (Connection) i daję głosującemu możliwość dalszego głosowania od pytania, na którym skończył. Pewnie można to zrobić inaczej, ale mam na to zbyt mało czasu.

Na początku myślałem, że takie diagramy jednoznacznie określają co się w systemie dzieje, ale teraz dopiero zrozumiałem, że wcale tak nie musi być i taki diagram stanowi tylko wstęp do obszerniejszego opisu.

0
MATORAX napisał(a):

Zamysł był taki, że każdy głosujący ma po stronie serwera własne obiekty obsługujące sieciowe operacje wejścia/wyjścia, czyli RequestHandler i ResponseHandler. Z tego względu

Najważniejsze, że potrafisz opisać jaki był zamysł i dlaczego :-)

Na początku myślałem, że takie diagramy jednoznacznie określają co się w systemie dzieje, ale teraz dopiero zrozumiałem, że wcale tak nie musi być i taki diagram stanowi tylko wstęp do obszerniejszego opisu.

Jednym diagramem nigdy tego nie pokażesz. Pokazałeś tylko jakie masz części użyte w implementacji (czyli struktura), ale nie pokazujesz w jaki sposób to wszystko działa (zachowanie).

1 użytkowników online, w tym zalogowanych: 0, gości: 1