Obiektowo Orientowane Projektowanie (cz. 3)

Dziś trzecia, ostatnia już część fragmentu książki Jerzego Grębosza ''Symfonia C++. Programowanie w języku C++ orientowane obiektowo' 'wydanej przez Oficynę Kallimach. Czytelników zachęcamy do przeczytania książki w całości.

Dziś trzecia, ostatnia już część fragmentu książki Jerzego Grębosza ''Symfonia C++. Programowanie w języku C++ orientowane obiektowo' 'wydanej przez Oficynę Kallimach. Czytelników zachęcamy do przeczytania książki w całości.

Etap 3b) Mieszczenie (zawieranie) w sobie obiektów innych klas

Związek klas polegający na mieszczeniu w sobie obiektu innej klasy łatwo sprawdzamy wygłaszając stwierdzenie:

Obiekt Klasy A składa się między innymi z obiektu klasy B

Jeśli zdanie zabrzmi sensownie, to obiekt klasy B jest składnikiem klasy A.

Przykładowo:

Samochód składa się między innymi z krowy

(bzdura)

Samochód składa się między innymi z kierownicy

(prawda)

Krowa składa się między innymi z dwóch oczu

(prawda)

Etap 4: Określenie wzajemnych zależności klas

Na tym etapie musimy zdecydować o wzajemnych relacjach klas - czyli o tym, która jest ważniejsza od której, która wydaje polecenia, a która polecenia te spełnia, albo które klasy są partnerami - prosząc się nawzajem o przysługi.

Określanie takich relacji dokonuje się dwojako. Po pierwsze sporządza się spis relacji łączących obiekty, a po drugie rysuje się tzw. graf współpracy obiektów.

Spis relacji

sporządza się w ten sposób, że mając przed sobą scenariusz, a także karty modelujące wypisuje się poszczególne relacje. Powinny one być zebrane w grupy. To znaczy, że najpierw wypisujemy wszystkie relacje takie, gdzie jakiś obiekt zmusza inny obiekt do wykonania jakiegoś działania, potem na przykład relację zwykłego powiadamiania się o czymś (komunikowanie się). Inaczej - kto żąda - a kto tylko realizuje żądania.

Oto przykład - dla hipotetycznego obiektu klasy: programator pralki automatycznej

Obiekt Zależność Kogo

Programator zmusza do otwarcia Zawór wodny

zmusza do zamknięcia Zawór wodny

zmusza do wirowania Silnik . dowiaduje się o bieżący poziom wody hydrostat

dowiaduje się czy drzwiczki są zamknięte czujnik drzwi

. . . . Następny obiekt . . . . . . . ..

Graf współpracy

Graf współpracy ma tę zaletę, że pokazuje jasno ścieżki komunikacji między obiektami. Graf taki sporządzany zostaje na podstawie ustaleń z dotychczasowych etapów projektowania. Rysujemy go w ten sposób, że prostokątami oznaczamy poszczególne klasy (obiekty), a strzałkami oznaczamy zależności. Jest wiele szkół rysowania takich grafów. Ja osobiście stosuję najprostszy sposób. Strzałkami oznaczam kto komu rozkazuje. Groty z obu konców strzałki oznaczają, że dwie klasy nawzajem proszą się o przysługi.

Narysowanie grafu to jeszcze nie wszystko. Teraz trzeba mu się krytycznie przyjrzeć. To bardzo ważne zobaczyć nasze obiekty narysowane w ten sposób, gdyż za jednym rzutem oka widzimy miejsca niepotrzebnego skomplikowania lub sytuacje, gdy naruszona jest enkapsulacja.

Jeśli patrząc na graf zauważysz, że jakaś klasa współpracuje z więcej niż trzema innymi klasami - oznacza to zwykle, że coś strasznie pokomplikowaliśmy. Tak, tak - i tutaj możesz wyprodukować "kod a la spaghetti", . Jeśli z Twojego grafu wynika, że wszyscy współpracują z wszystkimi - to trudno Ci będzie opanować taką hołotę. Zacznij od minimalizacji liczby współpracowników. Zwróć też uwagę czy niektóre klasy nie działają na jakieś obiekty tkwiące we wnętrzu innych klas. To byłoby pogwałceniem zasady enkapsulacji. Po to obiekt tkwi w środku innego obiektu, aby bezpośrednio do niego się nie zwracać (tylko przez jego szefa).

Innym rodzajem uproszczenia jest zminimalizowanie typów obowiązków spełnianych przez klasę (czyli tzw. kontraktów). Niech klasa zajmuje się tylko jednym zadaniem. Niech np. pionek do gry zajmuje się tylko poruszaniem się po szachownicy, a nie oblicza dodatkowo sinusa i logarytmu. Jeśli ten typ obowiązków (sin, log) musi być spełniany, to rozważmy budowę dodatkowej klasy.

