R11-06.DOC

(661 KB) Pobierz
Szablon dla tlumaczy

Rozdział 11.

Analiza i projektowanie zorientowane obiektowo

Gdy skoncentrujesz się wyłącznie na składni języka C++, łatwo zapomnisz, dlaczego techniki te są używane do tworzenia programów.

Z tego rozdziału dowiesz się, jak:

 

- używać analizy zorientowanej obiektowo w celu zrozumienia problemów, które próbujesz rozwiązać,

- używać modelowania zorientowanego obiektowo do tworzenia stabilnych, pewnych i możliwych do rozbudowania rozwiązań,

- używać zunifikowanego języka modelowania (UML, Unified Modeling Language) do dokumentowania analizy i projektu.

 

Budowanie modeli

Jeśli chcemy ogarnąć złożony problem, musimy stworzyć „model świata”. Zadaniem tego modelu jest symboliczne przedstawienie świata rzeczywistego. Taki abstrakcyjny model powinien być prostszy niż świat rzeczywisty, ale powinien poprawnie go odzwierciedlać, tak, aby na podstawie modelu można było przewidzieć zachowanie przedmiotów istniejących w realnym świecie.

Klasycznym modelem świata jest dziecięcy globus. Model ten nie jest tylko rzeczą; choć nigdy nie mylimy go z Ziemią, odwzorowuje on Ziemię na tyle dobrze, że możemy poznać jej budowę oglądając powierzchnię globusa.

W modelu występują oczywiście znaczne uproszczenia. Na globusie mojej córki nigdy nie pada deszcz, nie ma powodzi, trzęsień ziemi, itd., ale mogę go użyć, aby przewidzieć, ile czasu zajmie mi podróż z domu do Indianapolis, gdybym musiał osobiście stawić się w wydawnictwie i usprawiedliwić się, dlaczego rękopis się opóźnia („Wiesz, wszystko szło dobrze, ale nagle pogubiłem się w metaforach i przez kilka godzin nie mogłem się z nich wydostać”).

Metoda, która nie jest prostsza od modelowanej rzeczy, nie jest przydatna. Komik Steve Wright zażartował kiedyś: „Mam mapę, na której jeden cal równa się jednemu calowi. Mieszkam na E5.”

Projektowanie oprogramowania zorientowane obiektowo zajmuje się budowaniem dobrych modeli. Składa się z dwóch ważnych elementów: języka modelowania oraz procesu.

 

Projektowanie oprogramowania: język modelowania

Język modelowania jest najmniej znaczącym aspektem obiektowo zorientowanej analizy i projektowania; niestety, przyciąga on najwięcej uwagi. Język modelowania nie jest tylko niż konwencją, określającą sposób rysowania modelu na papierze. Możemy zdecydować, że trójkąty będą reprezentować klasy, a przerywane linie będą symbolizować dziedziczenie. Przy takich założeniach możemy stworzyć model geranium tak, jak pokazano na rysunku 11.1.

 

Rys. 11.1. Generalizacja – specjalizacja

 

Na tym rysunku widać, że Geranium jest szczególnym rodzajem Kwiatu. Jeśli zarówno ty, jak i ja zgodzimy się na rysowanie diagramów dziedziczenia (generalizacji – specjalizacji) w ten sposób, wtedy wzajemnie się zrozumiemy. Prawdopodobnie wkrótce zechcemy stworzyć model mnóstwa złożonych zależności, w tym celu opracujemy nasz złożony zestaw konwencji i reguł rysowania.

Oczywiście, musimy przedstawić nasze konwencje wszystkim osobom, z którymi pracujemy; będzie je musiał poznać każdy nowy pracownik lub współpracownik. Możemy współpracować z innymi firmami, posiadającymi własne konwencje, w związku z czym będziemy potrzebować czasu na wynegocjowanie wspólnej konwencji i wyeliminowanie ewentualnych nieporozumień.

Dużo wygodniej byłoby, gdyby wszyscy zgodzili się na wspólny język modelowania. (Wygodnie byłoby, gdyby wszyscy mieszkańcy Ziemi zgodzili się na używanie wspólnego języka, ale to już inne zagadnienie.) Takim lingua franca w projektowaniu oprogramowania jest UML – Unified Modeling Language (zunifikowany język modelowania)[1]. Zadaniem UML jest udzielenie odpowiedzi na pytania w rodzaju: „Jak rysować relację dziedziczenia?” Model geranium z rysunku 11.1 w UML mógłby zostać przedstawiony tak, jak na rysunku 11.2.

 

Rys. 11.2. Specjalizacja narysowana w UML

 

W UML klasy są rysowane w postaci prostokątów, zaś dziedziczenie jest przedstawiane jako linia zakończona strzałką. Strzałka przebiega w kierunku od klasy bardziej wyspecjalizowanej do klasy bardziej ogólnej. Dla większości osób taki kierunek strzałki jest niezgodny ze zdrowym rozsądkiem, ale nie ma to większego znaczenia; gdy wszyscy się na to zgodzimy, cały system zadziała poprawnie.

Szczegóły działania UML są raczej proste. Diagramy nie są trudne w użyciu i zrozumieniu; zostaną opisane w trakcie ich wykorzystywania. Choć na temat UML można napisać całą książkę, jednak w 90 procentach przypadków będziesz korzystał jedynie z małego podzbioru tego języka; podzbiór ten jest bardzo łatwy do zrozumienia.

 

Projektowanie oprogramowania: proces

Proces obiektowo zorientowanej analizy i projektowania jest dużo bardziej złożony i ważniejszy niż język modelowania. Oczywiście, słyszy się o nim dużo mniej. Dzieje się tak dlatego, że niezgodności dotyczące języka modelowania zostały już w dużym stopniu wyeliminowane; przemysł informatyczny zdecydował się na używanie UML. Debata na temat procesu wciąż trwa.

Metodolog jest osobą, która opracowuje lub studiuje jedną lub więcej metod. Zwykle metodolodzy opracowują i publikują własne metody. Metoda jest językiem modelowania i procesem. Trzech wiodących w branży metodologów to: Grady Booch, który opracował metodę Boocha, Ivar Jacobson, który opracował obiektowo zorientowaną inżynierię oprogramowania oraz James Rumbaugh, który opracował technologię OMT (Object Modeling Technology). Ci trzej mężczyźni stworzyli wspólnie tzw. Rational Unified Process (dawniej znany jako Objectory), metodę oraz komercyjny produkt firmy Rational Software Inc. Wszyscy trzej są zatrudnieni we wspomnianej wyżej firmie, gdzie są znani jako trzej przyjaciele (Three Amigos)[2].

Ten rozdział przedstawia w ogólnym zarysie stworzony przez nich procesem. Nie będę szczegółowo go przedstawiał, gdyż nie wierzę w niewolnicze przywiązanie do akademickiej teorii – dużo bardziej niż postępowanie zgodne z metodą interesuje mnie sprzedanie produktu. Inne metody również dostarczają ciekawych rozwiązań, więc będę starał się wybierać z nich to, co wydaje mi się najlepsze i łączyć to użyteczną całość.

Proces projektowania oprogramowania jest iteracyjny. Oznacza to, że opracowując program „przechodzimy” przez cały proces wielokrotnie, coraz lepiej rozumiejąc jego wymagania. Projekt ukierunkowuje implementację, ale szczegóły, na które zwracamy uwagę podczas implementacji, wpływają z kolei na projekt. Nie próbujemy opracować jakiegokolwiek niebanalnego projektu w pojedynczym, uporządkowanym procesie liniowym; zamiast tego rozwijamy fragmenty projektu, wciąż poprawiając jego założenia oraz ulepszając szczegóły implementacji.

Opracowywanie iteracyjne można odróżnić od opracowywania kaskadowego. W opracowywaniu kaskadowym wynik jednego etapu staje się wejściem dla następnego, przy czym nie istnieje możliwość powrotu (patrz rysunek 11.3). W procesie opracowywania kaskadowego wymagania są szczegółowo przedstawione klientowi i podpisane przez niego („Tak, właśnie tego potrzebuję”); następnie wymagania te są przekazywane projektantowi. Projektant tworzy projekt, po czym przekazuje go programiście, w celu implementacji. Z kolei programista wręcza kod osobie zajmującej się kontrolą jakości, która sprawdza jego działanie i przekazuje go klientowi. Wspaniałe w teorii, katastrofalne w praktyce.

 

Rys. 11.3. Model kaskadowy

 