Patrząc na graf współpracy możesz łatwiej zorientować się, co i jak można uprościć. Zwracam uwagę: do tego najlepiej nadaje się graf, bo tu widać wszystko jak na dłoni. Jeśli więc patrząc na niego wymyślisz jakąś modyfikację, to wróć do jednego z poprzednich etapów i popraw.

Podsystemy

Jeszcze jedną zaletą grafu jest to, że widać na nim także grupy klas, które nie mają ze sobą zupełnie nic wspólnego. Wówczas możemy spróbować zgrupować klasy w tak zwane podsystemy, czli grupy klas zajmujących się spełnianiem jednej, bardzo ogólnie pojętej roli.

Na przykład - jeśli program na grę w Chińczka wzbogacimy o grupę klas odpowiedzialnych za granie chińskiej muzyki, to ta nowa grupa klas nie ma nic wspólnego z grupą do której należą pionki, kostka, itd.

Oto inny przykład: program obsługujący eksperyment fizyczny, w którym to programie jest kilka klas realizujących rozmowę z użytkownikiem za pomocą eleganckich menu. Ta grupa klas to właśnie podsystem. Jeśli w tym samym programie jest inna grupa klas odpowiedzialnych za sterowanie dopływu ciekłego azotu do detektorów germanowych - to klasy te tworzą kolejny podsystem.

Na grafie takie grupy klas odgraniczamy czerwoną linią - co daje nam pojęcie, które klasy należą do danego podsystemu, a które do sąsiedniego. Przy wykreślaniu tej linii jako kryterium przyjmujemy zasadę: Klasa należy do danego podsystemu wtedy, gdy spełnia zadania jedynie na rzecz tego podsystemu.

Zapytasz pewnie: "- Dlaczego to takie ważne - przecież w C++ istnieje coś takiego jak klasa, ale nie istnieje nic w stylu "podsystem klas "?"

Rzeczywiście, jednak praca nad programem i projektem bardzo upraszcza się jeśli dokonaliśmy podziału na podsystemy. Najpierw zajmiemy się zaprojektowaniem - i nawet implementacją - części odpowiedzialnej za ciekły azot i nie pamiętamy o istnieniu reszty. Jeśli pracuje się w zespole, to jeden z programistów opracowuje ten podsystem, a drugi inny. Ich klasy nawzajem się nie kontaktują, więc taka rozłączna praca jest możliwa. Aby próbnie uruchomić jeden podsystem - drugi może nawet jeszcze nie być zaczęty.

Etap 5: Składanie modelu. Określanie sekwencji działań obiektów i cykli życiowych

Czas na to, by skonstruować model systemu będącego przedmiotem naszego programu.

Jeśli podzieliliśmy zagadnienie na kilka podsystemów, to teraz czas, by modelować jeden z nich. Gdy to zrobimy, to weźmiemy na warsztat inny podsystem. Ta metoda bardzo ułatwi nam pracę nad modelowaniem całości systemu.

Jak postępuje się w przypadku składania modelu?

Bardzo prosto: Bierze się do ręki scenariusz, który powstał w etapie 1. Jeśli udało Ci się go wtedy zrobić chronologicznie - to jest dużo pracy zrobionej. Jeśli nie - to czas zrobić to teraz.

To jednak nie wszystko. Teraz musimy ustalić sekwencje działań obiektów każdej z klas. To znaczy ustalamy kto inicjuje jaką akcję, co jest robione potem. Powstają wówczas takie sformułowania:

Jak tylko obiekt klasy A otrzyma sygnał, by zrobił to - wydaje on następujące polecenie obiektowi klasy B i... (np. czeka na rezultat)

Obiekty mają swoją indywidualność, więc nie zawsze musi być to takie bezmyślne. Na przykład po otrzymaniu sygnału obiekt A może dowiedzieć się o parę spraw, po czym zależnie od oceny sytuacji może wybrać jeden z wariantów postępowania. Jak w życiu. Niewolnictwo i feudalizm dawno już się skończyły.

Następnie opisujemy cykle życiowe poszczególnych obiektów.

Na przykład w grze w Chińczyka najpierw obiekt pionek jest w stanie "w drodze" potem, gdy zdarza się coś szczególnego, przechodzi w stan "w Pekinie " itd.

Rezultat tego etapu przyda się przy definiowaniu ciał poszczególnych funkcji składowych danej klasy.

Zwróć uwagę, że etap ten nie produkuje niczego nadzwyczajnego. Jest to raczej usystematyzowanie naszej wiedzy o obiektach. Praca tutaj przypomina trochę reżyserowanie spektaklu: Najpierw wchodzisz ty i robisz to...

Albo: Jeśli tylko on zwróci się do ciebie z takim żądaniem, wtedy zrób tamto.

Na tym zakończył się proces projektowania programu obiektowo orientowanego. Teraz można przystąpić do implementacji, czyli zamieniania tego na instrukcje.


TOP 200