Przy opracowywaniu iteracyjnym zaczynamy od koncepcji; pomysłu, jak moglibyśmy to zbudować. W miarę poznawania szczegółów nasza wizja może rozrastać się i ewoluować.

Gdy już dobrze znamy wymagania, możemy rozpocząć projektowanie, doskonale zdając sobie sprawę, że pytania, które się wtedy pojawią, mogą wprowadzić zmiany w wymaganiach. Pracując nad projektem, zaczynamy tworzyć prototyp, a następnie implementację produktu. Zagadnienia pojawiające się podczas opracowywania programu wpływają na zmiany w projekcie i mogą nawet wpłynąć na zrozumienie wymagań. Projektujemy i implementujemy tylko części produktu, powtarzając za każdym razem fazy projektowania i implementacji.

Choć poszczególne etapy tego procesu są powtarzane, jednak opisanie ich w sposób cykliczny jest prawie niemożliwe. Dlatego opiszę je w następującej kolejności: koncepcja początkowa, analiza, projekt, implementacja, testowanie, prezentacja. Nie zrozum mnie źle — w rzeczywistości, podczas tworzenia pojedynczego produktu przechodzimy przez każdy z tych kroków wielokrotnie. Po prostu proces iteracyjny byłby trudny do przedstawienia, gdybyśmy chcieli pokazać cykliczne wykonywanie każdego z kroków.

Oto kolejne kroki iteracyjnego procesu projektowania:

1. Konceptualizacja.

2. Analiza.

3. Projektowanie.

4. Implementacja.

5. Testowanie

6. Prezentacja.

 

Konceptualizacja to „tworzenie wizji”. Jest pojedynczym zdaniem, opisującym dany pomysł.

Analiza jest procesem zrozumienia wymagań.

Projektowanie jest procesem tworzenia modelu klas, na podstawie którego wygenerujemy kod.

Implementacja jest pisaniem kodu (na przykład w C++).

Testowanie jest upewnianiem się, czy wykonaliśmy wszystko poprawnie.

Prezentacja to pokazanie produktu klientom.

Bułka z masłem. Cała reszta to detale.

 

Kontrowersje

Pojawia się mnóstwo kontrowersji na temat tego, co dzieje się na każdym etapie procesu projektowania iteracyjnego, a nawet na temat nazw poszczególnych etapów. Zdradzę ci sekret: to nie ma znaczenia. Podstawowe kroki są w każdym obiektowo zorientowanym procesie takie same: dowiedz się, co chcesz zbudować, zaprojektuj rozwiązanie i zaimplementuj projekt.

Choć w grupach dyskusyjnych i listach mailingowych dyskutujących o technologii obiektowej dzieli się włos na czworo, podstawowa analiza i projektowanie obiektowe są niezmienne. W tym rozdziale przedstawię pewien punkt widzenia na ten temat, mając nadzieję, że utworzę w ten sposób fundament, na którym będziesz mógł stworzyć architekturę swojej aplikacji.

Celem tej pracy jest stworzenie kodu, który spełnia założone wymagania i który jest stabilny, możliwy do rozbudowania i łatwy do modyfikacji. Najważniejsze jest stworzenie kodu o wysokiej jakości (w określonym czasie i przy założonym funduszu).

 

Programowanie ekstremalne

Ostatnio pojawiła się nowa koncepcja analizy i projektowania, zwana programowaniem ekstremalnym. Programowanie to zostało omówione przez Kena Becka w książce Extreme Programming Expanded: Embrace Change (Addison-Wesley, 1999 ISBN 0201616416).

W tej książce Beck przedstawia kilka radykalnych i cudownych pomysłów, np. by nie kodować niczego, dopóki nie będzie można sprawdzić, czy to działa, a także programowanie w parach (dwóch programistów przy jednym komputerze). Jednak z naszego punktu widzenia, najważniejszym jego stwierdzeniem jest to, że zmianom ulegają wymagania. Należy więc sprawić, by program działał i utrzymywać to działanie; należy projektować dla wymagań, które się zna i nie tworzyć projektów „na wyrost”.

Jest to, oczywiście, ogromne uproszczenie wypowiedzi Becka i sądzę, że mógłby uznać je za przeinaczenie, jednak w każdym razie wierzę w jego sedno: spraw by program działał, tworząc go dla wymagań, które rozumiesz i staraj się nie doprowadzić do sytuacji, w której, programu nie można już zmienić.

Bez zrozumienia wymagań (analiz) i planowania (projekt), trudno jest stworzyć stabilny i łatwy do modyfikacji program, jednak staraj się nie kontrolować zbyt wielu czynności.

 

Pomysł

Wszystkie wspaniałe programy powstają z jakiegoś pomysłu. Ktoś ma wizję produktu, który uważa za warty wdrożenia. Pożyteczne pomysły rzadko kiedy powstają w wyniku pracy zbiorowej. Pierwszą fazą obiektowo zorientowanej analizy i projektowania jest zapisanie takiego pomysłu w pojedynczym zdaniu (a przynajmniej krótkim akapicie). Pomysł ten staje się myślą przewodnią tworzonego programu, zaś zespół, który zbiera się w celu zaimplementowania tego pomysłu, powinien podczas pracy odwoływać się do tej myśli przewodniej — w razie potrzeby nawet ją modyfikując.

Nawet jeśli pomysł narodził się w wyniku zespołowej pracy działu marketingu, „wizjonerem” powinna zostać jedna osoba. Jej zadaniem jest utrzymywanie „czystości idei”. W miarę rozwoju projektu wymagania będą ewoluować. Harmonogram pracy może (i powinien) modyfikować to, co próbujesz osiągnąć w pierwszej iteracji programowania, jednak wizjoner musi zapewnić, że wszystko to, co zostanie stworzone, odzwierciedli pierwotny pomysł. Właśnie jego bezwzględne poświęcenie i żarliwe zaangażowanie doprowadza do ukończenia projektu. Gdy stracisz z oczu pierwotny zamysł, twój produkt jest skazany na niepowodzenie.

 

Analiza wymagań

Faza konceptualizacji, w której precyzowany jest pomysł, jest bardzo krótka. Może trwać krócej niż błysk olśnienia połączony z czasem wymaganym do zapisania pomysłu na kartce. Często zdarza się, że dołączasz do projektu jako ekspert zorientowany obiektowo wtedy, gdy wizja została już sprecyzowana.

Niektóre firmy mylą pomysł z wymaganiami. Wyraźna wizja jest potrzebna, lecz sama w sobie nie jest wystarczająca. Aby przejść do analizy, musisz zrozumieć, w jaki sposób produkt będzie używany i jak musi działać. Celem fazy analizy jest sprecyzowanie tych wymagań. Efektem końcowym tej fazy jest stworzenie dokumentu zawierającego opracowane wymagania. Pierwszą częścią tego dokumentu jest analiza przypadków użycia produktu.

 

Przypadki użycia

Istotną częścią analizy, projektowania i implementacji są przypadki użycia. Przypadek użycia jest ogólnym opisem sposobu, w jaki produkt będzie używany. Przypadki użycia nie tylko ukierunkowują analizę, ale także pomagają w określeniu klas i są szczególnie ważne podczas testowania produktu.

Tworzenie stabilnego i wyczerpującego zestawu przypadków użycia może być najważniejszym zadaniem całej analizy. Właśnie wtedy jesteś najbardziej uzależniony od ekspertów w danej dziedzinie, to oni wiedzą najwięcej o dziedzinie pracy, której wymagania próbujesz określić.

Przypadki użycia są w niewielkim stopniu związane z interfejsem użytkownika, nie są natomiast związane z wnętrzem budowanego systemu. Każda osoba (lub system) współpracująca z projektowanym systemem jest nazywana aktorem.

Dokonajmy krótkiego podsumowania:

 

- przypadek użycia – opis sposobu, w jaki używane będzie oprogramowanie,

- eksperci – osoby znające się na dziedzinie, dla której tworzysz produkt,

- aktor – każda osoba (lub system) współpracująca z projektowanym systemem.

 

Przypadek użycia jest opisem interakcji zachodzących pomiędzy aktorem a samym systemem. W trakcie analizy przypadku użycia system jest traktowany jako „czarna skrzynka.” Aktor „wysyła komunikat” do systemu, po czym zwracana jest informacja, zmienia się stan systemu, statek kosmiczny zmienia kierunek, itd.

 

Identyfikacja aktorów

Należy pamiętać, że nie wszyscy aktorzy są ludźmi. Systemy współpracujące z budowanym systemem także są aktorami. Gdy budujesz na przykład bankomat, aktorami mogą być urzędnik bankowy i klient – a także inny system współpracujący z aktualnie tworzonym systemem, na przykład system śledzenia pożyczek czy udzielania kredytów studenckich. Oto podstawowa charakterystyki aktorów:

 

- są oni na zewnątrz dla systemu,

- współpracują z systemem.

 

Często najtrudniejszą częścią analizy przypadków użycia jest jej początek. Zwykle najlepszą metodą „ruszenia z miejsca” jest sesja burzy mózgów. Po prostu spisz listę osób i systemów, które będą pracować z nowym systemem. Pamiętaj, że mówiąc o ludziach, w rzeczywistości mamy na myśli role – urzędnika bankowego, kasjera, klienta, itd. Jedna osoba może pełnić więcej niż jedną rolę.

We wspomnianym przykładzie z bankomatem, na naszej liście mogą wystąpić następujące role:

 

- klient

- personel banku

- system bankowy

- osoba wypełniająca bankomat pieniędzmi i materiałami

 

Na początku nie ma potrzeby wychodzenia poza tę listę. Wygenerowanie trzech czy czterech aktorów może wystarczyć do rozpoczęcia generowania przypadków użycia. Każdy z tych aktorów pracuje z systemem w inny sposób; chcemy wykryć te interakcje w naszych sposobach użycia.

 

Wyznaczanie pierwszych przypadków użycia

Zacznijmy od roli klienta. Podczas burzy mózgów możemy określić następujące przypadki użycia dla klienta:

 

- klient sprawdza stan swojego rachunku,

- klient wpłaca pieniądze na swój rachunek,

- klient wypłaca pieniądze ze swojego rachunku,

- klient przelewa pieniądze z rachunku na rachunek,

- klient otwiera rachunek,

- klient zamyka rachunek.

 

Czy powinniśmy dokonać rozróżnienia pomiędzy „klient wpłaca pieniądze na swój rachunek bieżący” a „klient wpłaca pieniądze na lokatę”, czy też powinniśmy te działania połączyć (tak jak na powyższej liście) w „klient wpłaca pieniądze na swój rachunek?” Odpowiedź na to pytanie zależy od tego, czy takie rozróżnienie ma znaczenie dla danej dziedziny (dziedzina jest rzeczywistym środowiskiem, które modelujemy – w tym przypadku jest nią bankowość).

Aby sprawdzić, czy te działania są jednym przypadkiem użycia, czy też dwoma, musisz zapytać, czy ich mechanizmy są różne (czy klient w każdym z przypadków robi coś innego) i czy różne są wyniki (czy system odpowiada na różne sposoby). W naszym przykładzie, w obu przypadkach odpowiedź brzmi „nie”: klient składa pieniądze na każdy z rachunków w ten sam sposób, przy czym wynik także jest podobny, gdyż bankomat odpowiada, zwiększając stan odpowiedniego rachunku.

Zakładając, że aktor i system działają i odpowiadają mniej więcej identycznie, bez względu na to, na jaki rachunek dokonuje wpłaty, te dwa przypadki użycia są w rzeczywistości jednym sposobem. Później, gdy opracujemy scenariusze przypadków użycia, możemy wypróbować obie wariacje i sprawdzić, czy ich rezultatem są jakiekolwiek różnice.

Odpowiadając na poniższe pytania, możesz odkryć dodatkowe przypadki użycia:

 

1. Dlaczego aktor używa tego systemu?

Klient używa tego systemu, aby zdobyć gotówkę, złożyć depozyt lub sprawdzić bieżący stan rachunku.

2. Jakiego wyniku oczekuje aktor po każdym żądaniu?

Zwiększenia stanu rachunku lub uzyskania gotówki na zakupy.

3. Co spowodowało, że aktor używa w tym momencie systemu?

Być może ostatnio otrzymał wypłatę lub jest na zakupach.

4. Co aktor musi zrobić, aby użyć systemu?

Włożyć kartę do szczeliny w bankomacie.

Aha! Potrzebujemy przypadku użycia dla logowania się klienta do systemu.

5. Jakie informacje aktor musi dostarczyć systemowi?

Musi wprowadzić kod PIN.

Aha! Potrzebujemy przypadków użycia dla uzyskania i edycji kodu PIN.

6. Jakich informacji aktor oczekuje od systemu?

Stanu rachunku itd.

 

Często dodatkowe przypadki użycia możemy znaleźć, skupiając się na atrybutach obiektów w danej dziedzinie. Klient posiada nazwisko, kod PIN oraz numer rachunku; czy występują przypadki użycia dla zarządzania tymi obiektami? Rachunek posiada swój numer, stan oraz historię transakcji; czy wykryliśmy te elementy w przypadkach użycia?

Po szczegółowym przeanalizowaniu przypadków użycia dla klienta, następnym krokiem w opracowywaniu listy przypadków użycia jest opracowanie przypadków użycia dla wszystkich pozostałych aktorów. Poniższa lista przedstawia pierwszy zestaw przypadków użycia dla naszego przykładu z bankomatem:

 

- klient sprawdza stan swojego rachunku,

- klient wpłaca pieniądze na swój rachunek,

- klient wypłaca pieniądze ze swojego rachunku,

- klient przekazuje pieniądze z rachunku na rachunek,

- klient otwiera rachunek,

- klient zamyka rachunek,

- klient loguje się do swojego rachunku,

- klient sprawdza ostatnie transakcje,

- urzędnik bankowy loguje się do specjalnego konta przeznaczonego do zarządzania,

- urzędnik bankowy dokonuje zmian w rachunku klienta,

- system bankowy aktualizuje stan rachunku klienta na podstawie działań zewnętrznych,

- zmiany rachunku użytkownika są odzwierciedlane w systemie bankowym,

- bankomat sygnalizuje niedobór pieniędzy,

- technik uzupełnia w bankomacie gotówkę i materiały.

 

Tworzenie modelu dziedziny

Gdy masz już pierwszą wersję przypadków użycia, możesz zacząć wypełniać dokument wymagań szczegółowym modelem dziedziny. Model dziedziny jest dokumentem zawierającym wszystko to, co wiesz o danej dziedzinie (zagadnieniu, nad którym pracujesz). Jako część modelu dziedziny tworzysz obiekty dziedziny, opisujące wszystkie obiekty wymienione w przypadkach użycia. Przykład z bankomatem zawiera następujące obiekty: klient, personel banku, system bankowy, rachunek bieżący, lokata, itd.

Dla każdego z tych obiektów dziedziny chcemy uzyskać tak ważne dane, jak nazwa obiektu (na przykład klient, rachunek, itd.), czy obiekt jest aktorem, podstawowe atrybuty i zachowanie obiektu, itd. Wiele narzędzi do modelowania wspiera zbieranie tych informacji w opisach „klas.” Na przykład, rysunek 11.4 przedstawia sposób, w jaki te informacje są zbierane w systemie Rational Rose.

 

Rys. 11.4. Rational Rose

 

Należy zdawać sobie sprawę, że to, co opisujemy, nie jest obiektem projektu, ale obiektem dziedziny. Odzwierciedla sposób funkcjonowania świata, a nie sposób działania naszego systemu.

Możemy określić relacje pomiędzy obiektami dziedziny pojawiającymi się w przykładzie z bankomatem, używając UML – korzystając z takich samych konwencji rysowania, jakich użyjemy później do opisania relacji pomiędzy klasami w dziedzinie. Jest to jedna z ważniejszych zalet UML: możemy używać tych samych narzędzi na każdym etapie projektu.

Na przykład, używając konwencji UML dla klas i powiązań generalizacji, możemy przedstawić rachunki bieżące i rachunki lokat jako specjalizacje bardziej ogólnej koncepcji rachunku bankowego, tak jak pokazano na rysunku 11.5.

 

Rys. 11.5. Specjalizacje

 

Na diagramie z rysunku 11.5 prostokąty reprezentują różne obiekty dziedziny; zaś strzałki wskazują generalizację. UML zakłada, że linie są rysowane w kierunku od klasy wyspecjalizowanej do bardziej ogólnej klasy „bazowej.” Dlatego, zarówno Rachunek bieżący, jak i Rachunek lokaty, wskazują na Rachunek bankowy, informując że każdy z nich jest wyspecjalizowaną formą Rachunku bankowego.

 

UWAGA              Pamiętajmy, że...

Zgłoś jeśli naruszono regulamin