Analiza i projektowanie obiektowe. Rusz głową!

Współczesne systemy informatyczne mają niewiele wspólnego z tymi sprzed kilkunastu lat. Są skomplikowane, nafaszerowane

543 66 36MB

Polish Pages [606]

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

Analiza i projektowanie obiektowe. Rusz głową!

Table of contents :
Spis treści
Wprowadzenie
Dla kogo jest ta książka?
Wiemy, co sobie myślisz
Metapoznanie: myślenie o myśleniu
Ważne uwagi
Zespół techniczny
Podziękowania
1. Tu się zaczyna wspaniałe oprogramowanie
Rock-and-roll jest wieczny!
Nowa elegancka aplikacja Ryśka…
Co przede wszystkim zmieniłbyś w aplikacji Ryśka?
Doskonałe oprogramowanie… ma więcej niż jedną z wymienianych już cech
Wspaniałe oprogramowanie w trzech prostych krokach
W pierwszej kolejności skoncentruj się na funkcjonalności
Test
Szukamy problemów
Analiza metody search()
Stosuj proste zasady projektowania obiektowego
Projekt po raz pierwszy, projekt po raz drugi
Jak łatwo można wprowadzać zmiany w Twojej aplikacji?
Poddawaj hermetyzacji to, co się zmienia
Delegowanie
Nareszcie doskonałe opgrogramowanie (jak na razie)
OOA&D ma na celu tworzenie wspaniałego oprogramowania, a nie dodanie Ci papierkowej roboty!
Celne spostrzeżenia
2. Daj im to, czego chcą
Nadszedł czas na kolejny pokaz Twych programistycznych umiejętności
Test programu
Nieprawidłowe zastosowanie (cos w tym stylu)
Czym jest wymaganie?
Tworzenie listy wymagań
Zaplanuj, co może się popsuć w systemie
Problemy w działaniu systemu są obsługiwane przez ścieżki alternatywne
(Ponowne) przedstawienie przypadku użycia
Jeden przypadek użycia, trzy części
Porównaj wymagania z przypadkami użycia.
Twój system musi działać w praktyce
Poznajemy Szczęśliwą Ścieżkę
Przybornik projektanta
3. Kocham cię, jesteś doskonały… A teraz — zmień się
Jesteś bohaterem!
Jesteś patałachem!
Jedyny pewnik analizy i projektowania obiektowego
Ścieżką oryginalną? Ścieżką alternatywną? Kto to wie?
Przypadki użycia muszą być zrozumiałe i użyteczne przede wszystkim dla Ciebie
Od startu do mety: jeden scenariusz
Wyznanie Ścieżki Alternatywnej
Uzupełnienie listy wymagań
Powielanie kodu jest bardzo złym pomysłem
Ostateczny test drzwiczek
Napisz swoją własną zasadę projektową!
Przybornik projektanta
4. Zaczynamy używać naszych aplikacji w rzeczywistym świecie
Jeden pies, dwa psy, trzy psy, cztery…
Twoje oprogramowanie ma kontekst
Określ przyczynę problemu
Zaplanuj rozwiązanie
Opowieść o dwóch programistach
Delegowanie w kodzie Szymka — analiza szczegółowa
Potęga aplikacji, których elementy są ze sobą luźno powiązane
Zwracaj uwagę na rzeczowniki występujące w przypadku użycia
Od dobrej analizy do dobrych klas…
Diagramy klas bez tajemnic
Diagramy klas to nie wszystko
Celne spostrzeżenia
5. (Część 1.) Nic nie pozostaje wiecznie takie samo
Firma Instrumenty Strunowe Ryśka rozwija się
Klasy abstrakcyjne
Diagramy klas bez tajemnic (ponownie)
Ściągawka z UML-a
Porady dotyczące problemów projektowych
Trzy kroki tworzenia wspaniałego oprogramowania (po raz kolejny)
5. (Przerywnik) Obiektowa katastrofa!
5. (Część 2.) Zabierz swoje oprogramowanie na 30-minutowy trening
Wróćmy do aplikacji wyszukiwawczej Ryśka
Dokładniejsza analiza metody search()
Korzyści, jakie dała nam analiza
Dokładniejsza analiza klas instrumentów
Śmierć projektu (decyzja)
Zmieńmy złe decyzje projektowe na dobre
Zastosowanie „podwójnej hermetyzacji” w aplikacji Ryśka
Nigdy nie obawiaj się wprowadzania zmian
Elastyczna aplikacja Ryśka
Testowanie dobrze zaprojektowanej aplikacji Ryśka
Jak łatwo można zmodyfikować aplikacje Ryśka?
Wielki konkurs łatwości modyfikacji
Spójna klasa realizuje jedną operację naprawdę dobrze
Przegląd zmian wprowadzonych w oprogramowaniu dla Ryśka
Doskonałe oprogramowanie to zazwyczaj takie, które jest "wystarczająco dobre"
Przybornik projektanta
6. „Nazywam się Art Vandelay… jestem Architektem”
Rozwiązywanie dużych problemów
Wszystko zależy od sposobu spojrzenia na duży problem
Wymagania i przypadki użycia to dobry punkt wyjściowy...
Potrzebujemy znacznie więcej informacji
Określanie możliwości
Możliwość czy wymaganie
Przpadki użycia nie zawsze pomagają ujrzeć ogólny obraz tworzonego oprogramowania
Diagramy przypadków użycia
Mały aktor
Aktorzy to także ludzie (no dobrze… nie zawsze)
A zatem zabawmy się w analizę dziedziny!
Dziel i rządź
Nie zapominaj, kim tak naprawdę jest klient
Czym jest wzorzec projektowy?
Potęga OOA&D (i trochę zdrowego rozsądku)
Przybornik projektanta
7. Porządkowanie chaosu
Czy czujesz się nieco przytłoczony?
Potrzebujemy architektury
Zacznijmy od funkcjonalności
Co ma znaczenie dla architektury
Trzy „P” dotyczące architektury
Wszystko sprowadza się do problemu ryzyka
Scenariusze pomagają zredukować ryzyko
Koncentruj się na jednej możliwości w danej chwili
Architektura jest strukturą Twojego projektu
Podobieństwa po raz kolejny
Analiza podobieństw: ścieżka do elastycznego oprogramowania
Co to znaczy? Zapytaj klienta
Zmniejszanie ryzyka pomaga pisać wspaniałe oprogramowanie
Celne spostrzeżenia
8. Oryginalność jest przereklamowana
Zasada projektowania — w skrócie
Zasada otwarte-zamknięte
OCP krok po kroku
Zasada nie powtarzaj się
Zasada DRY dotyczy obsługi JEDNEGO wymagania w JEDNYM miejscu
Zasada jednej odpowiedzialności
Wykrywanie wielu odpowiedzialności
Przechodzenie od wielu do jednej odpowiedzialności
Zasada podstawienia Liskov
Studium błędnego sposobu korzystania z dziedziczenia
LSP ujawnia ukryte problemy związane ze strukturą dziedziczenia
Musi istnieć możliwość zastąpienia typu bazowego jego typem pochodnym
Naruszenia LSP sprawiają, że powstający kod staje się mylący
Deleguj funkcjonalność do innej klasy
Użyj kompozycji, by zebrać niezbędne zachowania z kilku innych klas
Agregacja — kompozycja bez nagłego zakończenia
Agregacja a kompozycja
Dziedziczenie jest jedynie jedną z możliwości
Celne spostrzeżenia
Przybornik projektanta
9. Oprogramowanie jest wciąż przeznaczone dla klienta
Twój przybornik narzędziowy powoli się wypełnia
Wspaniałe opgrogramowanie tworzy się iteracyjnie
Schodzenie w głąb: dwie proste opcje
Programowanie w oparciu o możliwości
Programowanie w oparciu o przypadki użycia
Dwa podejścia do tworzenia oprogramowania
Analiza możliwości
Pisanie scenariuszy testowych
Programowanie w oparciu o testy
Podobieństwa po raz wtóry
Kładziemy nacisk na podobieństwa
Hermetyzujemy wszystko
Dopasuj testy do projektu
Testy bez tajemnic…
Udowodnij klientowi, że wszystko idzie dobrze
Jak dotąd używaliśmy programowania w oparciu o kontrakt.
Tak naprawdę programowanie w oparciu o kontrakt dotyczy zaufania
Programowanie defensywne
Podziel swoją aplikację na mniejsze fragmenty funkcjonalności
Celne spostrzeżenia
Przybornik projektanta
10. Scalając to wszystko w jedno
Tworzenie oprogramowania w stylu obiektowym
Trans-Obiektów
Mapa metra w Obiektowie
Lista możliwości
Przypadki użycia odpowiadają zastosowaniu, możliwości odpowiadają funkcjonalności
A teraz zacznij powtarzać te same czynności
Dokładniejsza analiza sposobu reprezentacji sieci metra
Używać klasy Line czy też nie używać… oto jest pytanie
Najważniejsze sprawy związane z klasą Subway
Ochrona własnych klas
Czas na przerwę
Wróćmy znowu do etapu określania wymagań…
Koncentruj się na kodzie, a potem na klientach
Powtarzanie sprawia, że problemy stają się łatwiejsze
Jak wygląda trasa?
Samemu sprawdź Przewodnik Komunikacyjny po Obiektowie
Ktoś chętny na trzeci cykl prac?
Podróż jeszcze nie dobiegła końca…
A. Dziesięć najważniejszych tematów (których nie poruszyliśmy)
Nr 1. JEST i MA
Nr 2. Sposoby zapisu przypadków użycia
Nr 3. Antywzorce
Nr 4. Karty CRC
Nr 5. Metryki
Nr 6. Diagramy sekwencji
Nr 7. Diagramy stanu
Nr 8. Testowanie jednostkowe
Nr 9. Standardy kodowania i czytelny kod
Nr 10. Refaktoryzacja
B. Stosowanie języka obiektowego
UML i diagramy klas
Dziedziczenie
Polimorfizm…
Hermetyzacja
Celne spostrzeżenia

Citation preview

Spis treści

Spis treści (skrócony) 1

Dobrze zaprojektowane aplikacje są super. Tu zaczyna się wspaniałe oprogramowanie

2

Gromadzenie wymagań. Daj im to, czego chcą

3

Wymagania ulegają zmianom. Kocham cię, jesteś doskonały… A teraz — zmień się

137

4

Analiza. Zaczynamy używać naszych aplikacji w rzeczywistym świecie

169

5

Część 1. Dobry projekt = elastyczne oprogramowanie. Nic nie pozostaje wiecznie takie samo Przerywnik. Obiektowa katastrofa

221

31 83

245

Część 2. Dobry projekt = elastyczne oprogramowanie. Zabierz swoje oprogramowanie na 30-minutowy trening

257

6

Rozwiązywanie naprawdę dużych problemów. „Nazywam się Art Vandelay... jestem Architektem”

301

7

Architektura. Porządkowanie chaosu

343

8

Zasady projektowania. Oryginalność jest przereklamowana

395

9

Powtarzanie i testowanie. Oprogramowanie jest wciąż przeznaczone dla klienta

441

10 Proces projektowania i analizy obiektowej. Scalając to wszystko w jedno

499

A

571

B

Pozostałości Witamy w Obiektowie

589

Skorowidz

603

Spis treści (z prawdziwego zdarzenia)

W

Wprowadzenie Twój mózg koncentruje się na analizie i projektowaniu obiektowym. Podczas gdy Ty starasz się czegoś nauczyć, Twój mózg robi Ci przysługę i dba o to, abyś przez przypadek nie zapamiętał zdobywanych informacji. Myśli sobie: „Lepiej zostawić trochę miejsca na bardziej istotne sprawy, na przykład jakich zwierząt unikać albo czy jazda na snowboardzie nago jest dobrym pomysłem”. W jaki zatem sposób możesz oszukać swój mózg i przekonać go, że Twoje życie zależy od znajomości analizy i projektowania obiektowego? Dla kogo jest ta książka?

20

Wiemy, co sobie myślisz

21

Metapoznanie: myślenie o myśleniu

23

Zmuś swój mózg do posłuszeństwa

25

Ważne uwagi

26

Zespół techniczny

28

Podziękowania

29

5

Spis treści

Dobrze zaprojektowane aplikacje są super

1

Tu zaczyna się wspaniałe oprogramowanie A zatem, w jaki sposób w praktyce pisze się wspaniałe oprogramowanie? Zawsze bardzo trudno jest określić, od czego należy zacząć. Czy aplikacja faktycznie robi to, co powinna robić i czego od niej oczekujemy? A co z takimi problemami jak powtarzający się kod — przecież to nie może być dobre ani właściwe rozwiązanie, prawda? Zazwyczaj trudno jest określić, które z wielu problemów należy rozwikłać w pierwszej kolejności, a jednocześnie mieć pewność, że podczas wprowadzania poprawek nie popsujemy innych fragmentów aplikacji. Bez obaw. Po zakończeniu lektury tego rozdziału będziesz już dokładnie wiedział, jak pisać doskonałe oprogramowanie, i pewnie podążał w kierunku trwałego poprawienia sposobu tworzenia programów. I w końcu zrozumiesz, dlaczego OOA&D to czteroliterowy skrót (pochodzący od angielskich słów: Object-Oriented Analysis and Design, analiza i projektowanie obiektowe), który Twoja matka chciałaby, byś poznał.

Rock-and-roll jest wieczny!

Niby skąd mam wiedzieć, od czego należy zacząć? Mam wrażenie, że ilekroć zaczynam pracę nad nowym projektem, każdy ma inne zdanie odnośnie tego, co należy zrobić w pierwszej kolejności. Czasami zrobię coś dobrze, lecz czasami kończy się na tym, że muszę przerobić całą aplikację od początku, bo zacząłem w złym miejscu. A ja chcę jedynie pisać świetne oprogramowanie! A zatem, od czego powinienem zacząć pisanie aplikacji dla Ryśka?

6

32

Nowa elegancka aplikacja Ryśka…

33

Co przede wszystkim zmieniłbyś w aplikacji Ryśka?

38

Doskonałe oprogramowanie… ma więcej niż jedną z wymienionych już cech

42

Wspaniałe oprogramowanie w trzech prostych krokach

43

W pierwszej kolejności skoncentruj się na funkcjonalności

48

Test

53

Szukamy problemów

55

Analiza metody search()

56

Stosuj proste zasady projektowania obiektowego

61

Projekt po raz pierwszy, projekt po raz drugi

66

Jak łatwo można wprowadzać zmiany w Twojej aplikacji?

68

Poddawaj hermetyzacji to, co się zmienia

71

Delegowanie

73

Nareszcie doskonałe oprogramowanie (jak na razie)

76

OOA&D ma na celu tworzenie wspaniałego oprogramowania, a nie dodanie Ci papierkowej roboty

79

Celne spostrzeżenia

80

Spis treści

Gromadzenie wymagań

2

Daj im to, czego chcą Każdy lubi zadowolonych klientów. Już wiesz, że pierwszy krok w pisaniu doskonałego oprogramowania polega na upewnieniu się, czego chce klient. Ale jak się dowiedzieć, czego klient oczekuje? Co więcej — skąd mieć pewność, że klient w ogóle wie, czego tak naprawdę chce? Właśnie wówczas na arenę wkraczają „dobre wymagania”. W tym rozdziale dowiesz się, w jaki sposób zadowolić klientów, upewniając się, że dostarczysz im właśnie to, czego chcą. Kiedy skończysz lekturę, wszystkie swoje projekty będziesz mógł opatrzyć etykietą „Satysfakcja gwarantowana” i posuniesz się o kolejny krok na drodze do tworzenia doskonałego oprogramowania… i to za każdym razem.

Nadszedł czas na kolejny pokaz Twych programistycznych umiejętności

84

Test programu

87

Nieprawidłowe zastosowanie (coś w tym stylu)

89

Czym jest wymaganie?

90

Tworzenie listy wymagań

92

Zaplanuj, co może się popsuć w systemie

96

Problemy w działaniu systemu są obsługiwane przez ścieżki alternatywne

98

(Ponowne) przedstawienie przypadku użycia

100

Jeden przypadek użycia, trzy części

102

Porównaj wymagania z przypadkami użycia

106

Twój system musi działać w praktyce

113

Poznajemy Szczęśliwą Ścieżkę

120

Przybornik projektanta

134

System

Drzwiczki dla psa oraz pilot stanowią elementy systemu, bądź też znajdują się wewnątrz niego.

7

Spis treści

Wymagania ulegają zmianom

3

Kocham cię, jesteś doskonały… A teraz — zmień się Sądzisz, że dowiedziałeś się już wszystkiego o tym, czego chciał klient? Nie tak szybko… A zatem przeprowadziłeś rozmowy z klientem, zgromadziłeś wymagania, napisałeś przypadki użycia, napisałeś i dostarczyłeś klientowi odlotową aplikację. W końcu nadszedł czas na miłego, relaksującego drinka, nieprawdaż? Pewnie… aż do momentu gdy klient uzna, że tak naprawdę chce czegoś innego niż to, co Ci powiedział. Bardzo podoba mu się to, co zrobiłeś — poważnie! — jednak obecnie nie jest już w pełni usatysfakcjonowany. W rzeczywistym świecie wymagania zawsze się zmieniają; to Ty musisz sobie z tymi zmianami poradzić i pomimo nich zadbać o zadowolenie klienta.

Jesteś bohaterem!

138

Jesteś patałachem!

139

Jedyny pewnik analizy i projektowania obiektowego

141

Ścieżka oryginalna? Ścieżka alternatywna? Kto to wie?

146

Przypadki użycia muszą być zrozumiałe przede wszystkim dla Ciebie

148

Od startu do mety: jeden scenariusz

150

Wyznanie Ścieżki Alternatywnej

152

Uzupełnienie listy wymagań

156

Powielanie kodu jest bardzo złym pomysłem

164

Ostateczny test drzwiczek

166

Napisz swoją własną zasadę projektową!

167

Przybornik projektanta

168

SXEOLFYRLGSUHVV%XWWRQ ^ 6\VWHPRXWSULQWOQ ĵ1DFLĂQLÚWRSU]\FLVNQDSLORFLHĵ  LI GRRULV2SHQ ^ GRRUFORVH  `HOVH^ GRRURSHQ  ILQDO7LPHUWLPHU QHZ7LPHU  WLPHUVFKHGXOH QHZ7LPHU7DVN ^ SXEOLFYRLGUXQ ^ GRRUFORVH     WLPHUFDQFHO  ` `  `





FODVV 5HPRWH^ SUHVV %XWWRQ `

SVOTK`K

,YK\NTK`K

?XS^ ^cZO$ =^\SXQ Z\YZO\^SO]$ 7KZ ]O^>cZO=^\SXQ QO^>cZO$ =^\SXQ ]O^:\YZO\^c=^\SXQ 9LTOM^ QO^:\YZO\^c=^\SXQ$ 9LTOM^

Gdzieś musisz zacząć, jednak uważaj, żeby wybrać właściwe „gdzieś ”! Już wiesz, jak podzielić swoją aplikację na wiele małych problemów, jednak oznacza to tylko i wyłącznie tyle, iż obecnie nie masz jednego dużego, lecz WIELE małych problemów. W tym rozdziale spróbujemy pomóc Ci w określeniu, gdzie należy zacząć, i upewnimy się, że nie będziesz marnował czasu na zajmowanie się nie tym, co trzeba. Nadeszła pora, by pozbierać te wszystkie drobne kawałki na Twoim biurku i zastanowić się, jak można je przekształcić w uporządkowaną i dobrze zaprojektowaną aplikację. W tym czasie poznasz niesłychanie ważne „trzy P dotyczące architektury” i dowiesz się, że Risk to znacznie więcej niż jedynie słynna gra wojenna z lat 80.

8KTWXSOT]dcMR ]dKX] XK dN¦ÒOXSO a ^O\WSXSO

ASOVUS KU LVS]UY SNOK¸_ TKU ^cVUY

YZ\YQ\KWYaKXSO WYÒO Lc¨

13

Spis treści

Zasady projektowania

8

Oryginalność jest przereklamowana Powielanie jest najlepszą formą unikania głupoty. Nic chyba nie daje większej satysfakcji niż opracowanie całkowicie nowego i oryginalnego rozwiązania problemu, który męczy nas od wielu dni… aż do czasu gdy okaże się, że ktoś rozwikłał ten sam problem już wcześniej, a co gorsza — zrobił to znacznie lepiej niż my. W tym rozdziale przyjrzymy się kilku zasadom projektowania, które udało się sformułować podczas tych wszystkich lat stosowania komputerów, i dowiemy się, w jaki sposób mogą one sprawić, że staniesz się lepszym programistą. Porzuć ambitne myśli o „zrobieniu tego lepiej” — lektura tego rozdziału udowodni Ci, jak pisać programy sprytniej i szybciej.

Zasada otwarte-zamknięte

Zasada nie powtarzaj się

Zasada jednejości odpowiedzialn

Zasada projektowania — w skrócie

396

Zasada otwarte-zamknięte

397

OCP, krok po kroku

399

Zasada nie powtarzaj się

402

Zasada DRY dotyczy obsługi jednego wymagania w jednym miejscu

404

Zasada jednej odpowiedzialności

410

Wykrywanie wielu odpowiedzialności

412

Przechodzenie od wielu do jednej odpowiedzialności

415

Zasada podstawienia Liskov

420

Studium błędnego sposobu korzystania z dziedziczenia

421

LSP ujawnia ukryte problemy związane ze strukturą dziedziczenia

422

Musi istnieć możliwość zastąpienia typu bazowego jego typem pochodnym

423

Naruszenia LSP sprawiają, że powstający kod staje się mylący

424

Deleguj funkcjonalność do innej klasy

426

Użyj kompozycji, by zebrać niezbędne zachowania z kilku innych klas

428

Agregacja — kompozycja bez nagłego zakończenia

432

Agregacja a kompozycja

433

Dziedziczenie jest jedynie jedną z możliwości

434

Celne spostrzeżenia

437

Przybornik projektanta

438

Zasada podstawienia Liskov

14

Spis treści

Powtarzanie i testowanie

9

Oprogramowanie jest wciąż przeznaczone dla klienta Czas pokazać klientowi, jak bardzo Ci na nim zależy. Nękają Cię szefowie? Klienci są zmartwieni? Udziałowcy wciąż zadają pytanie: „Czy wszystko będzie zrobione na czas?”. Żadna ilość nawet wspaniale zaprojektowanego kodu nie zadowoli Twoich klientów; musisz pokazać im coś działającego. Teraz, kiedy dysponujesz już solidnym przybornikiem z narzędziami do programowania obiektowego, nadszedł czas, byś udowodnił swoim klientom, że pisane przez Ciebie oprogramowanie naprawdę działa. W tym rozdziale poznasz dwa sposoby pracy nad implementacją możliwości funkcjonalnych tworzonego oprogramowania — dzięki nim Twoi klienci poczują błogie ciepło, które sprawi, że powiedzą o Tobie: „O tak, nie ma co do tego wątpliwości, jest właściwą osobą do napisania naszej aplikacji!”.

Twój przybornik narzędziowy powoli się wypełnia

?XS^ ^cZO$ =^\SXQ Z\YZO\^SO]$ 7KZ

SN$ SX^

XKWO$ =^\SXQ

aOKZYX]$ AOKZYX 

]O^>cZO=^\SXQ QO^>cZO$ =^\SXQ ]O^:\YZO\^c=^\SXQ 9LTOM^ QO^:\YZO\^c=^\SXQ$ 9LTOM^

QO^3N$ SX^

]O^8KWO=^\SXQ

QO^8KWO$ =^\SXQ

KNNAOKZYXAOKZYX

QO^AOKZYX]$ AOKZYX] 

7SZYSTKIEWŽAwCIWOwCI KTÎRES”WSPÎLNEDLA WSZYSTKICHJEDNOSTEK ZOSTAŽYPRZEDSTAWIONE WKLASIE5NITWFORMIE ODRÅBNYCHZMIENNYCH

3ZYMEKDOSZEDŽ DOWNIOSKU šE IDENTYFIKATOR JEDNOSTKI BÅDZIEOKREwLANY WKONSTRUKTORZEKLASY 5NIT AZATEMNIEMA POTRZEBYDEFINIOWANIA ODRÅBNEJMETODYSET)D 

$LAKAšDEJZNOWYCH WŽAwCIWOwCIKLASY 3ZYMEKZDEFINIOWAŽ ODPOWIEDNI”PARÅMETOD

442

Wspaniałe oprogramowanie tworzy się iteracyjnie

444

Schodzenie w głąb: dwie proste opcje

445

Programowanie w oparciu o możliwości

446

Programowanie w oparciu o przypadki użycia

447

Dwa podejścia do tworzenia oprogramowania

448

Analiza możliwości

452

Pisanie scenariuszy testowych

455

Programowanie w oparciu o testy

458

Podobieństwa po raz wtóry

460

Kładziemy nacisk na podobieństwa

464

Hermetyzujemy wszystko

466

Dopasuj testy do projektu

470

Testy bez tajemnic…

472

Udowodnij klientowi, że wszystko idzie dobrze

478

Jak dotąd używaliśmy programowania w oparciu o kontrakt

480

Tak naprawdę programowanie w oparciu o kontrakt dotyczy zaufania

481

Programowanie defensywne

482

Podziel swoją aplikację na mniejsze fragmenty funkcjonalności

491

Celne spostrzeżenia

493

Przybornik projektanta

496

15

Spis treści

Proces projektowania i analizy obiektowej

10

Scalając to wszystko w jedno Czy dotarliśmy już do celu? Poświęciliśmy sporo czasu i wysiłku, by poznać wiele różnych sposobów pozwalających poprawić jakość tworzonego oprogramowania; teraz jednak nadeszła pora, by połączyć i podsumować wszystkie zdobyte informacje. Na to właśnie czekałeś: mamy zamiar zebrać wszystko, czego się nauczyłeś, i pokazać Ci, że wszystkie te informacje stanowią części jednego procesu, którego możesz wielokrotnie używać, by tworzyć wspaniałe oprogramowanie.

Tworzenie oprogramowania w stylu obiektowym

500

Trans-Obiektów

504

Mapa metra w Obiektowie

506

Lista możliwości

509

Przypadki użycia odpowiadają zastosowaniu, możliwości odpowiadają funkcjonalności

515

A teraz zacznij powtarzać te same czynności

519

Dokładniejsza analiza sposobu reprezentacji sieci metra

521

Używać klasy Line czy też nie używać... oto jest pytanie

530

Najważniejsze sprawy związane z klasą Subway

536

Ochrona własnych klas

539

Czas na przerwę

547

Wróćmy znowu do etapu określania wymagań

549

Koncentruj się na kodzie, a potem na klientach

551

Powtarzanie sprawia, że problemy stają się łatwiejsze

555

Jak wygląda trasa?

560

Samemu sprawdź Przewodnik Komunikacyjny po Obiektowie

564

Ktoś chętny na trzeci cykl prac?

567

Podróż jeszcze nie dobiegła końca…

569

 WRZHJR +HUPHW\]DFMD  RELHN $QDOL]D ¥FLHĝNDDOWHUQDW\ZQD ÈF\ RZDQLD XM W FM N L H QL M ĂF L R ZR LN 5R]PRZD ]NOLHQWHP U OL ]RZ\FKPRĝ =DVDG\ S =DVDG\SURMHNWRZH $UFKLWHNWXUD =HZQÚWU]Q\F]\QQ /LVWD NOXF :]RU]HFSURMHNW RZ\ 6FHQDULXV] :]RU]HFSURMHNWRZ\ .SKQ\KWc

6S]^K

:YNdSK¸ Z\YLVOW_ AcWKQKXSK +XKVSdK NdSONdSXc :\YTOU^ a]^°ZXc 3WZVOWOX^KMTK .Y]^K\MdOXSO LZRĂFL H L WYÒVSaYÄMS Z\dcZKNUœa _ÒcMSK Q H ] U 5R]PRZD ]NOLHQWHP R PRĝO 3RZWö QLH 3RGRELHñVWZD¥FLHĝNDDOWHUQDW\ZQD MD Z RSDUFLX +HUPHW\]DF =HZQÚWU]Q\ 3RZWöU]H /LVWD NOXF]R F]\QQLN 'HOHJRZDQLH ZDQLH R P D Z\FKPRĝOLZR $QDO U J L]D WHNVWRZD 6SöMQRĂÊ 3UR ĂFL ñ /LVWD Z\PDJD ]HQLH 3U 5öĝQLFH =DVDG\SURMHNWRZH RJUDPRZDQLH Z RSDUFLX 3RZWöU R WHVW\ $UFKLWHNWXUD ]HQLH 3RZWöU 6FHQDULXV]

16

Spis treści

Pozostałości

A

Dziesięć najważniejszych tematów (których nie poruszyliśmy) Możesz nam wierzyć albo i nie, ale to jeszcze nie jest koniec. Owszem, wyobraź sobie, że nawet po przeczytaniu tych 600 stron wciąż możesz jeszcze znaleźć tematy, o których nawet nie wspomnieliśmy. Choć dziesięć zagadnień, jakie mamy zamiar przedstawić w tym dodatku, nie zasługuje na wiele więcej niż krótką wzmiankę, to jednak nie chcieliśmy, byś opuszczał Obiektów bez informacji na ich temat. Teraz będziesz miał nieco więcej tematów do rozmów podczas firmowej imprezy z okazji wygrania telewizyjnego quizu Obiektowa Katastrofa… poza tym któż, od czasu do czasu, nie kocha stymulujących rozmów o analizie i projektowaniu? Kiedy już skończymy, pozostanie jeszcze… następny dodatek… no i oczywiście indeks, i może kilka reklam… ale później dotrzesz wreszcie do końca książki. Obiecujemy.

+X^cadY\MO

Z\YTOU^YacMR$

SaSOº]^aOW adY\Mœa +X^cadY\MO ]¦ Z\dOM dKXSK

]^Y]YaKXO D·/ \YdaS¦ ]^KXYaS¦ YXO Md°]^Y KXSO

:YaSXXSÄWc Lc¨ a ]^ ZOaXcMR Z\YLVOWœa

SUK¨ SMR OdZSOMdXO Z_¸KZUS S _X \YdZYdXKaK¨ ^O XSOL

Nr 1. JEST i MA

572

Nr 2. Sposoby zapisu przypadków użycia

574

Nr 3. Antywzorce

577

Nr 4. Karty CRC

578

Nr 5. Metryki

580

Nr 6. Diagramy sekwencji

581

Nr 7. Diagramy stanu

582

Nr 8. Testowania jednostkowe

584

Nr 9. Standardy kodowania i czytelny kod

586

Nr 10. Refaktoryzacja

588

5VK]K$ $OG$OOR 9ZS]$ 2EPREZENTUJEFAKTYCZNEDRZWICZKIDLAPSA3TANOWIINTERFEJS

ZAPEWNIAJ”CYMOšLIWOwÁKORZYSTANIAZURZ”DZEÌSPRZÅTOWYCH KONTROLUJ”CYCHDZIAŽANIEDRZWICZEKDLAPSA

9NZYaSONdSKVXYÄMS$ 8KdaK :WRÎÁUWAGÅ BY ZAPI TUZARÎWNOOPERACJE SAÁ KTÎREDANAKLASAREALIZ SAMODZIELNIE JAK ITE UJE KTÎREWYKONUJEPRZY UšYCIUINNYCHKLAS

A]Zœ¸Z\KMYaXSU

/TWORZENIEDRZWICZEK :AMKNIÅCIEDRZWICZEK

$OWYKONAN NIES” UšYWIA TYCH CZYNNOwCI ANE šADNE OBIEKTY INNE

17

Spis treści

Witamy w Obiektowie

B

Stosowanie języka obiektowego Przygotuj się na zagraniczną wycieczkę. Czas odwiedzić Obiektów — miejsce, gdzie obiekty robią to, co powinny, aplikacje są dobrze hermetyzowane (już wkrótce dowiesz się, co to znaczy), a projekty oprogramowania pozwalają na ich wielokrotne stosowanie i rozbudowę. Musisz jeszcze poznać kilka dodatkowych zagadnień i poszerzyć swoje umiejętności językowe. Nie przejmuj się jednak, nie zajmie Ci to wiele czasu i zanim się obejrzysz, już będziesz rozmawiał w języku obiektowym, jakbyś mieszkał w Obiektowie od wielu lat.

UML i diagramy klas

YKLASY /TOJAKPRZEDSTAWIAM MIEKLAS NA TAK ZWANYMDIAGRA B5-, OSÎ 4O WŽAwNIEW TAKISP NIE POZWALANAPRZEDSTAWIA ACJI SZCZEGΎOWYCHINFORM LIKACJÅ OKLASACH TWORZ”CYCHAP

4OS”ZMIENNESKŽADOWE KLASY+AšDAMAPEWN” NAZWÅ APODWUKROPKU OKREwLONYJESTJEJTYP

4O S” METODYKLASY +AšDA METODAMANAZWÅ ZA NI” WNAWIASACH PODAWANE S” PARAMETRY METODY A NASTÅPNIE PO DWUKROPKU TYPWARTOw CI WYNIKOWEJ

18

S

Skorowidz

591

Dziedziczenie

593

Polimorfizm

595

Hermetyzacja

596

Celne spostrzeżenia

600

4O JESTNAZWAKLASY :AWSZE JESTUMIESZCZANA NA SAMEJ GÎRZEDIAGRAMUIZAPI SYWANA POGRUBION”CZCIONK”

+S\ZVKXO ]ZOON$ SX^ QO^=ZOON$ SX^ ]O^=ZOONSX^

4ALINIAODDZIELAZMIENNE SKŽADOWE KLASYODJEJ METOD

YOBRAZIÁSOBIEOGÎLN” $IAGRAMKLASYPOZWALAW APIERWSZYRZUT U N TRUD EZ YB KLAS POSTAÁ CODANA KLASAROBI OKA MOšNAPOWIEDZIEÁ AMIEMOšNANAWET IAGR 7RAZIEPOTRZEBY WD ERAJ”CYZMIENNESKŽADOWE POMIN”ÁFRAGMENTZAWI šE WPRZEKAZANIU OMO LUB METODY JEwLI TOP  NIEZBÅDNYCHINFORMACJI

603

-DNNRU]\VWDÊ]WHMNVLÈĝNL

Wprowadzenie Nie mogę uwierzyć, że umieścili takie rzeczy w książce o analizie i projektowaniu!

amy na palące W tej części odpowiad orzy UMIEŚCILI aut ego acz „Dl e: ani pyt książce te wszystkie rzeczy w u obiektowym?” ani tow jek pro i o analizie

Jak korzystać z tej książki

Dla kogo jest ta książka? Jeśli możesz odpowiedzieć twierdząco na każde z poniższych pytań:

że poradzić, jeśli Powinieneś sobie tak znasz język C#. a Jav zamiast języka

1

Czy znasz język Java? (Nie musisz być w nim mistrzem).

2

Czy chcesz poznać, zrozumieć, zapamiętać i stosować techniki analizy i projektowania obiektowego w rzeczywistych projektach i dzięki temu pisać lepsze oprogramowanie?

3

Czy wolisz stymulujące rozmowy przy posiłku od suchych i nudnych wykładów akademickich?

to ta książka nadaje się dla Ciebie.

Kto raczej nie powinien sięgać po tę książkę? Jeśli możesz odpowiedzieć twierdząco na którekolwiek z poniższych pytań: 1

Jeszcze w ogóle nie znasz języka Java? (Nie musisz być zaawansowanym programistą i nawet jeśli nie znasz języka Java, a na przykład C#, to prawdopodobnie i tak będziesz rozumiał niemal wszystkie prezentowane przykłady. Prawdopodobnie wystarczy Ci także znajomość języka C++).

2

Jesteś doskonałym projektantem lub programistą poszukującym książki informacyjnej.

3

Boisz się spróbować czegoś innego? Wolałbyś raczej poddać się leczeniu kanałowemu, niż połączyć paski z kratą? Nie wierzysz, że książka techniczna może być poważna, jeśli pojęcia programistyczne będą personifikowane?

to ta książka nie jest przeznaczona dla Ciebie.

[Notatka od działu marketingu: ta książka jest dla każdego, kto ma kartę kredytową].

20

Wprowadzenie

Wprowadzenie

Wiemy, co sobie myślisz „Jakim cudem to może być poważna książka programistyczna?” „Po co te wszystkie obrazki?” „Czy w taki sposób można się czegokolwiek nauczyć?”

Twój mózg myśli, że właśnie TO jest istotn e.

Wiemy także, co sobie myśli Twój mózg. Twój mózg pragnie nowości. Zawsze szuka, przegląda i wyczekuje na coś niezwykłego. W taki sposób został stworzony i to pomaga mu przetrwać. Zatem co Twój mózg robi z tymi wszystkimi rutynowymi, zwyczajnymi, normalnymi informacjami, jakie do niego docierają? Otóż dokłada wszelkich starań, aby nie przeszkadzały w jego najważniejszym zadaniu — zapamiętywaniu rzeczy, które mają istotne znaczenie. Twój mózg nie traci czasu i energii na zapamiętywanie nudnych informacji; one nigdy nie przechodzą przez filtr „to jest w oczywisty sposób całkowicie nieważne”. Jak to się dzieje, że Twój mózg wie, co jest istotne? Wyobraźmy sobie, że jesteś na codziennej przechadzce i nagle przed Tobą staje tygrys; co się wówczas z Tobą dzieje? W dzisiejszych czasach jest mało prawdopodobne, abyś stał się przekąską dla tygrysa. Ale Twój mózg wciąż obserwuje. W końcu, nigdy nic nie wiadomo.

Wspaniale. Pozostało jeszcze jedynie 600 głupich, nudnych i drętwych stron.

Neurony płoną. Emocje szaleją. Adrenalina napływa falami. I właśnie dlatego Twój mózg wie, że...

To musi być ważne! Nie zapominaj o tym! Ale wyobraź sobie, że jesteś w domu albo w bibliotece. Przebywasz w bezpiecznym miejscu — przytulnym i takim, w którym nie ma tygrysów. Uczysz się. Przygotowujesz a, się do egzaminu. Albo poznajesz jakiś trudny problem techniczny, którego Twój mózg uważ że tego nie warto rozwiązanie według szefa powinno zająć Ci tydzień, a najdalej dziesięć dni. zapamiętywać.

Jest tylko jeden drobny problem. Twój mózg stara Ci się pomóc. Próbuje zapewnić, że te w oczywisty sposób nieistotne informacje nie zajmą jego cennych zasobów. Zasobów, które powinny zostać wykorzystane na zapamiętanie naprawdę ważnych rzeczy. Takich jak tygrysy. Takich jak zagrożenie, jakie niesie ze sobą pożar. Takich jak to, że już nigdy w życiu nie powinieneś jeździć na snowboardzie w krótkich spodenkach. Co gorsza, nie ma żadnego sposobu, aby powiedzieć mózgowi: „Hej, mój mózgu, dziękuję ci bardzo, ale niezależnie od tego, jak nudna jest ta książka i jak niewielkie są emocje, jakich aktualnie doznaję, to jednak naprawdę chciałbym zapamiętać wszystkie te informacje”.

jesteś tutaj 

21

Jak korzystać z tej książki

Wyobrażamy sobie, że czytelnik tej książki jest uczniem A zatem chcesz się czegoś nauczyć? W pierwszej kolejności powinieneś więc to poznać, a następnie postarać się tego nie zapomnieć. Nauka nie polega jedynie na „wtłoczeniu” do głowy suchych faktów. Najnowsze badania prowadzone w dziedzinie przyswajania informa cji, neurobiologii i psychologii nauczania dowodzą, że uczenie się wymaga czegoś więcej niż tylko czytania tekstu. My wiemy, co potrafi pobudzić nasze mózgi do działania. Oto niektóre z głównych zasad niniejszej książki: Wyobraź to sobie wizualnie. Rysunki są znacznie łatwiejsze do zapamię tania niż same słowa i sprawiają, że uczenie staje się znacznie bardziej efektywne (studia nad przypominaniem sobie i przekazywaniem informacji wykazują, że użycie rysunków poprawia efektyw ność zapamiętywania o 89%). Poza tym rysunki sprawiają, że informacje stają się znacznie bardziej zrozumiałe. Wystarczy umieścić słowa bezpośrednio na lub w sąsiedz twie rysunku, do którego się odnoszą, a nie na następnej stronie, a prawdopodobieństwo, że osoby uczące się będą w stanie rozwiązać problem, którego te słowa dotyczą, wzrośnie niemal dwukrotnie. /LQH

Stosuj konwersacje i personifikacje. Według najnowszych badań, w  ĵ/LQL D 5DPEDX testach JKDĵ końcowych studenci uzyskiwali wyniki o 40% lepsze, jeśli treść była przekazywana bezpośrednio, w pierwszej osobie i w konwencji rozmowy, a nie w sposób formalny. Zamiast wykładania opowiadaj historyjki. Używaj zwyczajnego języka. Nie traktuj swojej osoby zbyt Bycie metodą poważnie. Kiedy byłbyś bardziej uważny: podczas stymulującej rozmowy przy abstrakcyjną jest do obiedzie czy podczas wykładu? kitu. Twoje życie jest pozbawione treści.

Zmuś uczniów do głębszego zastanowienia się. Innymi słowy, jeśli nie pobudzisz neuronów do aktywnego wysiłku, w Twojej głowie nie zdarzy się nic wielkiego. Czytelnik musi być zmotywowany, zaangażowany, zaciekawiony i podeksc ytowany rozwiązywaniem problemów, wyciąganiem wniosków i zdobywaniem nowej wiedzy. A osiągnięcie tego wszystkiego jest możliwe poprzez stawianie wyzwań , zadawanie ćwiczeń i pytań zmuszających do zastanowienia oraz poprzez Doskonałe zmuszanie do działań, które wymagają zaangażowania obu półkul mózgowych oprogramowanie za każdym i wielu zmysłów. razem? Jakoś trudno mi to

DEVWUDFW

sobie w ogóle wyobrazić!

YRLG URDP



Przykuj — przyciągnij na dłużej — uwagę i zainteresowanie czytelnika. Każdy znalazł się kiedyś w sytuacji, gdy bardzo chciał się

czegoś nauczyć, lecz zasypiał po przeczytaniu pierwszej strony. Mózg zwraca uwagę na rzeczy niezwykłe, interesujące, dziwne, przykuwające wzrok, nieoczekiwane. Jednak poznawanie nowego technicznego zagadnienia wcale nie musi być nudne. Jeśli będzie ono interesujące, Twój mózg przyswoi je sobie znacznie szybciej.

Ta metoda nie ma treści — kodu! I kończy się średnikiem.

Wyzwól emocje. Teraz już wiemy, że zdolności do zapamiętywania informac ji są w znacznej mierze zależne od ich zawartości emocjonalnej. Zapamiętujemy to, na czym nam zależy. Zapamiętujemy w sytuacjach, w których coś odczuwamy. Oczywiście, nie mamy tu na myśli wzrusza jących historii o chłopcu i jego psie. Chodzi nam o emocje takie jak zaskoczenie, ciekawość, radosne podekscytowanie, „a niech to…” i uczucie satysfakcji — „Jestem wielki!” — jakie odczuwamy po poprawn ym rozwiązaniu zagadki, nauczeniu się czegoś, co powszechnie uchodzi za trudne, lub zdaniu sobie sprawy, że znamy więcej szczegółów technicznych niż Robert z działu inżynierii.

22

Wprowadzenie

Wprowadzenie

Metapoznanie: myślenie o myśleniu Jeśli naprawdę chcesz się czegoś nauczyć i jeśli pragniesz się tego nauczyć szybciej i dokładniej, to zwracaj uwagę na to, jak Ci na tym zależy. Myśl o tym, jak myślisz. Poznawaj sposób, w jaki się uczysz. Większość z nas w czasie dorastania nie uczestniczyła w zajęciach z metapoznania albo teorii nauczania. Oczekiwano od nas, że będziemy się uczyć, lecz nie uczono nas, jak mamy to robić.

Zastanawiam się, jak zmusić mózg do zapamiętania tych informacji...

Zakładamy jednak, że jeśli trzymasz w ręku tę książkę, to pragniesz nauczyć się analizy i projektowania obiektowego. I prawdopodobnie nie chcesz na to stracić zbyt wiele czasu. A ponieważ masz zamiar tworzyć oprogramowanie, musisz zapamiętać wszystkie zdobyte informacje. A w tym celu musisz je wcześniej zrozumieć. Aby w jak największym stopniu skorzystać z tej książki bądź z jakiejkolwiek innej, lub z dowolnych prób uczenia się czegokolwiek, musisz wziąć odpowiedzialność za czynności swego mózgu. Myśl o tym, czego się uczysz. Sztuczka polega na tym, aby przekonać mózg, że poznawany materiał jest Naprawdę Ważny. Kluczowy dla Twojego dobrego samopoczucia. Tak ważny jak tygrys stojący naprzeciw Ciebie. W przeciwnym razie będziesz prowadzić nieustającą wojnę z własnym mózgiem, który ze wszystkich sił będzie się starać, aby nowe informacje nie zostały utrwalone.

W jaki zatem sposób zmusić mózg, aby potraktował analizę i projektowanie obiektowe jak głodnego tygrysa? Można to zrobić w sposób powolny i męczący lub szybki i bardziej efektywny. Powolny sposób polega na wielokrotnym powtarzaniu. Oczywiście wiesz, że jesteś w stanie nauczyć się i zapamiętać nawet najnudniejsze zagadnienie, mozolnie je „wkuwając”. Po odpowiedniej liczbie powtórzeń Twój mózg stwierdzi: „Wydaje się, że to nie jest dla niego szczególnie ważne, lecz w kółko to czyta i powtarza, więc przypuszczam, że jakąś wartość to jednak musi mieć”. Szybszy sposób polega na zrobieniu czegokolwiek, co zwiększy aktywność mózgu, zwłaszcza jeśli czynność ta wyzwoli kilka różnych typów aktywności. Wszystkie zagadnienia, o jakich pisaliśmy na poprzedniej stronie, są kluczowymi elementami rozwiązania i udowodniono, że wszystkie z nich potrafią pomóc w zmuszeniu mózgu, aby pracował na Twoją korzyść. Na przykład badania wykazują, że umieszczenie słów na opisywanych rysunkach (a nie w innych miejscach tekstu na stronie, na przykład w nagłówku lub wewnątrz akapitu) sprawia, że mózg stara się zrozumieć relację pomiędzy słowami i rysunkiem, a to zwiększa aktywność neuronów. Większa aktywność neuronów — to z kolei większe szanse, że mózg uzna informacje za warte zainteresowania i, ewentualnie, zapamiętania. Prezentowanie informacji w formie konwersacji pomaga, gdyż ludzie zdają się wykazywać większe zainteresowanie w sytuacjach, gdy uważają, że biorą udział w rozmowie, gdyż oczekuje się od nich, że będą śledzić jej przebieg i brać w niej czynny udział. Zadziwiające jest to, iż mózg zdaje się nie zważać na to, że rozmowa jest prowadzona z książką! Z drugiej strony, jeśli sposób przedstawiania informacji jest formalny i suchy, mózg postrzega to tak samo jak w sytuacji, gdy uczestniczysz w wykładzie na sali pełnej znudzonych słuchaczy. Nie ma wówczas potrzeby wykazywania jakiejkolwiek aktywności. Jednak rysunki i przedstawianie informacji w formie rozmowy to jedynie początek.

jesteś tutaj 

23

Jak korzystać z tej książki

Oto co zrobiliśmy: Zamieściliśmy rysunki, ponieważ Twój mózg zwraca większą uwagę na obrazy niż na tekst. Jeśli chodzi o mózg, to faktycznie jeden obraz jest wart 1024 słów. W sytuacjach gdy pojawiał się zarówno tekst, jak i rysunek, umieszczaliśmy tekst na rysunku, gdyż mózg działa bardziej efektywnie, gdy tekst znajduje się wewnątrz czegoś, co opisuje, niż kiedy jest usytuowany w innym miejscu i stanowi część większego fragmentu tekstu.

:\RGUÚEQLP\ ]PLHQQHHOHPHQW\

WYNOV

i97"p

1D]Z\ ZïDĂFLZRĂFL

.ODVD,QVWUXPHQW6SHF

:DUWRĂFLZïDĂFLZRĂFL

:ïDĂFLZRĂFLLQVWUXPHQWX

Stosowaliśmy powtórzenia, wielokrotnie podając tę samą informację na różne sposoby, i przy wykorzystaniu różnych środków przekazu, oraz odwołując się do różnych zmysłów. Wszystko to po to, aby zwiększyć szanse, że informacja zostanie zakodowana w większej liczbie obszarów Twojego mózgu. Używaliśmy pomysłów i rysunków w nieoczekiwany sposób, ponieważ Twój mózg oczekuje i pragnie emocji, gdyż mózg jest skonstruowany nowości; poza tym staraliśmy się zawrzeć w nich w taki sposób, iż zwraca uwagę na biochemię związaną z emocjami. Prawdopodobieństwo zapamiętania czegoś jest większe, jeśli „to coś” wywoła jakąś reakcję emocjonalną, nawet jeśli to uczucie jest jedynie lekkim rozbawieniem, zaskoczeniem lub zainteresowaniem. Używaliśmy bezpośrednich zwrotów i przekazywaliśmy treści w formie konwersacji, gdyż mózg zwraca większą uwagę, jeśli uważa, że prowadzisz rozmowę, niż gdy jesteś jedynie biernym słuchaczem prezentacji.

2ELHNWRZD. DWDVWURID 8KTLK\NdSOT ZYZ_VK\Xc [_Sd a 9LSOU^YaSO ?XSUKXSO \cdcUK

=¸KaXS

Z\YTOU^KXMS

5YX]^\_UMTO

_ÒcaKXO

a UYNdSO

?^\dcWKXSO

S aSOVYU\Y^XO

_ÒcMSO

8O\aSMK

YZ\YQ\KWYaKXSK









































Zamieściliśmy w książce ponad 80 ćwiczeń, ponieważ mózg uczy się i pamięta więcej, gdy coś robi, niż gdy o czymś czyta. Poza tym podane ćwiczenia stanowią wyzwania, choć nie są przesadnie trudne, gdyż właśnie takie preferuje większość osób. Stosowaliśmy wiele stylów nauczania, gdyż Ty możesz preferować instrukcje opisujące krok po kroku sposób postępowania, ktoś inny analizowanie zagadnienia opisanego w ogólny sposób, a jeszcze inne osoby — przejrzenie przykładowego fragmentu kodu. Podawaliśmy informacje przeznaczone dla obu półkul Twojego mózgu, gdyż im bardziej mózg będzie zaangażowany, tym większe jest prawdopodobieństwo nauczenia się i zapamiętania podawanych informacji i tym dłużej możesz koncentrować się na nauce. Ponieważ angażowanie tylko jednej półkuli mózgu często oznacza, że druga będzie mogła odpocząć, zatem będziesz mógł uczyć się bardziej produktywnie przez dłuższy okres. Dodatkowo zamieszczaliśmy opowiadania i ćwiczenia prezentujące więcej niż jeden punkt widzenia, ponieważ mózg uczy się dokładniej, gdy jest zmuszony do przetwarzania i podawania własnej opinii. Stawialiśmy przed Tobą wyzwania, zarówno poprzez podawanie ćwiczeń, jak i zadając pytania, na które nie zawsze można odpowiedzieć w prosty sposób. Mózg bowiem uczy się i pamięta, gdy musi popracować nad czymś. Jednak dołożyliśmy wszelkich starań, aby zapewnić, że gdy pracujesz, to robisz dokładnie to, co trzeba. Aby ani jeden dendryt nie musiał przetwarzać trudnego przykładu ani analizować tekstu zbyt lapidarnego lub napisanego trudnym żargonem. Personifikowaliśmy tekst. W opowiadaniach, przykładach, rysunkach i wszelkich innych możliwych miejscach tekstu staraliśmy się personifikować tekst, gdyż jesteś osobą, a Twój mózg zwraca większą uwagę na osoby niż na rzeczy. Zastosowaliśmy metodę „80/20 ”. Zakładamy bowiem, że to nie jest książka dla osób, które mają zamiar pisać doktorat na temat projektowania oprogramowania. Zatem nie zajmujemy się w niej wszelkimi możliwymi zagadnieniami, a jedynie tymi, z których faktycznie możesz skorzystać.

24

Wprowadzenie

CELNE SPOSTRZEŻENIA

Pogawędki przy kominku

Wprowadzenie

Zmuś swój mózg do posłuszeństwa Zrobiliśmy zatem wszystko co w naszej mocy. Reszta zależy od Ciebie. Możesz zacząć od poniższych porad. Posłuchaj swojego mózgu i określ, które sprawdzają się w Twoim przypadku, a które nie dają pozytywnych rezultatów. Spróbuj czegoś nowego. Wytnij te porady i przyklej na lod ówce.

1 Zwolnij. Im więcej rozumiesz, tym mniej

5 Pij wodę. Dużo wody.

musisz zapamiętać. Nie ograniczaj się jedynie do czytania. Przerwij na chwilę lekturę i pomyśl. Kiedy znajdziesz w tekście pytanie, nie zaglądaj od razu na stronę odpowiedzi. Wyobraź sobie, że ktoś faktycznie zadaje Ci pytanie. Im bardziej zmusisz swój mózg do myślenia, tym większe będą szanse, że się nauczysz i zapamiętasz dane zagadnienie.

Twój mózg pracuje najlepiej, gdy dostarcza się mu obficie płynów. Odwodnienie obniża zdolność percepcji. Piwo oraz wszelkie inne „mocniejsze” napoje zostaw sobie na fetowanie ukończonego projektu. 6 Rozmawiaj o zdobywanych informacjach.

Na głos. Mówienie aktywuje odmienne obszary mózgu. Jeśli próbujesz coś zrozumieć lub zwiększyć szanse na zapamiętanie informacji na dłużej, powtarzaj je na głos. Jeszcze lepiej — staraj się je na głos komuś wytłumaczyć. W ten sposób nauczysz się szybciej.

2 Wykonuj ćwiczenia. Rób notatki.

Umieszczaliśmy je w tekście, jeśli jednak zrobilibyśmy je za Ciebie, to niczym nie różniłoby się to od sytuacji, w której ktoś za Ciebie wykonywałby ćwiczenia fizyczne. I nie ograniczaj się jedynie do czytania ćwiczeń. Używaj ołówka. Można znaleźć wiele dowodów na to, że fizyczna aktywność podczas nauki może poprawić jej wyniki. 3 Czytaj fragmenty oznaczone jako

„Nie istnieją głupie pytania”. Chodzi tu o wszystkie fragmenty umieszczone z boku tekstu. Nie są to fragmenty opcjonalne — stanowią one część podstawowej zawartości książki! Nie pomijaj ich. 4 Niech lektura tej książki będzie ostatnią

rzeczą, jaką robisz przed pójściem spać. A przynajmniej ostatnią czynnością stanowiącą wyzwanie intelektualne. Pewne elementy procesu uczenia się (a w szczególności przenoszenie informacji do pamięci długotrwałej) następują po odłożeniu książki. Twój mózg potrzebuje trochę czasu dla siebie i musi dodatkowo przetworzyć dostarczone informacje. Jeśli podczas tego czasu koniecznego na wykonanie dodatkowego „przetwarzania” zmusisz go do innej działalności, to część z przyswojonych informacji może zostać utracona.

7

Posłuchaj swojego mózgu. Zwracaj uwagę na to, kiedy Twój mózg staje się przeciążony. Jeśli zauważysz, że zaczynasz czytać pobieżnie i zapominać to, o czym przeczytałeś przed chwilą, to najwyższy czas na zrobienie sobie przerwy.

8 Poczuj coś!

Twój mózg musi wiedzieć, że to, czego się uczysz, ma znaczenie. Z zaangażowaniem śledź zamieszczane w tekście opowiadania. Nadawaj własne tytuły zdjęciom. Zalewanie się łzami ze śmiechu po przeczytaniu głupiego dowcipu i tak jest lepsze od braku jakiejkolwiek reakcji. 9 Zaprojektuj coś!

Zastosuj informacje zdobywane podczas lektury tej książki do stworzenia czegoś nowego, co właśnie projektujesz, lub zmodyfikowania jakiegoś starego projektu. Po prostu zrób coś, co pozwoli Ci zdobyć doświadczenia wykraczające poza ćwiczenia i przykłady prezentowane w tej książce. Wszystko, czego Ci będzie w tym celu potrzeba, to problem do rozwiązania… problem, którego rozwikłanie możesz ulepszyć, stosując prezentowane w książce techniki.

jesteś tutaj 

25

Jak korzystać z tej książki

Ważne uwagi To książka przeznaczona do nauki, a nie encyklopedia. Celowo usunęliśmy wszystko, co mogłoby Ci przeszkadzać w nauce, niezależnie od tego, nad czym właśnie pracujesz. Podczas pierwszej lektury książki należy zaczynać od jej samego początku, gdyż kolejne rozdziały bazują na tym, co widziałeś i czego się dowiedziałeś wcześniej.

Zakładamy, że już znasz język Java. Samo nauczenie Cię języka Java zajęłoby całą książkę (taka pozycja już nawet istnieje — Java. Rusz głową! Wydanie II). W niniejszej książce zdecydowaliśmy się skoncentrować uwagę na zagadnieniach analizy i projektowania, zatem pisaliśmy ją z założeniem, że znasz podstawy języka Java. Jeśli jednak w tekście musiały się pojawić zagadnienia średnio lub bardziej zaawansowane, to wyjaśnialiśmy je dokładnie, tak jakby były dla Ciebie całkowitą nowością. Jeśli dopiero zaczynasz poznawać język Java bądź jeśli znasz język C# lub C++, to gorąco zachęcamy, byś w pierwszej kolejności przeczytał dodatek B, a dopiero potem zajął się główną częścią książki. Znajdziesz w nim informacje, które ułatwią Ci zrozumienie zagadnień opisywanych w tej książce.

Najnowszej wersji języka — Java 5 — używamy tylko w razie konieczności. W języku Java 5.0 wprowadzono wiele nowych możliwości, takich jak typy ogólne, typy parametryczne, typy wyliczeniowe, czy też w końcu — pętla IRUHDFK. Wielu profesjonalnych programistów zaczyna stosować właśnie tę najnowszą wersję Javy, jednak my nie chcieliśmy stosować nowej składni języka w książce poświęconej projektowaniu i analizie. Dlatego też w większości przypadków stosujemy składnię znaną ze wszystkich starszych wersji języka Java. Jedynym wyjątkiem jest rozdział 1., w którym musieliśmy wykorzystać typy wyliczeniowe — nie obawiaj się jednak, wyjaśniliśmy je szczegółowo. Jeśli jeszcze nie miałeś kontaktu z językiem Java 5, to i tak zapewne nie będziesz mieć żadnych problemów ze zrozumieniem przykładów prezentowanych w tej książce. Jeśli jednak już potrafisz się nim posługiwać i korzystasz z niego, to zapewne podczas kompilacji przykładów zobaczysz kilka ostrzeżeń o niekontrolowanych i niebezpiecznych operacjach. Będą one spowodowane faktem, iż nie korzystamy z kolekcji o określonych typach. W razie potrzeby na pewno poradzisz sobie z wprowadzeniem do kodu drobnych modyfikacji, tak by nawet w Javie 5 wszystko było w porządku.

Ćwiczenia SĄ obowiązkowe. Ćwiczenia oraz wszelkie dodatkowe polecenia nie są jedynie dodatkami — stanowią one integralną część podstawowej treści książki. Niektóre z nich zostały umieszczone po to, by pomóc w zapamiętaniu informacji, inne są przydatne w zrozumieniu opisywanego materiału, a jeszcze inne są użyteczne w praktycznym zastosowaniu zdobytej wiedzy.

26

Wprowadzenie

Wprowadzenie

Powtórzenia są celowe i ważne. Jedną z cech, która wyróżnia serię książek Rusz głową!, jest to, iż naprawdę bardzo, bardzo, bardzo zależy nam na tym, abyś wszystko zrozumiał i przyswoił. Chcielibyśmy także, abyś zakończył lekturę tej książki, zapamiętując informacje, które w niej zamieściliśmy. Większość książek o charakterze informacyjnym i encyklopedycznym nie zwraca uwagi na przyswojenie i zapamiętanie informacji, jednak w tej znajdziesz wiele pojęć, które pojawiają się kilka razy. Badania czynności mózgu wykazują, że przeniesienie informacji do pamięci długotrwałej wymaga zazwyczaj minimum trzech „powtórzeń”.

Przykładowe kody są jak najbardziej zwięzłe. Czytelnicy niejednokrotnie zwracali nam uwagę, że przeglądanie 200 wierszy kodu w poszukiwaniu dwóch linijek, które należy zrozumieć, może być frustrujące. W większości przykładów zamieszczonych w tej książce dodatkowy kod, który nie jest bezpośrednio związany z omawianymi zagadnieniami, został w jak największym stopniu skrócony; dzięki temu fragmenty, których musisz się nauczyć, są przejrzyste i proste. Nie należy zatem oczekiwać, że podawane przykłady będą solidne, ani nawet tego, że będą kompletne — zostały one opracowane wyłącznie pod kątem nauki, nie zaś zapewnienia pełnej funkcjonalności. W niektórych przypadkach nie umieszczaliśmy w kodach wszystkich niezbędnych instrukcji LPSRUW, jednak zakładaliśmy, że skoro jesteś programistą używającym Javy, to zapewne będziesz wiedzieć, że klasa $UUD\/LVW należy do pakietu MDYDXWLO. Jeśli importowane klasy nie należą do podstawowego API J2SE, to zamieścimy stosowną informację na ten temat. Wszystkie przykłady prezentowane w niniejszej książce można znaleźć na serwerze FTP wydawnictwa Helion, pod adresem: ftp://ftp.helion.pl/przyklady/anprob.zip. Oprócz tego — chcąc, byś koncentrował się na analizie i poznawaniu kodu — nie umieszczaliśmy klas w pakietach (innymi słowy, wszystkie należą do pakietu domyślnego). Nie zalecamy takiego postępowania w przypadku tworzenia aplikacji produkcyjnych.

Do ćwiczeń z cyklu „Wysil szare komórki” nie podawaliśmy odpowiedzi. Do niektórych w ogóle nie można udzielić jednej dobrej odpowiedzi; w innych przypadkach to doświadczenie, które zdobywasz, rozwiązując te ćwiczenia, ma Ci umożliwić określenie, czy i kiedy podana odpowiedź będzie poprawna. W niektórych ćwiczeniach z tej serii znajdziesz także podpowiedzi, które ułatwią Ci znalezienie rozwiązania.

jesteś tutaj 

27

Zespół recenzentów

Zespół techniczny Hannibal Scipio Ara Yapejian

Chris Austin

Recenzenci techniczni: Pragniemy ogromnie podziękować trójce naszych recenzentów technicznych. Wykryli oni błędy, które umknęły naszej uwadze, informowali nas, kiedy pracowaliśmy zbyt szybko (lub zbyt wolno), a nawet zwracali uwagę, gdy nasze żarty były kiepskiej jakości. W kilku przypadkach udało im się sprawdzić oddawane rozdziały zaledwie w ciągu kilku godzin… nie wiemy jednak, czy to oznacza, że oni naprawdę są niezwykle pomocni, czy też to, że powinni zająć się tematyką niezwiązaną z programowaniem. Rozbawił nas zwłaszcza +DQQLEDO, który po przejrzeniu rozdziału 10. stwierdził, że wielka strzałka procesu OO&D jest „odlotowa”. Dziękujemy Wam, panowie, ta książka nie ujrzałaby światła dziennego bez Waszej ciężkiej pracy.

Kathy Sierra i Bert Bates: Wciąż nie możemy się nadziwić ogromnej wiedzy, jaką %HUW%DWHV ma na temat klifów, a .DWK\ 6LHUUD na temat psów. Nie dziw się, jeśli nie za bardzo wiesz, jaki ma to związek z niniejszą książką, ale po prostu kiedy spotykasz tę parę, to świat staje na głowie; choć w końcu okazuje się, że dzięki ich pomocy wszystko jest znacznie lepsze. Bert i Kathy wykonali ogromną pracę, przeglądając tę książkę niedługo przed jej oddaniem; jesteśmy im za to bardzo wdzięczni. Ich pomoc i porady wciąż mają kluczowe znaczenie dla tej serii.

28

Wprowadzenie

Kathy Sierra Bert Bates

Wprowadzenie

Podziękowania Moim współautorom: Ponieważ to są moje podziękowania, zatem na chwilę muszę zrezygnować z formy „my” i wyrazić szczere podziękowania dla współautorów: 'DYHijD:HVWDi *DU\ijHJR3ROOLFHijD. Żaden z nich nie wiedział, na co się porywa, wyrażając zamiar pracy nad tym projektem, jednak nigdy wcześniej żadna dwójka facetów nie wywarła na mnie takiego wrażenia swoją chęcią udzielania wyjaśnień, obrony, a nawet zmiany własnych opinii oraz głęboką wiedzą na temat projektowania oprogramowania, wymagań, analizy czy... szybów wind. Oni byli po prostu niesamowici; pisali wytrwale aż do ostatniego dnia, a nawet potrafili mnie zrelaksować, kiedy, w kilku sytuacjach, byłem już bliski załamania.

Naszej redaktorce:

Mary O’Brien

Ta książka nigdy nie znalazłaby się w Twoich rękach, gdyby nie upór 0DU\2ij%ULHQ. Sądzę, że w pełni usprawiedliwione będzie stwierdzenie, iż Mary stoczyła więcej bitew i przetarła więcej dróg, byśmy mogli spokojnie pracować, niż w ogóle jesteśmy tego świadomi. Przede wszystkim jednak sprawiła, że był to projekt, który dał nam najwięcej satysfakcji w naszych zawodowych karierach. Szczerze mówiąc, zrugała nas nieraz, ale właśnie tak trzeba było zrobić. Mary nie zdaje sobie sprawy z tego, jak wielki wpływ wywiera na osoby, z którymi współpracuje, ale to dlatego, iż zwykle nie zdradzamy, jak bardzo ją szanujemy i cenimy jej opinie. A zatem, Mary, teraz już wiesz. Gdybyśmy mogli umieścić Twoje nazwisko na okładce, na pewno byśmy to zrobili (zaraz, chwila… mogliśmy!).

W wydawnictwie O’Reilly: Wszystkie książki, nie wyłączając tej, są efektem pracy zespołowej. 0LNH+HQGULFNVRQ oraz /DXULH3HWU\FNL nadzorowali ten projekt na wielu etapach pracy nad nim i wielokrotnie prowadzili burzliwe rozmowy telefoniczne. 6DQGHUV.OHLQIHOG od samego początku zajmował się tym projektem… i jakoś to przeżył; co więcej, wykonał wspaniałą pracę, poprawiając tę książkę. Wszyscy jesteśmy bardzo poruszeni faktem, iż będzie to dopiero pierwsza książka z serii Rusz głową!, nad którą pracował. 0LNH/RXNLGHV dawno temu znalazł Berta i Kathy, a 7LP 2ij5HLOO\ zdecydował się przekształcić ich szalony pomysł w serię książek. Jak zwykle kluczowe znaczenie dla udostępnienia tej książki szerokiemu gronu Czytelników miała .\OH+DUW, a wspaniałe projekty okładek autorstwa (GLHJR)UHHGPDQD wciąż nie przestają nas zadziwiać. Szczególne podziękowania chcieliśmy złożyć /RXLVH%DUU, redaktorce projektu Rusz głową!. Louise spędziła z nami kilka długich 12-, a nawet 14-godzinnych dni pracy, poprawiając grafikę w książce i tworząc wspaniałą mapę sieci metra w Obiektowie, którą Czytelnik znajdzie w rozdziale 10. Lou, Twoja praca sprawiła, że ta książka znacznie lepiej nadaje się do nauki — nigdy nie będziemy Ci w stanie odpowiednio podziękować za wkład, jaki wniosłaś w jej powstanie.

Lou Barr

jesteś tutaj 

29

Specjalne podziękowania

Specjalne podziękowania Pod koniec prac nad niniejszą książką Laura Baldwin, szefowa działu finansów wydawnictwa O’Reilly, przeżyła osobistą tragedię. Bardzo trudno jest znaleźć odpowiednie słowa w takiej sytuacji, zwłaszcza iż pod wieloma względami Laura jest jednym z filarów wydawnictwa. Lauro, myślimy o Tobie i modlimy się za Ciebie i Twoją rodzinę, i życzymy Ci wszystkiego najlepszego. Wiemy, że nie pragnęłabyś niczego więcej niż tego, byśmy podczas Twojej nieobecności pracowali jeszcze lepiej. Niniejsza książka jest świadectwem tego, że Twoje imię często pojawia się w naszych rozmowach. Chcemy Cię wesprzeć i nie zapominamy o Tobie. Twój wpływ na firmę jest ogromny, dlatego całe wydawnictwo, jak i seria Rusz głową! będą znacznie lepsze, kiedy do nas wrócisz. Mamy nadzieję, że będziemy mogli znów wspólnie pracować na pełnych obrotach.

30

Wprowadzenie

'REU]H]DSURMHNWRZDQHDSOLNDFMHVÈVXSHU

Tu się zaczyna wspaniałe oprogramowanie

Naprawdę trudno mi przyjść po tym wszystkim do siebie, ale od kiedy zacząłem stosować analizę i projektowanie obiektowe, stałem się zupełnie innym człowiekiem… mówię ci, zupełnie innym!

A zatem, w jaki sposób w praktyce pisze się wspaniałe oprogramowanie? Zawsze bardzo trudno jest określić, od czego należy zacząć. Czy aplikacja faktycznie robi to, co powinna robić i czego od niej oczekujemy? A co z takimi problemami jak powtarzający się kod — przecież to nie może być dobre ani właściwe rozwiązanie, prawda? Zazwyczaj trudno jest określić, które z wielu problemów należy rozwikłać w pierwszej kolejności, a jednocześnie mieć pewność, że podczas wprowadzania poprawek nie popsujemy innych fragmentów aplikacji. Bez obaw. Po zakończeniu lektury tego rozdziału będziesz już dokładnie wiedział, jak pisać doskonałe oprogramowanie i pewnie podążał w kierunku trwałego poprawienia sposobu tworzenia programów. I w końcu zrozumiesz, dlaczego OOA&D to czteroliterowy skrót (pochodzący od angielskich słów: Object-Oriented Analysis and Design, analiza i projektowanie obiektowe), który Twoja matka chciałaby, byś poznał.

to jest nowy rozdział 

31

Dźwięki drewna i stali

Rock-and-roll jest wieczny! Nie ma nic lepszego niż dźwięki doskonałej gitary w rękach świetnego muzyka, a firma Gitary Ryśka specjalizuje się w wyszukiwaniu doskonałych instrumentów dla wymagających i doświadczonych klientów.

Nie uwierzyłbyś, jaki mamy wybór gitar. Zapraszam, powiedz mi, jaka gitara Cię interesuje, a zapewniam, że znajd ziemy instrument idealnie odpowiadając y Twoim potrzebom i oczekiwan iom!

Poznaj Ryśka, pasjonata i miłośnika gitar, a jednocześnie właściciela ekskluzywnego sklepu z gitarami.

Właśnie kilka miesięcy temu Rysiek porzucił stosowany wcześniej „papierowy” system informacji o gitarach i podjął decyzję o przechowywaniu danych o stanie magazynu i transakcjach w systemie komputerowym. Wynajął w tym celu popularną firmę programistyczną SzybkoIKiepsko Sp. z o.o., która napisała mu odpowiednią aplikację. Rysiek poprosił ich nawet o napisanie nowego narzędzia — programu, który wspomagałby dobieranie gitar dla klientów.

32

Rozdział 1.

Dobrze zaprojektowane aplikacje są super

Nowa elegancka aplikacja Ryśka… Oto aplikacja, którą firma programistyczna napisała dla Ryśka… Jej zespół stworzył system, który całkowicie zastępuje papierowe notatki spisywane przez Ryśka wcześniej i który pomaga mu w odnajdywaniu gitar doskonale spełniających oczekiwania klientów. Oto diagram UML klas, który programiści firmy przedstawili Ryśkowi, by pokazać, co dla niego zrobili: Każda gitara znajdująca się w magazynie sklepu Ryśka jest reprezentowana przez obiekt tej klasy.

Oto cały magazyn sklepu Ryśka, który jednocześnie zapewnia mu możliwość poszukiwania gitar.

Guitar

Oto zmienne dostępne w klasie Guitar.

Oto metody klasy Guitar.

ę Magazyn zawiera list Rysiek imi jak ar, git ch tki wszys . uje pon dys aktualnie

Inventory

serialNumber: String price: double builder: String model: String type: String backWood: String topWood: String getSerialNumber(): String getPrice(): double setPrice(float) getBuilder(): String getModel(): String getType(): String getBackWood(): String getTopWood(): String

guitars: List addGuitar(String, double, String, String, String, String, String) getGuitar(String): Guitar search(Guitar): Guitar Ta metoda pobiera numer seryjny gitary i zwraca reprezentujący ją obiekt.

do informacji Rysiek zdecydował, że należą: arę git dą każ opisujących ent duc pro a, cen numer seryjny, yczna bądź oraz model, typ (elektr k drewna, akustyczna) oraz gatune tał wykonany. zos z jakiego instrument

Ta metoda służy do wyszukiwania gitar; pobiera ona obiekt stanowiący reprezentację gitary idealnie spełniającej wymagania klienta i zwraca wyszukany w magazynie obiekt gitary, która pasuje do zadanych kryteriów.

Do tej metody przekazywane są szczegółowe informacje o gitarze; metoda ta tworzy odpowiedni obiekt Guitar i zapisuje go w magazynie Ryśka.

Przygotowaliśmy dla Ciebie kilka interesujących i ciekawych sma kołyków, znajdziesz je w dodatku B. Jeśl i pierwszy stykasz się z zagadnie po raz niam dotyczącymi projektowania obiektow i ego lub języka UML, to zajrzyj tam koniecznie.

1RZ\Z2ELHNWRZLH" Jeśli nie spotkałeś się wcześniej z projektowaniem obiektowym, nie słyszałeś o diagramach UML bądź też nie jesteś pewny, czy dobrze rozumiesz znaczenie diagramów przedstawionych na powyższym rysunku, nie przejmuj się! Przygotowaliśmy specjalny pakiet ratunkowy „Witamy w Obiektowie”, który pomoże Ci wszystko zrozumieć. Zajrzyj na sam koniec książki i przeczytaj dodatek B — gwarantujemy Ci, że nie będziesz żałował. Kiedy skończysz, ponownie przeczytaj tę stronę, a na pewno wszystko nabierze zupełnie nowego sensu.

jesteś tutaj 

33

Początkowy kod aplikacji Ryśka

Oto jak wygląda kod Guitar.java Na poprzedniej stronie przedstawiliśmy diagramy klas tworzących aplikację Ryśka; teraz nadszedł czas, abyś zobaczył, jak wygląda faktyczny kod źródłowy klas *XLWDU i ,QYHQWRU\ (umieszczony odpowiednio w plikach *XLWDUMDYD oraz ,QYHQWRU\MDYD). SXEOLFFODVV*XLWDU^ SULYDWH6WULQJVHULDO1XPEHUEXLOGHUPRGHOW\SHEDFN:RRGWRS:RRG To wszystko są właściwości, które SULYDWHGRXEOHSULFH niej widzieliśmy już wcześ itar. SXEOLF*XLWDU 6WULQJVHULDO1XPEHUGRXEOHSULFH Gu sy kla mie gra na dia 6WULQJEXLOGHU6WULQJPRGHO6WULQJW\SH 6WULQJEDFN:RRG6WULQJWRS:RRG ^ WKLVVHULDO1XPEHU VHULDO1XPEHU WKLVSULFH SULFH WKLVEXLOGHU EXLOGHU Na diagramach UML nie są umieszczane konstruktory klas; konstruktor klasy Guitar robi WKLVPRGHO PRGHO dokładnie to, czego można od niego oczekiwać: WKLVW\SH W\SH określa początkowe wartości właściwości nowego WKLVEDFN:RRG EDFN:RRG obiektu Guitar. WKLVWRS:RRG WRS:RRG ` SXEOLF6WULQJJHW6HULDO1XPEHU ^ UHWXUQVHULDO1XPEHU ` SXEOLFGRXEOHJHW3ULFH ^ UHWXUQSULFH ` SXEOLFYRLGVHW3ULFH IORDWQHZ3ULFH ^ WKLVSULFH QHZ3ULFH ` SXEOLF6WULQJJHW%XLOGHU ^ Łatwo zauważyć, UHWXUQEXLOGHU jak diagram klas ` odpowiada metodom, SXEOLF6WULQJJHW0RGHO ^ które możemy znaleźć w kodzie źródłowym UHWXUQPRGHO klasy Guitar ` SXEOLF6WULQJJHW7\SH ^ UHWXUQW\SH ` SXEOLF6WULQJJHW%DFN:RRG ^ UHWXUQEDFN:RRG ` SXEOLF6WULQJJHW7RS:RRG ^ UHWXUQWRS:RRG ` `

Guitar serialNumber: String price: double builder: String model: String type: String backWood: String topWood: String getSerialNumber(): String getPrice(): double setPrice(float) getBuilder(): String getModel(): String getType(): String getBackWood(): String FODVV *XLWDU^ getTopWood(): String *XL WDU `

Guitar.java

34

Rozdział 1.

Dobrze zaprojektowane aplikacje są super

Plik Inventory.java… iśmy instrukcje

Pamiętaj, że usunęl SXEOLFFODVV,QYHQWRU\^ nieco miejsca. import, by zaoszczędzić SULYDWH/LVWJXLWDUV SXEOLF,QYHQWRU\ ^ JXLWDUV QHZ/LQNHG/LVW  ` SXEOLFYRLGDGG*XLWDU 6WULQJVHULDO1XPEHUGRXEOHSULFH 6WULQJEXLOGHU6WULQJPRGHO 6WULQJW\SH6WULQJEDFN:RRG6WULQJWRS:RRG  ^ *XLWDUJXLWDU QHZ*XLWDU VHULDO1XPEHUSULFHEXLOGHU PRGHOW\SHEDFN:RRGWRS:RRG  Metoda addGuitar() pob JXLWDUVDGG JXLWDU  wszystkie informacje iera ` utworzenia nowego obikonieczne do Guitar i dodaje go do ektu typu  magazynu. SXEOLF*XLWDUJHW*XLWDU 6WULQJVHULDO1XPEHU ^ IRU ,WHUDWRUL JXLWDUVLWHUDWRU LKDV1H[W  ^ *XLWDUJXLWDU  *XLWDU LQH[W  LI JXLWDUJHW6HULDO1XPEHU HTXDOV VHULDO1XPEHU ^ UHWXUQJXLWDU ` ` UHWXUQQXOO plikowana… Ta metoda jest nieco bardziej skomści obiektu ` porównuje ona wszystkie właściwo iu, ołan SXEOLF*XLWDUVHDUFK *XLWDUVHDUFK*XLWDU ^ Guitar, przekazanego w jej wyw któw tego typu, obie h stkic wszy mi IRU ,WHUDWRUL JXLWDUVLWHUDWRU LKDV1H[W  ^ ścia ciwo właś z *XLWDUJXLWDU  *XLWDU LQH[W  dostępnych w magazynie Ryśka. ,JQRUXMHP\QXPHUVHU\MQ\ERMHVWXQLNDOQ\ ,JQRUXMHP\FHQÚJG\ĝMHVWXQLNDOQD 6WULQJEXLOGHU VHDUFK*XLWDUJHW%XLOGHU  LI EXLOGHU QXOO   EXLOGHUHTXDOV Ĵĵ   EXLOGHUHTXDOV JXLWDUJHW%XLOGHU FRQWLQXH 6WULQJPRGHO VHDUFK*XLWDUJHW0RGHO  LI PRGHO QXOO   PRGHOHTXDOV Ĵĵ   PRGHOHTXDOV JXLWDUJHW0RGHO FRQWLQXH 6WULQJW\SH VHDUFK*XLWDUJHW7\SH  LI W\SH QXOO   W\SHHTXDOV Ĵĵ   W\SHHTXDOV JXLWDUJHW7\SH FRQWLQXH 6WULQJEDFN:RRG VHDUFK*XLWDUJHW%DFN:RRG  LI EDFN:RRG QXOO   EDFN:RRGHTXDOV Ĵĵ  Inventory  EDFN:RRGHTXDOV JXLWDUJHW%DFN:RRG FRQWLQXH guitar: List 6WULQJWRS:RRG VHDUFK*XLWDUJHW7RS:RRG  addGuitar(String, double, String, String, String, LI WRS:RRG QXOO   WRS:RRGHTXDOV Ĵĵ   WRS:RRGHTXDOV JXLWDUJHW7RS:RRG String, String) FRQWLQXH FODVV getGuitar(String): Guitar ,QYHQ UHWXUQJXLWDU WRU\^ search(Guitar): Guitar ` VHDUFK UHWXUQQXOO ` Inventory.java `

jesteś tutaj 

35

Problem brakującej gitary

Wkrótce jednak okazało się, że Rysiek zaczął tracić klientów… Wygląda na to, że niezależnie od tego, kim jest klient i jakiej gitary szuka, nowy program wyszukujący gitary prawie nigdy nie jest w stanie dopasować odpowiedniego instrumentu. Jednak Rysiek doskonale wie, że posiada gitarę, która na pewno spodobałaby się danemu klientowi… Zatem gdzie tkwi problem? Program FindGuitarTester.java symuluje typowy dzień pracy Ryśka… Zjawia się klient, mówi Ryśkowi, jaka gitara by go interesowała, a Rysiek przeszukuje swój magazyn.

SXEOLFFODVV)LQG*XLWDU7HVWHU^ SXEOLFVWDWLFYRLGPDLQ 6WULQJ>@DUJV ^ ,QLFMDOL]DFMD]DZDUWRĂFLPDJD]\QXJLWDU5\ĂND ,QYHQWRU\LQYHQWRU\ QHZ,QYHQWRU\  LQLWLDOL]H,QYHQWRU\ LQYHQWRU\ 

tary Ewa szuka gi Fender, y m fir ” at tr „S całości wykonanej w owego. ch ol na z drew

*XLWDUZKDW(YH/LNHV QHZ*XLWDU ĴĵĴIHQGHUĵĴ6WUDWRFDVWRUĵ ĴHOHNWU\F]QDĵĴROFKDĵĴROFKDĵ  *XLWDUJXLWDU LQYHQWRU\VHDUFK ZKDW(YH/LNHV  LI JXLWDU QXOO ^ 6\VWHPRXWSULQWOQ Ĵ(ZRPRĝHVSRGRED&LVLÚJLWDUDĴ JXLWDUJHW%XLOGHU ĴPRGHOĴJXLWDUJHW0RGHO ĴĴ JXLWDUJHW7\SH Ĵ?QĴ JXLWDUJHW%DFN:RRG ĴW\ïLERNL?QĴ JXLWDUJHW7RS:RRG ĴJöUD?Q0RĝHV]MÈPLHÊ]DĴ JXLWDUJHW3ULFH Ĵ3/1ĵ  `HOVH^ 6\VWHPRXWSULQWOQ Ĵ3U]\NURPL(ZRQLH]QDOD]ïHPQLFGOD&LHELHĵ  ` ` SULYDWHVWDWLFYRLGLQLWLDOL]H,QYHQWRU\ ,QYHQWRU\LQYHQWRU\ ^  ` FODVV `

)LQJ*XL WDU^ PDLQ `

FindGuitarTester.java

Przykro mi, Ryśku, ale wygląda na to, że pójdę do innego sklepu.

zie do Oto co się dzieje, gdy Ewa wejd leźć sklepu Ryśka, a ten spróbuje odna gitarę spełniającą jej oczekiwania.

36

Rozdział 1.

Dobrze zaprojektowane aplikacje są super

Ale przecież ja wiem, że świetną gitarę Stratocas mam tor fir Fender. Jest właśnie tut my aj:

LQYHQWRU\DGG*XLWDU ĵ9ĵ ĵ)HQGHUĵĵ6WUDWRFDVWRUĵ ĵHOHNWU\F]QDĵĵROFKDĵĵROFKDĵ 

Oto fragment kodu inicjalizującego zawartość magazynu Ryśka. Wygląda na to, że Rysiek faktycznie ma gitarę, która spełnia oczekiwania Ewy.

Zaostrz ołówek

Wydaje się, że specyfikacja gitary dokładnie odpowiada oczekiwaniom Ewy… Zatem co się stało?

W jaki sposób zmieniłbyś projekt aplikacji Ryśka? Przeanalizuj kody aplikacji Ryśka, przedstawione na trzech poprzednich stronach, oraz wyniki wykonanego testu. Jakie problemy udało Ci się zauważyć? Co byś zmienił? Poniżej zapisz PIERWSZĄ rzecz, jaką chciałbyś poprawić w aplikacji Ryśka.

jesteś tutaj 

37

Jak pisać wspaniałe programy?

Co przede wszystkim zmieniłbyś w aplikacji Ryśka? Oczywiste jest, że w aplikacji Ryśka występuje jakiś problem; jednak znacznie trudniej jest określić, od czego należy zacząć wprowadzanie poprawek. I wygląda na to, że istnieje wiele różnych opinii na ten temat:

Hm… w swoich notatkach właściciel firmy wyraźnie pisze, że chciałby, żeby klienci mieli możliwość wyboru wielu instrumentów. Czy zatem metoda search() nie powinna zwracać listy obiektów?

Spójrzcie na te wszystkie łańcuchy znaków! To jest po prostu okropne… czy zamiast nich nie możemy użyć stałych lub obiektów?

Guitar serialNumber: String price: double builder: String model: String type: String backWood: String topWood: String getSerialNumber(): String getPrice(): double setPrice(float) getBuilder(): String getModel(): String getType(): String getBackWood(): String getTopWood(): String

Jerzy zajmuje się programowaniem stosunkowo krótko, jednak szczerze i głęboko wierzy w słuszność pisania kodu obiektowego. Franek zajmuje się programowan iem już od jakiegoś czasu i naprawd ę dobrze zna zasady projektowania obiektowego i wzorce projektowe.

38

Rozdział 1.

Inventory guitar: List addGuitar(String, double, String, String, String, String, String) getGuitar(String): Guitar search(Guitar): Guitar

Ten projektant musi być naprawdę kiepski. Klasy Guitar oraz Inventory w zbyt dużym stopniu zależą od siebie nawzajem. Nie potrafię sobie wyobrazić, by to była architektura, na której mógłby bazować rozwój aplikacji. Musimy ją jakoś zmienić.

Julka zyskała reputację osoby, która zawsze oferuje klientom to, czego chcą.

Co zrobiłbyś w pierwszej kolejności?

Dobrze zaprojektowane aplikacje są super

Niby skąd mam wiedzieć, od czego należy zacząć? Mam wrażenie, że ilekroć zaczynam pracę nad nowym projektem, każdy ma inne zdanie odnośnie tego, co należy zrobić w pierwszej kolejności. Czasami zrobię coś dobrze, lecz czasami kończy się na tym, że muszę przerobić całą aplikację od początku, bo zacząłem w złym miejscu. A ja chcę jedynie pisać świetne oprogramowanie! A zatem, od czego powinienem zacząć pisanie aplikacji dla Ryśka?

W jaki sposób można za każdym razem pisać dobre oprogramowanie?

jesteś tutaj 

39

Ale co to znaczy „doskonałe oprogramowanie”?

Chwileczkę… nie lubię się wtrącać, ale co to właściwie oznacza „wspaniałe oprogramowanie"? To jakieś tajemnicze hasło, które rzuca się w rozmowach, by zrobić odpowiednie wrażenie? Dobre pytanie… na które można podać wiele różnych odpowiedzi:

3URJUDPLVWDGEDMÈF\RGREURNOLHQWDRGSRZLH „Doskonałe oprogramowanie zawsze robi to, czego chce klient. A zatem, nawet jeśli klient wymyśli nowy sposób zastosowania takiego oprogramowania, to nie będzie ono działać niewłaściwie ani zwracać nieoczekiwanych wyników”. Podstawą tego podejścia jest zwrócenie największej uwagi na to, by użytkownik był zadowolony z działania aplikacji.

3URJUDPLVWDRELHNWRZ\RGSRZLH 1_S^K\ ]O\SKV8_WLO\$ =^\SXQ Z\SMO$ NY_LVO ]ZOM$ 1_S^K\=ZOM QO^=O\SKV8_WLO\$ =^\SXQ QO^:\SMO$ NY_LVO ]O^:\SMOPVYK^ QO^=ZOM$ 1_S^K\=ZOM

1_S^K\=ZOM

3X`OX^Y\c

L_SVNO\$ ,_SVNO\ WYNOV$ =^\SXQ ^cZO$ >cZO LKMUAYYN$ AYYN ^YZAYYN$ AYYN

Q_S^K\$ 6S]^ KNN1_S^K\=^\SXQ NY_LVO ,_SVNO\ =^\SXQ >cZO













AYYN AYYN QO^1_S^K\=^\SXQ$ 1_S^K\ ]OK\MR1_S^K\=ZOM$ 6S]^

QO^,_SVNO\$ ,_SVNO\ QO^7YNOV$ =^\SXQ QO^>cZO$ >cZO QO^,KMUAYYN$ AYYN QO^>YZAYYN$ AYYN

,_SVNO\ ^Y=^\SXQ$ =^\SXQ

>cZO

^Y=^\SXQ$ =^\SXQ AYYN ^Y=^\SXQ$ =^\SXQ

„Doskonałe oprogramowanie to kod napisany obiektowo. Jest to zatem kod, w którym nie ma żadnych powtórzeń oraz w którym obiekty w bardzo dużym stopniu kontrolują swoje własne zachowanie. Takie oprogramowanie także łatwo rozbudować, gdyż jego projekt jest solidny i elastyczny”.

ze Dobrzy programiści obiektowi zaws ich by to, na obów spos poszukują kod był jak najbardziej elastyczny.

Nie jesteś całkiem pewny, co to wszystko znaczy? Nie martw się… wszystkiego dowiesz się w kole jnych rozdziałach.

3URJUDPLVWDEÚGÈF\DXWRU\WHWHPZG]LHG]LQLHSURMHNWRZDQLDRGSRZLH „Oprogramowanie będzie doskonałe, jeśli podczas jego tworzenia zastosujemy wypróbowane i sprawdzone wzorce projektowe i zasady projektowania. Zachowałeś luźne powiązania pomiędzy obiektami i sprawiłeś, by kod był otwarty na rozszerzanie, lecz zamknięty na modyfikacje. To także powoduje, że łatwiejsze będzie wielokrotne wykorzystanie kodu, a dzięki temu nie będziesz go musiał przerabiać, chcąc wykorzystać fragmenty aplikacji w innych projektach”.

40

Rozdział 1.

To podejście, koncentrujące uwagę na zagadnieniach projektu, pozwala na tworzenie kodu zoptymalizowanego pod kątem przyszłego rozwoju aplikacji i wielokrotnego używania jej elementów. Wykorzystuje ono wzorce projektowe oraz wielokrotnie sprawdzone techniki projektowania i programowania obiektowego.

Dobrze zaprojektowane aplikacje są super

Zaostrz ołówek

A co dla Ciebie znaczy termin „doskonałe oprogramowanie”? Dowiedziałeś się już, co termin „doskonałe oprogramowanie” oznacza dla kilku różnych typów programistów. Zatem kto z nich ma rację? A może masz swoją własną definicję, która określa, co sprawia, że tworzona aplikacja będzie doskonała? Jeśli tak, to nadszedł czas, byś napisał własną definicję doskonałego oprogramowania:

… oje personalia Tu zapisz sw

...a tu podaj co wedłu termin „doskonałe opr g Ciebie oznacza ogramowanie”:

XZDĝDĝH ķ

ĵ

jesteś tutaj 

41

Doskonałe oprogramowanie spełnia potrzeby zarówno klienta, jak i programisty

Doskonałe oprogramowanie… ma więcej niż jedną z wymienianych już cech Nie sposób określić, czym jest „doskonałe oprogramowanie”, przy użyciu jednej prostej definicji. W rzeczywistości wszystkie stwierdzenia różnych programistów dotyczące dobrego oprogramowania, podane na stronie 40, określają cechy, dzięki którym oprogramowanie można uznać za „doskonałe”.

Przede wszystkim doskonałe oprogramowanie musi spełniać wymagania i oczekiwania klienta. Oprogramowanie musi robić to, czego klient od niego oczekuje.

Podbij swoich klientów. Klienci uznają Twoje oprogramowanie za doskonałe, jeśli będzie ono robić to, czego od niego oczekują.

Tworzenie oprogramowania, które działa we właściwy sposób, jest czymś wspaniałym. Co się jednak stanie w sytuacjach, kiedy takie oprogramowanie trzeba będzie rozbudować lub zastosować jego część w innym projekcie? Napisanie kodu, który działa zgodnie z oczekiwaniami klienta, to za mało; równie ważne jest to, by przeszedł on próbę czasu.

Poza tym, doskonałe oprogramowanie powinno być dobrze zaprojektowane, poprawnie napisane oraz musi zapewniać łatwość utrzymania, wielokrotnego stosowania i rozszerzania. Niech Twój kod będzie tak inteligentny jak Ty sam. Zarówno Ty, jak i Twoi współpracownicy sami uznacie, że oprogramowanie jest doskonałe, jeśli jego utrzymanie, rozszerzanie i wielokrotne stosowanie nie będą przysparzać większych problemów.

42

Rozdział 1.

O rany, jeśli mój kod naprawdę mógłby mieć te wszystkie cechy, to pisane przeze mnie aplikacje byłyby wspaniałe! Potrafię nawet zapisać te wszystkie wymagania w kilku prostych punktach, które można by stosować we wszystkich projektach.

Dobrze zaprojektowane aplikacje są super

Wspaniałe oprogramowanie w trzech prostych krokach Obecnie może Ci się wydawać, że to wcale nie jest takie łatwe. Jednak pokażemy, że analiza i projektowanie obiektowe, wraz z kilkoma prostymi zasadami, mogą na zawsze zmienić postać tworzonego przez Ciebie oprogramowania.

1. Upewnij się, że oprogramowanie robi to, czego oczekuje klient.

e. jemy uwagę na klienci W tym kroku koncentru ŚCI powinieneś zapewnić, NO W PIERWSZEJ KOLEJ ić to, czego klient od niej że aplikacja będzie rob prac dużą rolę odgrywa pie oczekuje. Na tym eta i przeprowadzenie przygotowanie wymagań . lizy ana odpowiedniej

2. Zastosuj proste zasady projektowania obiektowego, by poprawić elastyczność oprogramowania. Kiedy oprogramowanie możesz odszukać w nimbędzie już działać, powtarzające się fragm i usunąć upewnić się, że zastos enty kodu oraz projektowania obiektow owałeś dobre techniki ego.

Czy uzyskałeś dobrą aplikację obiektową, która robi to, co powinna? Nadszedł czas, by zastosować wzorce i zasady, dzięki którym upewnisz się, że Twoje oprogramowanie jest odpowiednio przygotowane do wieloletniego użytkowania.

3. Staraj się, by projekt oprogramowania zapewniał łatwość jego utrzymania i pozwalał na jego wielokrotne stosowanie.

jesteś tutaj 

43

Stosowanie przedstawionych wcześniej kroków

Pamiętasz Ryśka? Pamiętasz klientów, których stracił? Wypróbujmy nasze pomysły i założenie dotyczące tworzenia doskonałego oprogramowania i przekonajmy się, czy nadają się one do zastosowania w realnym świecie. Rysiek dostał program do wyszukiwania gitar, który nie działa poprawnie, i to Twoim zadaniem będzie jego poprawienie i dołożenie wszelkich starań, by stworzyć naprawdę wspaniałą aplikację. Przyjrzyjmy się jeszcze raz programowi, jakim obecnie dysponuje Rysiek, i zobaczmy, w czym tkwi problem: Oto nasz program testowy, który uwidocznił problemy w działaniu narzędzia do wyszukiwania gitar pasujących do kryteriów podanych przez użytkownika.

SXEOLFFODVV)LQG*XLWDU7HVWHU^ SXEOLFVWDWLFYRLGPDLQ 6WULQJ>@DUJV ^ ,QLFMDOL]DFMDPDJD]\QXJLWDU5\ĂND ,QYHQWRU\LQYHQWRU\ QHZ,QYHQWRU\ 

Aplikacja Ryśk powinna dopa a te wymaganiasować …

LQLWLDOL]H,QYHQWRU\ LQYHQWRU\  *XLWDUZKDW(YH/LNHV QHZ*XLWDU ĵĵĵIHQGHUĵĵ6WUDWRFDVWRUĵ ĵHOHNWU\F]QDĵĵROFKDĵĵROFKDĵ 

...do tej gitary znajdującej się w magazynie.

*XLWDUJXLWDU LQYHQWRU\VHDUFK ZKDW(YH/LNHV  LI JXLWDU QXOO ^ LQYHQWRU\DGG*XLWDU ĵ9ĵ ĵ)HQGHUĵĵ6WUDWRFDVWRUĵ ĵHOHNWU\F]QDĵĵROFKDĵĵROFKDĵ 

FODVV )LQJ*XL WDU^ PDLQ `

FindGuitarTester.java

A zatem wykonajmy nasze trzy podane wcześniej kroki: 1. Upewnij się, że oprogramowanie robi to, czego oczekuje klient. Pamiętaj, że musimy , by zacząć od zapewnienia go aplikacja robiła to, cze nie oczekuje Rysiek… a cna ma wątpliwości, że obe o teg aplikacja nie spełnia warunku.

2. Zastosuj proste zasady projektowania obiektowego, by poprawić elastyczność oprogramowania.

Na razie nie zawr głowy stosowaniemacaj sobie wzorców ani obiek w aplikacji technik programo towych skoncentruj się wania… na tym, by działała tak, jak powinna.

3. Staraj się, by projekt oprogramowania zapewniał łatwość jego utrzymania i pozwalał na jego wielokrotne stosowanie. 44

Rozdział 1.

Dobrze zaprojektowane aplikacje są super

Skoro zaczynamy od funkcjonalności, to sprawdźmy, co się dzieje z tą niedziałającą metodą search(). Wygląda na to, że w magazynie Ryśka nazwa producenta jest zapisana małymi literami, a w wymaganiach klienta nazwa „Fender" zaczyna się z wielkiej litery. A zatem w metodzie search() musimy zastosować wyszukiwanie, które nie będzie uwzględniać wielkości liter.

Skorzystajmy z drobnej pomocy naszych znajomy ch programistów.

)UDQHNOczywiście, to by rozwiązało bieżące problemy Ryśka, niemniej jednak uważam, że istnieje lepszy sposób zapewnienia poprawnego działania aplikacji niż wywoływanie metody WR/RZHU&DVH we wszystkich miejscach, gdzie są porównywane łańcuchy znaków. -HU]\ Właśnie, o tym samym pomyślałem. Wydaje mi się, że to całe porównywanie łańcuchów znaków to nie jest najlepszy pomysł. Czy nie moglibyśmy zastosować jakichś stałych lub jakiegoś typu wyliczeniowego do określania producentów gitar oraz gatunków drewna? -XOND Panowie, wybiegacie myślami zdecydowanie zbyt daleko. Krok 1. miał polegać na poprawieniu aplikacji w taki sposób, by robiła to, czego od niej oczekuje klient. Sądziłam, że na tym etapie prac nie będziemy zajmowali się zagadnieniami związanymi z projektem. )UDQHN Cóż, to prawda; rozumiem, że mamy się skoncentrować na kliencie. Ale możemy przynajmniej zastanowić się, jak inteligentnie rozwiązać aktualnie występujące problemy, nieprawdaż? Chodzi mi o to, by nie tworzyć kolejnych problemów, które w przyszłości znowu będziemy musieli rozwiązywać.

Franek

Julka

Jerzy

-XOND Hmm… tak, całkiem słusznie. Na pewno nie chcemy, by zaproponowane przez nas rozwiązanie bieżących problemów powodowało pojawienie się nowych problemów projektowych. Jednak pomimo to na razie nie będziemy zajmowali się innymi fragmentami aplikacji, dobra? )UDQHN Dobra. Możemy ograniczyć się do usunięcia tych wszystkich łańcuchów znaków, porównywania łańcuchów znaków i problemów związanych z wielkościami liter. -HU]\ Właśnie. Jeśli zastosujemy typy wyliczeniowe, to zyskamy pewność, że podczas określania producenta gitary, typu drewna oraz rodzaju instrumentu będą używane wyłącznie prawidłowe wartości. W ten sposób zagwarantujemy, że Rysiek będzie mógł znajdować gitary pasujące do wymagań i oczekiwań klientów.

Rozwiązując istniejące problemy, nie twórz nowych.

-XOND A jednocześnie poprawimy nieco projekt samej aplikacji… super! A zatem — do roboty, panowie.

jesteś tutaj 

45

Krok 1: Zaspokojenie potrzeb klienta

Eliminacja porównywania łańcuchów znaków Pierwszą poprawką, jaką możemy wprowadzić w aplikacji Ryśka, jest usunięcie porównywania łańcuchów znaków. Choć można by zastosować metodę WR/RZHU&DVH , by rozwiązać problem z porównywaniem liter różnych wielkości, to jednak postaramy się w ogóle wyeliminować porównywanie łańcuchów znaków:

SXEOLFHQXP7\SH^ HQXP 7\SH^ WR 6WULQJ `

$&2867,&(/(&75,&

Każdy z typów wyliczeniowych zastąpi jedną z właściwości klasy Guitar, która jest standardowa dla wszystkich jej obiektów.

SXEOLF6WULQJWR6WULQJ ^ VZLWFK WKLV ^ FDVH$&2867,&UHWXUQĵDNXVW\F]QDĵ

To wszys tk wyliczenio o są Javy — e we typy działają p num — które stałych. odobnie do

Type.java

FDVH(/(&75,&UHWXUQĵHOHNWU\F]QDĵ SXEOLFHQXP%XLOGHU^ GHIDXOWUHWXUQĵQLHRNUHĂORQDĵ )(1'(50$57,1*,%621&2//,1*62/6215@DUJV ^ ,QLFMDOL]DFMD]DZDUWRĂFLPDJD]\QXJLWDU5\ĂND ,QYHQWRU\LQYHQWRU\ QHZ,QYHQWRU\  LQLWLDOL]H,QYHQWRU\ LQYHQWRU\ 

Tym razem klient przekazuje do metody search()*XLWDU6SHFZKDW(YH/LNHV  obiekt klasy GuitarSpec. QHZ*XLWDU6SHF %XLOGHU)(1'(5Ĵ6WUDWRFDVWRUĵ

7\SH(/(&75,&:RRG$/'(5:RRG$/'(5  /LVWPDWFKLQJ*XLWDUV LQYHQWRU\VHDUFK ZKDW(YH/LNHV  LI PDWFKLQJ*XLWDUVLV(PSW\ ^ 6\VWHPRXWSULQWOQ Ĵ(ZRPRĝHVSRGREDMÈ&LVLÚQDVWÚSXMÈFHJLWDU\ĵ IRU ,WHUDWRUL PDWFKLQJ*XLWDUVLWHUDWRU LKDV1H[W  ^ *XLWDUJXLWDU  *XLWDU LQH[W  *XLWDU6SHFVSHF JXLWDUJHW6SHF  Także tutaj 6\VWHPRXWSULQWOQ Ĵ0DP\ZPDJD]\QLHJLWDUÚĴ używamy nowej klasy VSHFJHW%XLOGHU ĴPRGHOĴVSHFJHW0RGHO Ĵ GuitarSpec. MHVWĴĴWRJLWDUDĵVSHFJHW7\SH Ĵ?QĴ VSHFJHW%DFN:RRG ĴW\ïLERNL?QĴ VSHFJHW7RS:RRG ĴJöUD?Q0RĝHV]MÈPLHÊ]DĴ JXLWDUJHW3ULFH Ĵ3/1?Qĵ  ` `HOVH^ 6\VWHPRXWSULQWOQ Ĵ3U]\NURPL(ZRQLH]QDOD]ïHPQLFGOD&LHELHĵ  ` ` SULYDWHVWDWLFYRLGLQLWLDOL]H,QYHQWRU\ ,QYHQWRU\LQYHQWRU\ ^ GRGDQLHJLWDUGRPDJD]\QX ` `

FODVV )LQG*XL WDU^ PDLQ `

FindGuitarTester.java

jesteś tutaj 

63

Zastosowanie zasad projektowania obiektowego :

7

?

7

DLACZEGO MAM NA TO ZWRACAĆ UWAGĘ ? 7

Dowiedziałeś się już całkiem dużo na temat pisania doskonałego oprogramowania, jednak wciąż jeszcze musisz się wiele nauczyć. Weź głęboki oddech i zastanów się nad niektórymi terminami i zasadami, które przedstawiliśmy w tym rozdziale. Połącz słowa umieszczone w lewej kolumnie z wyjaśnieniami celów stosowania danych technik lub zasad, znajdującymi się w kolumnie prawej.

Elastyczność

%H]HPQLH7ZöMNOLHQWQLJG\QLHEÚG]LH]DGRZRORQ\%H]Z]JOÚGXQDWRMDNZVSDQLDOHE\ïDE\ ]DSURMHNWRZDQDLQDSLVDQDDSOLNDFMDWRZïDĂQLHMDVSUDZLDPĝHQDWZDU]\NOLHQWDSRMDZLDVLÚ XĂPLHFK

Hermetyzacja

-DZNUDF]DPGRDNFMLWDPJG]LHFKRG]LRPRĝOLZRĂFLZLHORNURWQHJRZ\NRU]\VWDQLDWHJRVDPHJRNRGX LX]\VNDQLHSHZQRĂFLĝHQLHWU]HEDEÚG]LHUR]ZLÈ]\ZDÊSUREOHPöZNWöUHNWRĂLQQ\UR]ZLÈ]DïMXĝ ZF]HĂQLHM

Funkcjonalność

3URJUDPLĂFLXĝ\ZDMÈPQLHSRWRE\RGG]LHOLÊRGVLHELHIUDJPHQW\NRGXNWöUHXOHJDMÈ]PLDQRP RGW\FKNWöUHSR]RVWDMÈWDNLHVDPHG]LÚNLWHPXïDWZRPRĝQDZSURZDG]DÊZNRG]LHPRG\ILNDFMH ĽEH]REDZ\ĝHFDïDDSOLNDFMDSU]HVWDQLHG]LDïDÊ

Wzorce projektowe

3URJUDPLĂFLXĝ\ZDMÈPQLHSRWRE\RSURJUDPRZDQLHPRĝQDE\ïRUR]ZLMDÊL]PLHQLDÊEH]NRQLHF]QRĂFL FLÈJïHJRSLVDQLDJRRGVDPHJRSRF]ÈWNX7RMDVSUDZLDPĝHDSOLNDFMHVWDMÈVLÚVROLGQHLRGSRUQH 2GSRZLHG]L]QDMG]LHV]QDVWURQLH Nie istnieją

głupie pytania

P: Hermetyzacja nie jest jedyną zasadą projektowania

P: Ale nie do końca rozumiem, w jaki sposób ta cała

obiektowego, jakiej używamy na tym etapie prac nad aplikacją, prawda?

hermetyzacja poprawia elastyczność mojego kodu. Możecie to jeszcze raz wyjaśnić?

O: Nie. Kolejnymi z zasad, o których warto pamiętać, są

O: Kiedy już poprawiłeś swój kod i zapewniłeś, że działa on tak,

W dalszej części książki przedstawimy znacznie więcej informacji o zasadach projektowania obiektowego (a w rozdziale 8. zaprezentujemy nawet kilka przykładów); zatem nie przejmuj się, jeśli teraz jeszcze nie wszystko dokładnie rozumiesz. Zanim skończysz lekturę tej książki, dowiesz się znacznie więcej o hermetyzacji, projektowaniu klas oraz o wielu innych zagadnieniach.

Jednak dzięki zastosowaniu takich zasad jak hermetyzacja oraz poprawne projektowanie klas wprowadzanie zmian może być znacznie łatwiejsze, a sama aplikacja stanie się bardziej elastyczna.

dziedziczenie i polimorfizm. Jednak także i one łączą się z powtarzalnością kodu oraz hermetyzacją; dlatego rozpoczynanie pracy od poszukiwania miejsc, w jakich można zastosować hermetyzację, zawsze będzie dobrym rozwiązaniem.

64

Rozdział 1.

jak sobie tego życzył klient, największym problemem staje się elastyczność. Co się stanie, jeśli klient poprosi o dodanie do aplikacji kilku nowych właściwości lub możliwości funkcjonalnych? Jeśli w aplikacji będzie wiele powtarzających się fragmentów kodu bądź jeśli jej struktura dziedziczenia będzie skomplikowana i niejasna, to wprowadzanie modyfikacji w takim programie może stać się prawdziwym koszmarem.

Dobrze zaprojektowane aplikacje są super

Wróćmy do aplikacji Ryśka… Sprawdźmy teraz, czy wszystkie wprowadzone zmiany nie wpłynęły negatywnie na poprawność działania aplikacji Ryśka. Skompiluj wszystkie klasy i jeszcze raz uruchom program )LQG*XLWDU7HVWHU:

Tym razem wyniki od niczym się nie różnią o, uzyskiwanych poprzedni jednak sama aplikacja ana jest lepiej zaprojektow i znacznie bardziej elastyczna.

WYSIL

SZARE KOMÓRKI Czy jesteś w stanie podać trzy konkretne czynniki, które sprawiają, że w dobrze zaprojektowanym oprogramowaniu wprowadzanie zmian jest łatwiejsze niż w oprogramowaniu, w którym fragmenty kodu powielają się?

jesteś tutaj 

65

Czas na poważne projektowanie

Projekt po raz pierwszy, projekt po raz drugi Skoro już raz przeanalizowałeś aplikację i zastosowałeś w niej podstawowe techniki projektowania obiektowego, czas przyjrzeć się jej ponownie i upewnić, że jest ona nie tylko elastyczna, lecz także zapewnia łatwość wielokrotnego stosowania fragmentów kodu oraz ich rozszerzania.

1. Upewnij się, że oprogramowanie robi to, czego oczekuje klient.

2. Zastosuj proste zasady projektowania obiektowego, by poprawić elastyczność oprogramowania.

Nadszedł czas, by poważnie rozważyć możliwości wielokrotnego wykorzystania kodu aplikacji oraz łatwość wprowadzania w nim zmian. To właśnie teraz możesz zmienić poprawnie zaprojektowane klasy na rozszerzalne, nadające się do wielokrotnego stosowania komponenty.

Skoro już za zasady proj stosowałeś podstaw nadszedł cz ektowania obiektowegowe wzorce proj as, by wykorzystać o, się na moż ektowe i skoncentrow stosowania liwościach wielokrotneać go kodu.

3. Staraj się, by projekt oprogramowania zapewniał łatwość jego utrzymania i pozwalał na jego wielokrotne stosowanie. 66

Rozdział 1.

Dobrze zaprojektowane aplikacje są super

(naprawdę) Sprawdźmy, czy klasa Inventory.java jest dobrze zaprojektowana W poprzedniej części rozdziału zastosowaliśmy hermetyzację, by poprawić projekt narzędzia do wyszukiwania gitar w magazynie Ryśka, niemniej jednak w naszym kodzie wciąż można znaleźć miejsca, które należałoby poprawić w celu pozbycia się potencjalnych problemów. Dzięki temu nasz kod będzie można łatwiej rozszerzać, kiedy Rysiek wymyśli kolejne możliwości, które chciałby dodać do aplikacji, oraz łatwiej wykorzystać, jeśli zechcemy zastosować jego fragmenty w innej aplikacji.

Teraz, kiedy już oddałe sprawne narzędzie wy ś Ryśkowi szu w magazynie gitary pas kujące wymagań określanych ujące do masz pewność, że Rysprzez klientów, do Ciebie zadzwoni, jeśiek ponownie zmienić w swojej aplikali zechce coś cji.



Oto kod metody search() klasy Inventory. Przyjrzyj mu się dokładniej.

SXEOLF/LVWVHDUFK *XLWDU6SHFVHDUFK6SHF ^ /LVWPDWFKLQJ*XLWDUV QHZ/LQNHG/LVW  IRU ,WHUDWRUL JXLWDUVLWHUDWRU LKDV1H[W  ^ *XLWDUJXLWDU  *XLWDU LQH[W  *XLWDU6SHFJXLWDU6SHF JXLWDUJHW6SHF  LI VHDUFK6SHFJHW%XLOGHU  JXLWDU6SHFJHW%XLOGHU FRQWLQXH 6WULQJPRGHO VHDUFK6SHFJHW0RGHO WR/RZHU&DVH  LI PRGHO QXOO   PRGHOHTXDOV ĵĵ   PRGHOHTXDOV JXLWDU6SHFJHW0RGHO WR/RZHU&DVH FRQWLQXH LI VHDUFK6SHFJHW7\SH  JXLWDU6SHFJHW7\SH FRQWLQXH LI VHDUFK6SHFJHW%DFN:RRG  JXLWDU6SHFJHW%DFN:RRG FRQWLQXH LI VHDUFK6SHFJHW7RS:RRG  JXLWDU6SHFJHW7RS:RRG FRQWLQXH PDWFKLQJ*XLWDUVDGG JXLWDU  ` UHWXUQPDWFKLQJ*XLWDUV `

FODVV ,QYHQ WRU\^ 6HDUFK `

Inventory.java

=REDF]FRQDSLVDOLĂP\QDVWURQLH

Zaostrz ołówek Co zmieniłbyś w tym fragmencie kodu? W przedstawionym kodzie występuje poważny problem, Twoim zadaniem jest go odnaleźć. W poniższych pustych wierszach zapisz, na czym według Ciebie polega ten problem oraz w jaki sposób można by go rozwiązać.

jesteś tutaj 

67

Czy proste zmiany naprawdę są proste?

No wiesz… Zawsze uwielbiałem grać na 12-strunowych gitarach. Jak trudne byłoby zmodyfikowanie aplikacji w taki sposób, bym mógł sprzedawać takie gitary i aby moi klienci mogli je wyszukiwać?

Jak łatwo będzie wprowadzić taką zmianę do aplikacji Ryśka? Przeanalizuj diagram klas tworzących aplikację Ryśka i zastanów się, co należałoby zrobić, aby wyposażyć ją w możliwość obsługi gitar 12-strunowych. Jakie właściwości i metody musiałbyś dodać, w jakich klasach należałoby je umieścić? Jakie zmiany musiałbyś wprowadzić w kodzie aplikacji, by zapewnić klientom Ryśka możliwość wyszukiwania 12-strunowych gitar? Ile klas musiałeś zmienić, by wprowadzić powyższą modyfikację? Czy dalej uważasz, że aplikacja Ryśka jest dobrze zaprojektowana?

Guitar serialNumber: String price: double spec: GuitarSpec getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): GuitarSpec

68

Rozdział 1.

Dobrze zaprojektowane aplikacje są super

Przypiski do diagramu klas aplikacji Ryśka

Zaostrz ołówek

Rysiek chciałby sprzedawać 12-strunowe gitary. Przygotuj zatem ołówek i umieść na diagramie klas notatki zawierające następujące informacje:

GuitarSpec builder: Builder model: String type: Type backWood: Wood topWood: Wood

1. Do jakiej klasy dodałbyś nową właściwość o nazwie numStrings, która będzie służyć do przechowywania informacji o liczbie strun w danej gitarze? 2. Gdzie dodałabyś nową metodę o nazwie JHW1XP6WULQJV , która zwraca liczbę strun w gitarze?

getBuilder(): Builder getModel(): String getType(): Type getBackWood(): Wood getTopWood(): Wood

3. W jakich innych miejscach aplikacji powinieneś wprowadzić zmiany w kodzie, tak by podczas wyszukiwania gitar klienci Ryśka mogli określać, że interesują ich gitary 12-strunowe? W końcu, w umieszczonych poniżej pustych wierszach zapisz wszelkie problemy związane z projektem aplikacji, które odnalazłeś podczas dodawania obsługi 12-strunowych gitar.

Inventory guitars: List

Oto podpowiedź: zapisana tutaj odpowiedź powinna być związana z informacjami, które zapisałeś w pustych wierszach na stronie 67.

addGuitar(String, double, Builder, String, Type, Wood, Wood) getGuitar(String): Guitar search(GuitarSpec): List

WYSIL

Builder toString(): String

Type

toString(): String

SZARE KOMÓRKI

Wood

toString(): String

Na czym polega przewaga utworzenia nowej właściwości o nazwie numStrings nad dodaniem właściwości logicznej określającej, czy dana gitara ma 12 strun?

jesteś tutaj 

69

Mamy problem z hermetyzacją

Zaostrz ołówek

Przypiski do diagramu klas aplikacji Ryśka

Rozwiązanie

Rysiek chciałby sprzedawać 12-strunowe gitary. Przygotuj zatem ołówek i umieść na diagramie klas notatki zawierające następujące informacje:

1. Do jakiej klasy dodałbyś nową właściwość o nazwie numStrings, która będzie służyć do przechowywania informacji o liczbie strun w danej gitarze? 2. Gdzie dodałabyś nową metodę o nazwie JHW1XP6WULQJV , która zwraca liczbę strun w gitarze? 3. W jakich innych miejscach aplikacji powinieneś wprowadzić zmiany w kodzie, tak by podczas wyszukiwania gitar klienci Ryśka mogli określać, że interesują ich gitary 12-strunowe? W końcu, w umieszczonych poniżej pustych wierszach zapisz wszelkie problemy związane z projektem aplikacji, które odnalazłeś podczas dodawania obsługi 12-strunowych gitar.

Dodajemy właściwość do klasy GuitarSpec, lecz jednocześnie musimy zmienić kod metody search() w klasie Inventory oraz konstruktor klasy Guitar. Oto co my wymyśliliśmy. Czy Ty zapisałeś coś podobnego?

Do klasy GuitarSpec musimy dodać właściwość numStrings.

Guitar serialNumber: String price: double spec: GuitarSpec getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): GuitarSpec

GuitarSpec

Do tej klasy musimy także dodać metodę getNumStrings(), która będzie zwracać liczbę strun, jaką posiada dana gitara.

guitar: List addGuitar(String, double, Builder, String, Type, Wood, Wood) getGuitar(String): Guitar search(GuitarSpec): List

Builder toString(): String

Type

toString(): String Wood toString(): String

70

Rozdział 1.

Metoda addGuitar() zdefiniowana w tej klasie także operuje na wszystkich właściwościach gitary. Dodanie nowej właściwości oznacza zatem konieczność wprowadzenia modyfikacji w tej metodzie — a to jest problem.

Inventory

builder: Builder model: String type: Type backWood: Wood topWood: Wood getBuilder(): Builder getModel(): String getType(): Type getBackWood(): Wood getTopWood(): Wood

Musimy zmienić konstruktor tej klasy, gdyż w jego wywołaniu są przekazywane wszystkie informacje, które następnie zapisujemy w obiekcie GuitarSpec.

Kolejny problem: musimy zmienić metodę search() klasy Inventory, aby uwzględnić nowe właściwości dodane do klasy GuitarSpec.

Dobrze zaprojektowane aplikacje są super

Czyli problem polega właśnie na tym? Dodawanie nowych właściwości do klasy GuitarSpec nie powinno zmuszać nas do wprowadzania modyfikacji w klasach Guitar oraz Inventory. Czy także ten problem możemy rozwiązać, wykorzystując hermetyzację?

Owszem — musimy hermetyzować specyfikacje gitary i lepiej izolować je od pozostałych fragmentów aplikacji Ryśka. Choć nowe właściwości dodajemy jedynie do klasy *XLWDU6SHF, to jednak przy tej okazji musimy zmodyfikować dwie inne klasy: *XLWDU oraz ,QYHQWRU\. W konstruktorze klasy *XLWDU należy przekazywać dodatkowy argument, a w metodzie VHDUFK klasy ,QYHQWRU\ trzeba dodać jedno porównanie. Ten konstruktor tworzy obiekt GuitarSpec, zatem każda zmiana specyfikacji gitary będzie powodować konieczność zmiany samego konstruktora.

SXEOLF*XLWDU 6WULQJVHULDO1XPEHU GRXEOHSULFH %XLOGHUEXLOGHU 6WULQJPRGHO7\SHW\SH :RRGEDFN:RRG:RRGWRS:RRG ^ WKLVVHULDO1XPEHU VHULDO1XPEHU WKLVSULFH SULFH WKLVVSHF QHZ*XLWDU6SHF EXLOGHUPRGHO W\SHEDFN:RRGWRS:RRG  FODVV *XLWDU *XL WDU `

Guitar.java

m Zastosowanie tego kodu w inny programie nie będzie prostym acji zadaniem. Wszystkie klasy aplik Ryśka są od siebie wzajemnie ać uzależnione i nie można zastosow o sneg ocze jedn bez nich z ej jedn wykorzystania wszystkich pozostałych.

SXEOLF/LVWVHDUFK *XLWDU6SHFVHDUFK6SHF ^ /LVWPDWFKLQJ*XLWDUV QHZ/LQNHG/LVW  IRU ,WHUDWRUL JXLWDUVLWHUDWRU LKDV1H[W  ^ *XLWDUJXLWDU  *XLWDU LQH[W  *XLWDU6SHFJXLWDU6SHF JXLWDUJHW6SHF  LI VHDUFK6SHFJHW%XLOGHU  JXLWDU6SHFJHW%XLOGHU FRQWLQXH 6WULQJPRGHO VHDUFK6SHFJHW0RGHO WR/RZHU&DVH  LI PRGHO QXOO   PRGHOHTXDOV ķĵ   PRGHOHTXDOV JXLWDU6SHFJHW0RGHO WR/RZHU&DVH FRQWLQXH LI VHDUFK6SHFJHW7\SH  JXLWDU6SHFJHW7\SH FRQWLQXH LI VHDUFK6SHFJHW%DFN:RRG  JXLWDU6SHFJHW%DFN:RRG FRQWLQXH LI VHDUFK6SHFJHW7RS:RRG  JXLWDU6SHFJHW7RS:RRG  FRQWLQXH PDWFKLQJ*XLWDUVDGG JXLWDU  ` FODVV ,QYHQ UHWXUQPDWFKLQJ*XLWDUV WRU\^ ` 6HDUFK `

Inventory.java

jesteś tutaj 

71

Staraj się, by fragmenty aplikacji można było wielokrotnie używać

=DJDGNDSURMHNWRZD Sama wiedza, że w aplikacji Ryśka coś szwankuje, to zdecydowanie za mało. Nie wystarczy także świadomość, że konieczne jest ponowne zastosowanie hermetyzacji. Teraz musisz wymyślić, jak należy poprawić tę aplikację, by łatwiej można było wielokrotnie stosować jej fragmenty oraz by jej rozszerzanie nie przysparzało tylu problemów. 3UREOHP Dodanie nowej właściwości do klasy *XLWDU6SHF zmusza nas do wprowadzenia zmian także w kodzie klas *XLWDU oraz ,QYHQWRU\. Strukturę aplikacji należy zmienić w taki sposób, by dodawanie nowych właściwości do klasy *XLWDU6SHF nie powodowało konieczności modyfikacji innych klas. 7ZRMH]DGDQLH 1

Dodaj właściwość QXP6WULQJV oraz metodę JHW1XP6WULQJV do klasy *XLWDU6SHF.

2

Zmodyfikuj kod klasy *XLWDU w taki sposób, by właściwości obiektu *XLWDU6SHF zostały usunięte z konstruktora tej klasy.

3

Zmień metodę VHDUFK w klasie ,QYHQWRU\ w taki sposób, by porównywanie dwóch obiektów *XLWDU6SHF zostało delegowane do klasy *XLWDU6SHF, a nie było realizowane bezpośrednio w tej metodzie.

4

Zmodyfikuj kod klasy )LQG*XLWDU7HVWHU, by działała ona poprawnie ze zmienionymi klasami *XLWDU, *XLWDU6SHF i ,QYHQWRU\, a następnie upewnij się, że wyszukiwanie gitar działa poprawnie.

5

Porównaj odpowiedzi, które podałeś, z naszymi odpowiedziami zamieszczonymi na stronie 74; następnie przygotuj się na kolejny test, który pozwoli Ci przekonać się, czy wreszcie zakończyłeś prace nad aplikacją Ryśka.

Jedyna zmiana wprowadzić w , jaką będziesz musiał kodu tworzące tym miejscu, dotyczy magazynu i pogo testową zawartość nowego konstr lega na zastosowaniu uktora klasy Guitar.

72

Rozdział 1.

Nie wiesz, co — w tym kontekście — oznacza „delegowanie” — sprawdź…

Dobrze zaprojektowane aplikacje są super

Nie istnieją

głupie pytania

P: Napisaliście, że powinienem „delegować”

P: A niby w jaki sposób to delegowanie ma ułatwiać

porównywanie do klasy GuitarSpec. Co to jest delegowanie?

możliwości wielokrotnego wykonywania kodu?

O: O delegowaniu mówimy w sytuacji, gdy obiekt, który musi

wykonać pewną czynność, zamiast wykonać ją samemu, prosi o jej wykonanie (w całości lub częściowo) inny obiekt.

A zatem, w swojej zagadce projektowej nie chcesz, by metoda VHDUFK klasy ,QYHQWRU\ porównywała dwie specyfikacje *XLWDU6SHF bezpośrednio w swoim kodzie. Zamiast tego chcesz, by poprosiła obiekt *XLWDU6SHF o określenie, czy specyfikacje te odpowiadają sobie. A zatem metoda VHDUFK deleguje porównanie do obiektu *XLWDU6SHF.

P: A dlaczego używamy takiego rozwiązania? O: Delegowanie ułatwia wielokrotne wykorzystywanie kodu

aplikacji. Oprócz tego pozwala ono, by każdy obiekt koncentrował się wyłącznie na swoich możliwościach funkcjonalnych i chroni przed umieszczaniem zachowań związanych z jednym obiektem w różnych miejscach kodu aplikacji. Jednym z najczęściej spotykanych przykładów delegowania w języku Java jest metoda HTXDOV . Metoda ta nie stara się samodzielnie sprawdzać, czy dwa obiekty są sobie równe, lecz zamiast tego wywołuje metodę HTXDOV jednego z porównywanych obiektów i przekazuje w jej wywołaniu drugi obiekt. Następnie pobiera jedynie wartość true lub false zwróconą przez wywołanie metody HTXDOV .

O: Dzięki delegowaniu każdy obiekt może sam zajmować

się porównywaniem z innymi obiektami (lub wykonywaniem jakiejkolwiek innej operacji). To z kolei oznacza, że obiekty mogą być bardziej niezależne albo luźniej powiązane. Takie luźniej powiązane obiekty łatwiej jest zastosować w innej aplikacji, gdyż nie są one ściśle uzależnione od kodu innych obiektów.

P: Jeszcze raz — co oznacza „luźne powiązanie”? O: Luźne powiązanie oznacza, że każdy z obiektów używanych

w aplikacji wykonuje tylko i wyłączenie swoje zadania. A zatem cała funkcjonalność aplikacji jest rozdzielona i zaimplementowana w dobrze zdefiniowanych obiektach, z których każdy w doskonały sposób realizuje przypisane mu zadania.

P: A dlaczego to jest dobre rozwiązanie? O: Aplikacje tworzone przez luźno powiązane obiekty zazwyczaj

są bardziej elastyczne i łatwiej można wprowadzać w nich modyfikacje. Ponieważ poszczególne obiekty nie są zwykle w żaden sposób zależne od innych obiektów, zatem możemy zmienić zachowanie jednego z obiektów bez konieczności wprowadzania modyfikacji w innych. Dzięki temu dodawanie nowych właściwości lub możliwości funkcjonalnych staje się znacznie prostsze.

Kącik naukowy delegowanie — proces polegający na tym, iż jeden obiekt zleca wykonanie pewnej operacji innemu, który wykonuje ją w imieniu pierwszego obiektu.

jesteś tutaj 

73

Jeszcze więcej hermetyzacji

=DJDGNDSURMHNWRZD5R]ZLÈ]DQLH Sama wiedza, że w aplikacji Ryśka coś szwankuje, to zdecydowanie za mało. Nie wystarczy także świadomość, że konieczne jest ponowne zastosowanie hermetyzacji. Teraz musisz wymyślić, jak należy poprawić tę aplikację, by łatwiej można było wielokrotnie stosować jej fragmenty oraz by jej rozszerzanie nie przysparzało tylu problemów. 3UREOHP Dodanie nowej właściwości do klasy *XLWDU6SHF zmusza nas do wprowadzenia zmian także w kodzie klas *XLWDU oraz ,QYHQWRU\. Strukturę aplikacji należy zmienić w taki sposób, by dodawanie nowych właściwości do klasy *XLWDU6SHF nie powodowało konieczności modyfikacji innych klas. 7ZRMH]DGDQLH 1

Dodaj właściwość QXP6WULQJV oraz metodę JHW1XP6WULQJV do klasy *XLWDU6SHF. SXEOLFFODVV*XLWDU6SHF^ LQQHZïDĂFLZRĂFL SULYDWHLQWQXP6WULQJV

To było naprawdę łatwe…

Nie zapomnij zmodyfiko wać konstruktora klasy GuitarSpec.

SXEOLF*XLWDU6SHF %XLOGHUEXLOGHU6WULQJPRGHO7\SHW\SH LQWQXP6WULQJV:RRGEDFN:RRG:RRGWRS:RRG ^ WKLVEXLOGHU EXLOGHU WKLVPRGHO PRGHO WKLVW\SH W\SH WKLVQXP6WULQJV QXP6WULQJV WKLVEDFN:RRG EDFN:RRG WKLVWRS:RRG WRS:RRG ` LQQHPHWRG\ SXEOLFLQWJHW1XP6WULQJV ^ UHWXUQQXP6WULQJV ` `

FODVV *XLWDU 6SHF^ JHW 1XP 6WULQJV

GuitarSpec.java

74

Rozdział 1.

Dobrze zaprojektowane aplikacje są super

2

Zmodyfikuj kod klasy *XLWDU w taki sposób, by właściwości obiektu *XLWDU6SHF zostały usunięte z konstruktora tej klasy.

SXEOLF*XLWDU 6WULQJVHULDO1XPEHUGRXEOHSULFH*XLWDU6SHFVSHF ^ WKLVVHULDO1XPEHU VHULDO1XPEHU WKLVSULFH SULFH WKLVVSHF VSHF ` FODVV

arSpec Teraz nie tworzymy obiektu Guit w konstruktorze klasy Guitar, lecz ołania wyw przekazujemy go jako argument konstruktora.

3

*XLWDU^ *XL WDU `

Guitar.java

Zmień metodę VHDUFK w klasie ,QYHQWRU\ w taki sposób, by porównywanie dwóch obiektów *XLWDU6SHF zostało delegowane do klasy *XLWDU6SHF, a nie było realizowane bezpośrednio w tej metodzie.  ^  LWDU6SHF VHDUFK6SHF  SXEOLF/LVW VHDUFK *X  LVW V QHZ/LQNHG/ /LVWPDWFKLQJ*XLWDU DV1H[W   ^ LWDUVLWHUDWRU  LK JX L WRU HUD IRU ,W  *XLWDU LQH[W   *XLWDUJXLWDU F F PDWFKHV VHDUFK6SH 6SH JHW DU XLW  LI J  DU XLW G J DG DUV   PDWFKLQJ*XLW ` DUV UHWXUQ PDWFKLQJ*XLW `

Znaczna część kodu metody search() została z niej usunięta i przeniesiona do metody matches() klasy GuitarSpec.

Teraz dodawanie nowych właściwości do klasy GuitarSpec wymaga jedynie wprowadzania zmian w kodzie tej kasy — klasy Guitar oraz Inventory nie muszą być modyfikowane.



Metoda search() stała się znacznie prostsza.

FODVV ,QYHQ WRU\^ VHDUFK

Inventory.java

SXEOLFERROHDQPDWFKHV *XLWDU6SHFRWKHU6SHF ^ LI EXLOGHU RWKHU6SHFEXLOGHU UHWXUQIDOVH LI PRGHO QXOO   PRGHOHTXDOV ĵĵ   PRGHOWR/RZHU&DVH HTXDOV RWKHU6SHFPRGHOWR/RZHU&DVH UHWXUQIDOVH LI W\SH RWKHU6SHFW\SH UHWXUQIDOVH LI QXP6WULQJV RWKHU6SHFQXP6WULQJV UHWXUQIDOVH LI EDFN:RRG RWKHU6SHFEDFN:RRG UHWXUQIDOVH LI WRS:RRG RWKHU6SHFWRS:RRG UHWXUQIDOVH UHWXUQWUXH FODVV ` *XLWDU

6SHF^ JHW 1XP 6WULQJV

GuitarSpec.java

jesteś tutaj 

75

Test aplikacji

Ostatni test aplikacji (przygotowanej do wielokrotnego używania kodu) O rany… naprawdę wykonaliśmy kawał dobrej roboty od momentu, gdy Rysiek pokazał nam pierwszą wersję swojej aplikacji. Upewnijmy się, czy jej ostatnia wersja wciąż poprawnie działa — zarówno z punktu widzenia Ryśka, jak i jego klientów — i czy spełnia nasze wymagania dotyczące poprawnego projektu, prostego utrzymania oraz kodu, którego z łatwością będzie można wielokrotnie używać.

Oto co powinieneś zobaczyć po wykonaniu nowej wersji programu FindGuitarTester.

Ewa może obejrzeć kilka wyszuka dla niej gitar, a Rysiek — znów nych sprzedawać instrumenty swojej wyszukanej klienteli.

Gratulujemy! Udało Ci się zmodyfikować niedziałającą aplikację przeszukującą magazyn Ryśka i zmienić ją w poprawnie zaprojektowane, doskonałe oprogramowanie. 76

Rozdział 1.

Dobrze zaprojektowane aplikacje są super

Oto co zrobiliśmy Przyjrzyjmy się pokrótce, w jaki sposób udało nam się sprawić, że obecnie aplikacja przeszukująca magazyn Ryśka działa tak dobrze:

Rozpoczęliśmy od poprawienia niektórych problemów związanych z działaniem aplikacji.

Czy pamiętasz nasze Wykonaliśmy je kolejn trzy kroki? o, przekształcić niedziała by przeszukujące magazynjące narzędzie we w pełni funkcjona Ryśka lną zaprojektowaną aplika , dobrze cję.

1. Upewnij się, że oprogramowanie robi to, czego oczekuje klient.

Następnie dodaliśmy do niej kilka nowych możliwości funkcjonalnych, dzięki czemu wyszukiwanie zwracało nie jedną, lecz całą listę gitar.

żliwości Rozbudowując mo acji, funkcjonalne aplik że podejmowane , upewniliśmy się z jej strukturą e an iąz zw zje decy bre. są właściwe i do

2. Zastosuj proste zasady projektowania obiektowego, by poprawić elastyczność oprogramowania.

Oprócz tego hermetyz owaliśmy właściwości gitary i zapewniliśmy, że dodawanie nowych nie będzie stanowić większego pro blemu.

3. Staraj się, by projekt oprogramowania zapewniał Udało się nam nawet , nie zastosować delegowa używane dzięki czemu obiekty luźniej się ły sta cji ika apl w poprawiło powiązane; to z kolei o neg rot lok wie i możliwośc cji. używania kodu aplika

łatwość jego utrzymania i pozwalał na jego wielokrotne stosowanie. jesteś tutaj 

77

Analiza i projektowanie obiektowe pomagają w tworzeniu doskonałego oprogramowania

Czy pamiętasz tego biednego gościa? $OBRZEZAPROJEKTOWANEA

PLIKACJES’SUPER

On chciał jedynie pisać wspaniałe oprogramowanie. Jaka jest zatem odpowiedź? W jaki sposób można konsekwentnie pisać wspaniałe oprogramowanie?

-AM · ODCZEGONALE‘YZACZ· .IBY SKD MAMWIEDZIE WYMPROJEKTEM ACZYNAMPRAC»NADNO WRA‘ENIE ‘EILEKRO·Z E‘YZROBI· ONAL GO C NIETE ODNOv CZ KA‘DYMAINNEZDANIE SAMI ZROBI»COvDOBRZE LE WPIERWSZEJKOLEJNOvCI#ZA USZ» PRZEROBI· CAˆAPLIKACJ» ‘EM CZASAMIKOÂCZY SI»NATYM MMIEJSCU !JACHC» WZˆY ˆEM ZACZ U BO IE ODPOCZTK TNE OPROGRAMOWAN JEDYNIEPISA· vWIE IE WINIENEMZACZ·PISAN !ZATEM ODCZEGOPO APLIKACJIDLA2YvKA

Potrzebujesz w tym celu sekwencji czynności, które pozwolą Ci upewnić się, że Twoje oprogramowanie działa i jest dobrze zaprojektowane. Nie musi ona być ani długa, ani skomplikowana — wystarczy prosta, trójetapowa sekwencja, której użyliśmy do poprawienia aplikacji Ryśka i której z powodzeniem będziesz mógł używać we wszystkich swoich projektach.

A TKUS ]ZY]œL

WYÒXK dK UKÒNcW

\KdOW ZS]K¨ NYL\O

YZ\YQ\KWYaKXSO)

Analiza i projektowanie obiektowe pomoże Ci pisać wspaniałe programy, i to za każdym razem. JESTEuTUTAJ



Wyrażenie to będziemy określali skrótowo jako OOA&D, od angielskich słów Object-Oriented Analysis & Design.

78

Rozdział 1.

Za każdym razem gdy w tym rozdziale wspominaliśmy o trzech krokach pozwalających na pisanie wspaniałego oprogramowania, tak naprawdę mieliśmy na myśli OOA&D — analizę i projektowanie obiektowe. W istocie analiza i projektowanie obiektowe jest sposobem pisania oprogramowania. Jej celem jest to, by oprogramowanie robiło, co do niego należy, i było dobrze zaprojektowane. To z kolei oznacza, że kod jest elastyczny, łatwo można wprowadzać w nim zmiany, jest prosty w utrzymaniu i nadaje się do wielokrotnego używania.

Dobrze zaprojektowane aplikacje są super

OOA&D ma na celu tworzenie wspaniałego oprogramowania, a nie dodanie Ci papierkowej roboty! .OLHQFLVÈ]DGRZROHQLNLHG\LFKDSOLNDFMH'=,$’$-k O wymaganiach napiszemy w rozdziale 2.

Możemy uzyskać od klienta wymagania, które pozwolą nam upewnić się, że tworzona aplikacja będzie robić to, o co klient prosił. Z powodzeniem można do tego celu zastosować tak zwane przypadki użycia oraz diagramy, niemniej jednak wszystko sprowadza się do tego, by dowiedzieć się, jakich możliwości klient oczekuje od aplikacji. .OLHQFLVÈ]DGRZROHQLNLHG\LFKDSOLNDFMHEÚGÈ'=,$’$m'’8*2

Już nieco się dowiedziałeś o wrażliwych i kiepskich aplikacjach.

Nikt nie przepada za sytuacją, gdy aplikacja, która do tej pory działała dobrze, nagle zacznie szwankować. Jeśli dobrze zaprojektujemy nasze aplikacje, to będą one solidne i nie będą przysparzać problemów za każdym razem, gdy użytkownik zacznie ich używać w niezwykły bądź niespodziewany sposób. Klasy oraz diagramy sekwencji mogą pomóc w wykryciu usterek w projekcie aplikacji, jednak kluczowe znaczenie ma pisanie dobrze zaprojektowanego i elastycznego kodu. .OLHQFLVÈ]DGRZROHQLNLHG\LFKDSOLNDFMHPRĝQD8$.78$/1,m

Chcesz poznać więcej informacji o delegowaniu, złożeniach i agregacji? Wszystkie te zagadnienia poruszymy szczegółowo najpierw w rozdziale 5., a następnie w rozdziale 8.

W rozdziale 8. przekonasz się, jak duże znaczenie i wpływ na powstający kod mają te zasady.

Kiedy klient prosi o dodanie kilku nowych, prostych możliwości, to dla niego nie ma nic gorszego, niż usłyszeć, że wykonanie poprawek zajmie dwa tygodnie i będzie kosztowało kilkadziesiąt tysięcy złotych. Dzięki zastosowaniu technik projektowania obiektowego, takich jak hermetyzacja, składanie oraz delegowanie, tworzone oprogramowanie będzie proste w utrzymaniu i łatwe do rozszerzania oraz aktualizacji. 3URJUDPLĂFLVÈ]DGRZROHQLNLHG\QDSLVDQHJRNRGXPRĝQD :,(/2.5271,(8¿@DUJV ^ 'RJ'RRUGRRU QHZ'RJ'RRU  5HPRWHUHPRWH QHZ5HPRWH GRRU  6\VWHPRXWSULQWOQ Ĵ$]RUV]F]HNDE\Z\MĂÊQDVSDFHUĵ  UHPRWHSUHVV%XWWRQ  6\VWHPRXWSULQWOQ Ĵ?Q$]RUZ\V]HGïQD]HZQÈWU]ĵ  UHPRWHSUHVV%XWWRQ     

Ponieważ drzwiczki zamykają się automatycznie, Azor ma sporo czasu na powrót do domu. Janka nie musi ponownie otwierać drzwiczek, by wpuścić Azora do domu.

W nowej, poprawionej wersji programu

Janka  nie musi naciskać

przycisku na pilocie, by zamknąć drzwiczki. Teraz drzwiczki zamkną się automatycznie.

6\VWHPRXWSULQWOQ Ĵ?Q$]RU]DïDWZLïVZRMHSRWU]HE\ĵ  UHPRWHSUHVV%XWWRQ  6\VWHPRXWSULQWOQ Ĵ?Q$]RUMHVW]SRZURWHPZGRPXĵ  UHPRWHSUHVV%XWWRQ      ` ` A to jest kol

P

: Przestałem rozumieć, gdy wspomnieliście o kodzie licznika czasu. Wytłumaczcie mi jeszcze raz, co się w nim dzieje.

O: Dobrze… Nie musisz w tym miejscu

wgłębiać się w tajniki Javy. Najważniejsze jest jedno — że przypadek użycia pomógł nam napisać dobre wymagania, a wymagania pomogły w napisaniu kodu, który zapewnił poprawne działanie drzwiczek dla psa. A to jest znacznie ważniejsze od tego, w jaki sposób — a nawet w jakim języku — zostało napisane oprogramowanie obsługujące drzwiczki dla psa.



ejne miejsc w którym możemy poz e, być się niepotrzebnego kodu.

P: Zatem nowa wersja symulatora

P: A dlaczego nie testujemy ścieżki

testuje główną ścieżkę zapisaną w przypadku użycia, czy tak?

alternatywnej, także uwzględnionej w przypadku użycia?

O: Owszem. Zajrzyj ponownie na stronę

O: I to jest bardzo dobre pytanie.

106 i przeanalizuj działanie drzwiczek dla psa. To właśnie to działanie testuje nowa wersja symulatora 'RJ'RRU6LPXODWRU. Chcemy mieć pewność, że nowe drzwiczki działają dokładnie tak, jak tego chcieli Tadek i Janka.

Przetestujmy tę wersję drzwiczek, a później zastanowimy się nad odpowiedzią.

jesteś tutaj 

111

Jak to działa?

Test, wersja 2.0 Czas sprawdzić, czy nasza ciężka praca dała zamierzone efekty. Przetestujmy nasze nowe i poprawione drzwiczki dla psa.

1

Skompiluj wszystkie pliki z kodem źródłowym.

FODVV 'RJ'RRU 6LP^ ` FODVV 'RJ'RRU DogDoor.java 6LP^ ` FODVV 'RJ'RRU Remote.java 6LP^ `

javac *.java

Remote.class DogDoorSimulator.class

DogDoorSimulator.java

2

Wykonaj program testowy!

7LN 7DN Po otworzeniu drzwiczek upłynie kilka sekund…

…zanim drzwiczki się zamkną.

112

Rozdział 2.

DogDoor.class

7LN 7DN

Gromadzenie wymagań

To działa! Pokażmy system Tadkowi i Jance…

Twój system musi działać w praktyce…

…dlatego planuj i testuj sytuacje, w których mogą pojawić się problemy.

Ale uważam, że jeszcze nie jesteśmy gotowi, by pokazać nasz system Tadkowi i Jance… Co z tą ścieżką alternatywną, czyli sytuacją, gdy Azor utknie na dworze, a drzwiczki zamkną się automatycznie?

Trafna uwaga… musimy przetestować ścieżkę alternatywną tak samo jak ścieżkę główną. Czyż nie byłoby cudownie, gdyby programy za każdym razem działały tak, jak byśmy tego oczekiwali i życzyli sobie? Oczywiście w praktyce to niemal nigdy się nie zdarza. Zanim będziemy mogli pokazać nowe drzwiczki Tadkowi i Jance, powinniśmy poświęcić nieco czasu, by przetestować drzwiczki i upewnić się, że będą one działać prawidłowo w sytuacji, gdy Azor nie wróci do domu bezpośrednio po załatwieniu swoich potrzeb.

WYSIL

SZARE KOMÓRKI Jakie modyfikacje wprowadziłbyś w kodzie klasy DogDoorSimulator, by przetestować sytuację, gdy drzwiczki zamkną się automatycznie, zostawiając Azora na zewnątrz.

R SUPE WYSIL

SZARE KOMÓRKI

Czy możesz wymyślić chociaż jedną dodatkową ścieżkę alternatywną dla systemu Tadka i Janki? Dla podanej ścieżki napisz także przypadek użycia i zmodyfikuj listę wymagań.

jesteś tutaj 

113

Ścieżki alternatywne

Przegląd ścieżki alternatywnej Upewnijmy się, że dokładnie rozumiemy, co się dzieje na ścieżce alternatywnej, a kiedy wszystko już będzie jasne, zaktualizujemy nasz symulator — 'RJ'RRU6LPXODWRU — tak by uwzględniał tę ścieżkę. Poniżej przedstawiliśmy oryginalny diagram ścieżki głównej, zamieszczony wcześniej na stronie 96, rozbudowany o wyznaczoną przez nas ścieżkę alternatywną, którą dodaliśmy do naszego przypadku użycia.

Janka, otwórz drzwiczki dla Azora, bo ten pies inaczej nie przestanie szczekać!

Hau, hauu

1

Azor szczeka, by właściciele wypuścili go na spacer.

A co jeśli ani Tadka, ani Janki nie będzie akurat w domu? Co się stanie, jeśli nie usłyszą szczekania Azora?

2 Tadek lub Janka słyszą, że Azor szczeka. 3 Tadek lub Janka naciskają przycisk

na pilocie.

7 Azor wchodzi 8 Drzwi zamykają się automatycznie.

114

Rozdział 2.

do środka.

Gromadzenie wymagań

Pamiętaj, że to jest ścieżka alternatywna… Nie będzie ona realizowana podczas każdego użycia system u.

To tu zaczyna się ścieżka alternatywna… Drzwiczki zamykają się, kiedy Azor wciąż jest na zewnątrz.

Hau! Hau! 6.1 Drzwiczki zamykają się

automatycznie. Teraz czuję się znacznie lepiej! Nasz system obsługuje już wszystkie te operacje, jednak bez narysowania tej alternatywnej ścieżki nie moglibyśmy o tym wiedzieć.

4 Drzwiczki otwierają się. 5 Azor wychodzi

na zewnątrz.

6.2 Azor szczeka, by właściciele

wpuścili go do środka Znowu szczeka… Niechże ktoś wpuści tego psa do środka.

6 Azor załatwia

swoje potrzeby. 6.3 Tadek lub Janka słyszą Zauważ, że wraz z otworzeniem się drzwiczek w tym punkcie ścieżki alternatywnej, powracamy do ścieżki głównej.

(ponownie) szczekanie Azora.

6.4 6.4 Drzwiczki otwierają się

Tadek lub Janka naciskają przycisk na pilocie

(ponownie).

jesteś tutaj 

115

Testy ścieżki alternatywnej

Magnesiki z kodem 1DGV]HGâF]DVE\]DNWXDOL]RZDþV\PXODWRUMHGQDNW\PUD]HPQDSLVDQLH VWRVRZQHJRNRGXEĐG]LH7ZRLP]DGDQLHP3RQLİHMSU]HGVWDZLOLĤP\ GRW\FKF]DVRZċSRVWDþNRGXV\PXODWRUD

FODVV 'RJ'RRU 6LP^ `

DogDoorSimulator.java

7ZRLP]DGDQLHPMHVWGRSDVRZDQLHPDJQHVLNyZ]NRGHPZLGRF]Q\FKXGRâXVWURQ\LXPLHV]F]HQLHLFK ZRGSRZLHGQLFKPLHMVFDFKNRGX-HĤOLEĐG]LHV]PLHþMDNLHĤSUREOHP\SU]HDQDOL]XMGLDJUDP]QDMGXMċF\VLĐQD GZyFKSRSU]HGQLFKVWURQDFKE\GRZLHG]LHþVLĐMDNLHF]\QQRĤFLVċZ\NRQ\ZDQHZSRV]F]HJyOQ\FKNURNDFKQD ĤFLHİFHDOWHUQDW\ZQHM$«LMHVWMHGHQNUXF]HN1LHVWHW\]ORGyZNLVSDGâ\ZV]\VWNLHPDJQHVLNL]NURSNDPL ĤUHGQLNDPLLQDZLDVDPLEĐG]LHV]]DWHPPXVLDâGRGDþMHVDPHPXZV]ĐG]LHWDPJG]LHEĐGċSRWU]HEQH SXEOLFFODVV'RJ'RRU6LPXODWRU^ SXEOLFVWDWLFYRLGPDLQ 6WULQJ>@DUJV ^ 'RJ'RRUGRRU QHZ'RJ'RRU  5HPRWHUHPRWH QHZ5HPRWH GRRU  BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB To właśnie w tymBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB na miejscu rozpoczy się ścieżka 6\VWHPRXWSULQWOQ ĵ?Q$]RUZ\V]HGïQD]HZQÈWU]ĵ  alternatywna.

6\VWHPRXWSULQWOQ ĵ?Q$]RU]DïDWZLïVZRMHSRWU]HE\ĵ  WU\^    Chcemy, by wykonywani e programu 7KUHDGFXUUHQW7KUHDG BBBBBBBB   zostało wstrzymane, co na automatyczne zam pozwoli `FDWFK ,QWHUUXSWHG([FHSWLRQH ^` knięcie drzwiczek dla psa.

6\VWHPRXWSULQWOQ ĵDOH$]RUXWNQÈïQD]HZQÈWU]ĵ  BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 6\VWHPRXWSULQWOQ ĵ?Q$]RUMHVW]SRZURWHPZGRPXĵ   ` `

6\VWHPRXWSULQWOQ 6\VWHPRXWSULQWOQ 6\VWHPRXWSULQWOQ ULQWOQ 6\VWHPRXWS 6\VWHPRXWSULQWOQ 6\VWHPRXWSULQWOQ Oto kilka różnych komunikatów, które możesz wyświetlać

SUHVV%XWWRQ SUHVV%XWWRQ SUHVV%XWWRQ SUHVV%XWWRQ UHPRWH UHPRWH UHPRWH PR UH WH

W tym miejscu ścieżka alternatywna ponownie łączy się ze ścieżką główną.

To są metody pozwalające na korzystanie z pilota.

ZDLW ZDLW)RU VOHHS

To są metody pozwalające na wykonywanie operacji na wątkach Javy.

ĵ$]RU GUDSLHGU]ZLĵ ĵ?Q$]RU]DF]\QDV]F]HNDÊĵ ĵDOH$]RUXWNQÈïQD]HZQÈWU ĵ]DWHP7DGHNVLÚJDSRSLORWDĵ ]ĵ ĵDOH$]RUXWNQÈïZĂURGNXĵ D SRSLORWDĵ ĵ]DWHP -DQND VLÚJ

116

Rozdział 2.

Gromadzenie wymagań

Test, wersja 2.1 Wprowadź modyfikacje do swojej wersji pliku 'RJ'RRU6LPXODWRU MDYD, a następnie go skompiluj. Teraz możesz już przetestować działania alternatywnej ścieżki naszego przypadku użycia.

7LN 7DN Drzwiczki otwierają się i Azor wychodzi załatwić swoje potrzeby.

Ale Azor zaczyna gonić owady i drzwiczki zamykają się automatycznie, kiedy jeszcze jest na zewnątrz. Azor szczeka, by do domu, zatem wejść sięga po pilota Janka do drzwiczek…

7LN

…i Azor wraca do klimatyzowanego wnętrza.

7DN ki zamykają Po chwili drzwicziemożliwiając un ie, wn no się po iom i różnym królikom, gryzon się do ie an st owadom do domu.

jesteś tutaj 

117

Dokończenie symulatora

Magnesiki z kodem. Rozwiązanie 2WRFR]URELOLĤP\ZFHOXGRNRĚF]HQLDSUDFQDGV\PXODWRUHP 8SHZQLMVLĐİH7ZRMDRGSRZLHGĮMHVWWDNDVDPD

FODVV 'RJ'RRU 6LP^ `

SXEOLFFODVV'RJ'RRU6LPXODWRU^ DogDoorSimulator.java

SXEOLFVWDWLFYRLGPDLQ 6WULQJ>@DUJV ^ 'RJ'RRUGRRU QHZ'RJ'RRU 

Powinieneś używać kropek, średników i nawiasów, gdyż są one niezbędne.

5HPRWHUHPRWH QHZ5HPRWH GRRU 



 6\VWHPRXWSULQWOQ BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB





UHPRWH  SUHVV%XWWRQ  BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 6\VWHPRXWSULQWOQ ĵ?Q$]RUZ\V]HGïQD]HZQÈWU]ĵ  6\VWHPRXWSULQWOQ ĵ?Q$]RU]DïDWZLïVZRMHSRWU]HE\ĵ  WU\^

VOHHS

7KUHDGFXUUHQW7KUHDG BBBBBBBBBBBB   `FDWFK ,QWHUUXSWHG([FHSWLRQH ^`

owałeś się Być może zdecyd powiedzi wy ję rs wybrać we miętasz, z Tadkiem, ale pa system ać mieliśmy testow ach. nk w realnych waru owaliśmy, yd ec zd my o eg Dlat ie robić że wszystko będz Janka.

BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 6\VWHPRXWSULQWOQ ķDOH$]RUXWNQÈïQD]HZQÈWU]ĵ

 ĵ F]HNDÊ \QDV] ķ?Q$]RU]DF]           ULQWOQ BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB ķ]DWHP-DQNDVLÚJDSRSLORWDĵ  6\VWHPRXWS BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB UHPRWH  SUHVV%XWWRQ  BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 6\VWHPRXWSULQWOQ



6\VWHPRXWSULQWOQ ĵ?Q$]RUMHVW]SRZURWHPZGRPXĵ  ` `

118

Rozdział 2.



Gromadzenie wymagań

Dostarczenie nowych drzwiczek dla psa Dobre przypadki użycia, wymagania, ścieżka główna, ścieżki alternatywne i działający symulator; bez dwóch zdań, znajdujemy się na dobrej drodze do tworzenia wspaniałego oprogramowania. A teraz zainstalujmy nowe drzwiczki dla psa w domu Tadka i Janki.

Drzwiczki dla psa są odlotowe! Nie musimy już wychodzić z łóżka, żeby wypuścić Azora na zewnątrz, a dodatkowo drzwiczki zamykają się automatycznie. Życie może być piękne!

Teraz noce Tadka i Jan są już przerywane, dzi ki nie czemu stali się zadow ęki olonymi klientami.

Azor jest w domu, a króliki, owady i myszy — na zewnątrz.

Działająca aplikacja, zadowoleni klienci e To są właśnie te rezultaty, jaki dawno temu — na stronie 88 — mieliśmy nadzieję uzyskać. Jak ogromną różnicę oznaczają dobre wymagania, nieprawdaż?

Nie tylko sprawiliśmy, że Tadek i Janka stali się zadowolonymi klientami, lecz także zapewniliśmy, iż drzwiczki dla psa będą działać, nawet jeśli Azor zrobi coś nieprzewidzianego — na przykład zostanie na zewnątrz nieco dłużej, by się pobawić.

jesteś tutaj 

119

Kiedy wszystko przebiega zgodnie z planem

:\dcZKNUS_řcMSKLOd^KTOWXSM W tym tygodniu:

Poznajemy Szczęśliwą Ścieżkę Rusz głową!: Witaj, Ścieżko Główna. Szczęśliwa Ścieżka: Hm… wolę, jak mnie nazywają „Szczęśliwą Ścieżką”. Wiem, że w wielu książkach określają mnie terminem „Ścieżka Główna”, jednak zauważyłam, że znacznie więcej osób pamięta mnie, jeśli jestem określana jako „Szczęśliwa Ścieżka”. Rusz głową!: Oczywiście… przepraszam. Cóż, w każdym razie bardzo się cieszę, że jesteś dziś z nami; przybyłaś w samą porę. Szczęśliwa Ścieżka: Dziękuję… Ja zawsze jestem na czas; punktualność jest dla mnie bardzo ważna. Rusz głową!: Czy to prawda? Nigdy się nie spóźniasz? Szczęśliwa Ścieżka: Nie. Jeszcze nigdy mi się to nie zdarzyło. Oprócz tego zawsze pamiętam o wyznaczonych spotkaniach i zawsze na nie przychodzę. Ja nigdy nie popełniam błędów, nigdy nie zdarzają mi się nieprzewidziane sytuacje… Naprawdę możesz mieć pewność, że pojawię się zgodnie z planem, i to za każdym razem. Rusz głową!: To jest naprawdę poważne oświadczenie. Szczęśliwa Ścieżka: Cóż, jest to po prostu cecha mojej osobowości. Rusz głową!: I właśnie dzięki takim cechom zyskałaś swoją nazwę? Sprawiasz, że ludzie są szczęśliwi, gdyż zawsze zdążasz na czas i nigdy nie popełniasz błędów? Szczęśliwa Ścieżka: Nie, ale byłeś blisko. Nazywają mnie „Szczęśliwą Ścieżką”, gdyż kiedy mną podążasz, wszystko zawsze wychodzi tak, jakbyś sobie tego życzył. Jeśli idziesz tam, gdzie prowadzę, nigdy nic nie pójdzie źle. Rusz głową!: Muszę przyznać, że jestem nieco zaskoczony, że wokół Ciebie wszystko i zawsze dzieje się tak jak należy. Czy ty aby na pewno żyjesz w realnym świecie? Szczęśliwa Ścieżka: Cóż, nie zrozum mnie źle, w realnym świecie zdarzają się problemy. Ale kiedy tak się stanie, to pozwalam działać mojej kumpeli — Ścieżce Alternatywnej. Rusz głową!: Hm… chyba zaczynam rozumieć. Zatem problemy mogą występować, tylko jeśli się pojawią, to rozwiązuje je ta Ścieżka Alternatywna. Szczęśliwa Ścieżka: Właśnie tak. Ale ja się tym wszystkim nie przejmuję. Działam wtedy, gdy słońce jasno świeci, a wszystko przebiega zgodnie z oczekiwaniami i zamierzeniami. Rusz głową!: Super. Ależ to musi być satysfakcjonujące. Szczęśliwa Ścieżka: Cóż… w większości przypadków faktycznie tak jest. Ale sprawy naprawdę często przybierają niewłaściwy obrót. Wygląda na to, że już rzadko kto podąża mną od samego początku aż do końca. Zazwyczaj, w takim czy innym momencie, zawsze jest wykorzystywana Ścieżka Alternatywna; jednak świetnie się obie dogadujemy, więc nie ma z tym wszystkim większych problemów. Rusz głową!: Czy kiedykolwiek miałaś odczucie, że Ścieżka Alternatywna za bardzo się wtrąca? Mógłbym sobie wyobrazić tu potencjalne źródło konfliktów. Szczęśliwa Ścieżka: Nie, bynajmniej. Chodzi o to, że obie łączy nas jedno: doprowadzenie użytkowników do zamierzonego celu i zapewnienie ich zadowolenia. A jeśli zostaniemy dobrze i precyzyjnie zdefiniowane, to samo zaimplementowanie aplikacji będzie znacznie łatwiejsze. Rusz głową!: Cóż, Czytelnicy, widzieliście to na własne oczy. W następnym tygodniu postaramy się przeprowadzić wywiad ze Ścieżką Alternatywną i poznać jej punkt widzenia. Jednak, aż do tego czasu trzymajcie się Szczęśliwej Ścieżki i pamiętajcie, by zaplanować, że mogą wystąpić problemy!

120

Rozdział 2.

Gromadzenie wymagań :

7

?

7

JAKIE JEST MOJE PRZEZNACZENIE? 7

Poniżej, w lewej kolumnie zamieściliśmy kilka nowych terminów wprowadzonych w niniejszym rozdziale. W prawej kolumnie podaliśmy natomiast wyjaśnienia tych terminów oraz informacje o sposobie ich stosowania. Twoim zadaniem jest połączenie terminów z lewej kolumny z odpowiednimi opisami w prawej kolumnie.

Zewnętrzny _______

5R]SRF]\QDOLVWÚNURNöZRSLVDQ\FKZSU]\SDGNXXĝ\FLD%H]QLHJRUHDOL]DFMD SURFHVXRSLV\ZDQHJRSU]H]SU]\SDGHNXĝ\FLDQLJG\VLÚQLHUR]SRF]QLH

Przypadek ______

&RĂFRV\VWHPPXVLVSHïQLÊE\PRĝQDSRZLHG]LHÊĝHG]LDïDGREU]HLSRSUDZQLH

______ rozpoczęcia

,QIRUPXMHNLHG\SU]\SDGHNXĝ\FLD]RVWDQLH]DNRñF]RQ\%H]QLHJRSURFHV RSLV\ZDQ\SU]H]SU]\SDGHNXĝ\FLDPRĝHG]LDïDÊZQLHVNRñF]RQRĂÊ

Wymaganie

3RPDJDZSU]\JRWRZDQLXGREU\FKZ\PDJDñ2SRZLDGDKLVWRULÚRW\PMDNG]LDïD V\VWHP

_______ znaczenie

2NUHĂODFRVLÚG]LHMHJG\V\VWHPG]LDïD]JRGQLH]RF]HNLZDQLDPL7RZïDĂQLHWHQ SURFHV]D]Z\F]DMRSLVXMÈNOLHQFLRSRZLDGDMÈFF]HJRRF]HNXMÈRGV\VWHPX

Warunek _______

=DZV]HMHVWSLHUZV]\PNURNLHPZSU]\SDGNXXĝ\FLD

Ścieżka _______

%H]QLHJRSU]\SDGHNXĝ\FLDQLHEÚG]LHPLHÊĝDGQHJR]QDF]HQLD%H]QLHJR SU]\SDGHNXĝ\FLDQLJG\QLHVSHïQLSRNïDGDQ\FKZQLPQDG]LHL

ęły Och… niestety gdzieś nam zgin ych fragmenty terminów zamieszczon iesz będz m zate A . mnie kolu j w lewe y musiał nie tylko dopasować opis , zamieszczone w prawej kolumnie ci częś lecz także uzupełnić brakujące samych terminów.

jesteś tutaj 

121

Moje istnienie ma cel :

Rozwiązania ćwiczeń

7

?

7

JAKIE JEST MOJE PRZEZNACZENIE? 7

Poniżej, w lewej kolumnie zamieściliśmy kilka nowych terminów wprowadzonych w niniejszym rozdziale. W prawej kolumnie podaliśmy natomiast wyjaśnienia tych terminów oraz informacje o sposobie ich stosowania. Twoim zadaniem jest połączenie terminów z lewej kolumny z odpowiednimi opisami w prawej kolumnie.

czynnik Zewnętrzny _______

5R]SRF]\QDOLVWÚNURNöZRSLVDQ\FKZSU]\SDGNXXĝ\FLD%H]QLHJRUHDOL]DFMD SURFHVXRSLV\ZDQHJRSU]H]SU]\SDGHNXĝ\FLDQLJG\VLÚQLHUR]SRF]QLH

użycia Przypadek ______

&RĂFRV\VWHPPXVLVSHïQLÊE\PRĝQDSRZLHG]LHÊĝHG]LDïDGREU]HLSRSUDZQLH

Warunek rozpoczęcia ______

,QIRUPXMHNLHG\SU]\SDGHNXĝ\FLD]RVWDQLH]DNRñF]RQ\%H]QLHJRSURFHV RSLV\ZDQ\SU]H]SU]\SDGHNXĝ\FLDPRĝHG]LDïDÊZQLHVNRñF]RQRĂÊ

Wymaganie

3RPDJDZSU]\JRWRZDQLXGREU\FKZ\PDJDñ2SRZLDGDKLVWRULÚRW\PMDNG]LDïD V\VWHP

Oczywiste _______ znaczenie

2NUHĂODFRVLÚG]LHMHJG\V\VWHPG]LDïD]JRGQLH]RF]HNLZDQLDPL7RZïDĂQLHWHQ SURFHV]D]Z\F]DMRSLVXMÈNOLHQFLRSRZLDGDMÈFF]HJRRF]HNXMÈRGV\VWHPX

zakończenia Warunek _______

=DZV]HMHVWSLHUZV]\PNURNLHPZSU]\SDGNXXĝ\FLD

główna Ścieżka _______

%H]QLHJRSU]\SDGHNXĝ\FLDQLHEÚG]LHPLHÊĝDGQHJR]QDF]HQLD%H]QLHJR SU]\SDGHNXĝ\FLDQLJG\QLHVSHïQLSRNïDGDQ\FKZQLPQDG]LHL

Upewnij się, puste miejscaże uzupełniłeś wszystkie tak, jak my to zrobiliśmy.

122

Rozdział 2.

Gromadzenie wymagań

Zaostrz ołówek Nadszedł czas, by napisać nieco więcej przypadków użycia! Poniżej przedstawiliśmy trzech kolejnych potencjalnych klientów chętnych do zakupu drzwiczek dla psa. Twoim zadaniem jest napisanie, dla każdego z nich, przypadku użycia, który będzie rozwiązywać problemy konkretnego klienta.

Brysia bezustannie podchodzi do kuchennych drzwi do domu lub trąca noskiem okna w kuchni. Chciałabym mieć system, który zablokuje za mną drzwiczki dla psa i okna w kuchni, za każdym razem, gdy podam odpowiedni kod. Dzięki temu Brysia nie będzie mogła wyjść na zewnątrz.

Brysia

Firma PsieOdrzw współpracuje z ia ściśle fir ochroną mienia, mą zajmującą się sposób liczbę us zwiększając w ten wciąż rosnącej grług świadczonych i mogąc sprostać upie klientów ich dużym wymaganiom.

Kryśka

Brus cały czas szczeka, przez co nigdy nie wiem, czy naprawdę chce wyjść na spacer, czy nie. Czy możecie zbudować drzwiczki, które będą się otwierać automatycznie, gdy Brus zacznie je drapać pazurami?

Janek

Tech zawsze przynosi do domu masę błota. Chciałbym mieć drzwiczki, które będą się automatycznie zamykać za każdym razem, gdy Tech wyjdzie na zewnątrz, i będą zamknięte do momentu, aż nacisnę przycisk na pilocie.

Hanka

Brus

Tech

2GSRZLHG]L]QDMG]LHV]QDVWURQLH jesteś tutaj  123

Zatrzęsienie przypadków użycia

Zaostrz ołówek

Rozwiązanie

Nadszedł czas, by napisać nieco więcej przypadków użycia! Poniżej przedstawiliśmy trzech kolejnych potencjalnych klientów chętnych do zakupu drzwiczek dla psa. Twoim zadaniem jest napisanie dla każdego z nich przypadku użycia, który będzie rozwiązywać problemy konkretnego klienta.

Brysia bezustannie podchodzi do kuchennych drzwi do domu lub trąca noskiem okna w kuchni. Chciałabym mieć system, który zablokuje za mną drzwiczki dla psa i okna w kuchni, za każdym razem, gdy podam odpowiedni kod. Dzięki temu Brysia nie będzie mogła wyjść na zewnątrz.

Brysia

Przypadek użycia się jedynie z dw dla Kryśki składa wpisaniu kodu dróch kroków: po zwi i okna zostają zablokowane.

Kryśka

Drzwiczki dla Kryśki i jej Brysi 1. Kryśka wpisuje kod na specjalnej klawiaturze. 2. Drzwiczki dla psa oraz wszystkie okna w domu blokują się. lko Chociaż to są ty sama a, drzwiczki dla ps dnego Brysia nie ma ża ie an wpływu na dział u. em syst

Tech zawsze przynosi do domu masę błota. Chciałbym mieć drzwiczki, które będą się automatycznie zamykać za każdym razem, gdy Tech wyjdzie na zewnątrz, i będą zamknięte do momentu, aż nacisnę przycisk na pilocie.

rdzo azują się być ba Żądania Janka okgań, jakie postawili podobne do wyma dnym z aspektów Tadek i Janka. Jeh wymagań jest możliwość tworzenia dobryc będziemy dysponować już dy mina to, zauważenia, kie em, który przypo gotowym system t. czego chce klien

124

Rozdział 2.

Janek Tech

Gromadzenie wymagań

Brus cały czas szczeka, przez co nigdy nie wiem, czy naprawdę chce wyjść na spacer, czy nie. Czy możecie zbudować drzwiczki, które będą się otwierać automatycznie, gdy Brus zacznie je drapać pazurami?

Drzwiczki dla Hanki i jej psa Brusa 1. Brus podchodzi do drzwiczek i zaczyna je drapać. 2. Drzwiczki otwierają się. 3. Brus wychodzi na zewnątrz. 4. Drzwiczki zamykają się automatycznie. 5. Brus ponownie zaczyna drapać drzwiczki. 6. Drzwiczki ponownie się otwierają. 7. Brus wchodzi do domu. 8. Drzwiczki zamykają się automatycznie. ały jawnie punktów nie zost Niektóre z tych z Hankę, jednak powinieneś wymienione prze zastanawiając się nad sam je wymyślić,powinien być używany. tym, jak system

Hanka

Brus

Choć Janek narzekał, że Tech zazw błotem, to jednak wcale nie ozna yczaj brudzi się cza to, iż musi tak być za każdym razem… A zatem to będzie ścieżka alternatywna.

Drzwiczki dla Janka i jego psa Techa 1. (W jakiś sposób) Drzwiczki otwierają się. 2. Tech wychodzi na zewnątrz. 3. Drzwiczki zamykają się automatycznie. Okazuje się, że będziemy potrzebowali więcej informacji, by napisać ten przypadek użycia… Wygląda na to, że będziemy musieli zadać Jankowi kilka dodatkowych pytań.

4. Tech załatwia swoje potrzeby. 4.1. Tech brudzi sobie łapy błotem. 4.2. Janek wyciera Techowi łapy. 5. Janek naciska guzik na pilocie. 6. Drzwiczki otwierają się. 7. Tech wchodzi do domu. 8. Drzwiczki zamykają się automatycznie. jesteś tutaj  125

Trzy komponenty przypadków użycia

Więcej magnesików przypadków użycia &]\SDPLĐWDV]WU]\HOHPHQW\SU]\SDGNyZXİ\FLD"1DGV]HGâ F]DVE\ĤZ\NRU]\VWDâ]GRE\WċZF]HĤQLHMZLHG]Đ ZSUDNW\FH1DWHMRUD]QDVWĐSQ\FKVWURQDFK]QDMG]LHV] NLONDSU]\SDGNyZXİ\FLD7ZRLP]DGDQLHPMHVWGRSDVRZDQLH PDJQHVLNyZSU]HGVWDZLRQ\FKXGRâXVWURQ\LXPLHV]F]HQLHLFK SU]\RGSRZLHGQLFKSXQNWDFKNDİGHJR]SU]\SDGNyZXİ\FLD

Oczywiste znaczenie Początek i Koniec

Zewnętrzny czynnik inicjujący Więcej informacji o tych elementach przypadków użycia możesz znaleźć na stronie 102.

Drzwiczki dla Hanki i jej psa Brusa Brus drapie w drzwiczki, aby go wypuścić na zewnątrz. Drzwiczki otwierają się automatycznie i Brus wychodzi. Drzwiczki zamykają się po z góry określonym czasie. Brus idzie do swojej toalety, a następnie ponownie zaczyna drapać drzwiczki. Drzwiczki otwierają się automatycznie i Brus ponownie wchodzi do domu. Następnie drzwiczki zamykają się automatycznie. Jeśli Brus drapie w drzwi, lecz zostanie wewnątrz domu (bądź na zewnątrz), to może je podrapać ponownie, by drzwiczki się otworzyły.

Bez większych problemów powinieneś być w stanie przeanalizować alternatywny sposób zapisu przypadków użycia, pokazany na następnej stronie. Jeśli jednak będziesz mieć z tym jakieś problemy, to zajrzyj na koniec książki do Dodatku A.

Drzwiczki dla Kryśki i jej psa Brysi 1. Kryśka wpisuje kod na specjalnej klawiaturze. 2. Drzwiczki dla psa oraz wszystkie okna w domu blokują się. Użyj tych magnesików, by oznaczyć warunek rozpoczęcia przypadku użycia.

Użyj tych magnesików, by oznaczyć, co jest oczywistą wartością przypadku użycia.

126

Rozdział 2.

Gromadzenie wymagań

Drzwiczki dla Janka i jego psa Techa Ścieżka główna 1. Tech wychodzi na zewnątrz. 2. Drzwiczki zamykają się automatycznie. 3. Tech załatwia swoje potrzeby. 4. Janek naciska przycisk na pilocie. 5. Drzwiczki otwierają się. 6. Tech wchodzi do domu. 7. Drzwiczki zamykają się automatycznie.

Rozszerzenia

2GSRZLHG]L]QDMG]LHV]QDVWURQLH

Główny aktor: Tech Aktor drugoplanowy: Janek Warunki wstępne: Drzwiczki dla psa są otwarte, by Tech mógł w dowolnej chwili wyjść na zewnątrz. Cel: Tech załatwia swoje potrzeby i wraca do środka, jednak bez brudzenia domu zabłoconymi łapami.

3.1. Tech brudzi sobie łapy błotem. 3.2. Janek czyści łapy Techa.

Użyj tych magnesików do oznaczania warunków zakończenia przypadku użycia. W jaki sposób określisz, kiedy Twój przypadek użycia został zakończony?

W tym przypadku użyliśmy Azora do oznaczenia zewnętrznego czynnika inicjującego przypadek użycia, czyli zdarzenia, od którego cały przypadek użycia się rozpoczyna.

jesteś tutaj  127

Magnesiki przypadków użycia — Rozwiązania

Magnesiki przypadków użycia. Rozwiązania &]\SDPLĐWDV]WU]\HOHPHQW\SU]\SDGNyZXİ\FLD" 1DGV]HGâF]DVE\ĤZ\NRU]\VWDâ]GRE\WċZF]HĤQLHMZLHG]Đ ZSUDNW\FH1DWHMRUD]QDVWĐSQ\FKVWURQDFK]QDMG]LHV] NLONDSU]\SDGNyZXİ\FLD7ZRLP]DGDQLHPMHVWGRSDVRZDQLH PDJQHVLNyZSU]HGVWDZLRQ\FKXGRâXVWURQ\LXPLHV]F]HQLHLFK SU]\RGSRZLHGQLFKSXQNWDFKNDİGHJR]SU]\SDGNyZXİ\FLD

Oczywiste znaczenie Początek i Koniec

Zewnętrzny czynnik inicjujący

Drzwiczki dla Hanki i jej psa Brusa Brus drapie w drzwiczki, aby go wypuścić na zewnątrz. Drzwiczki otwierają się automatycznie i Brus wychodzi. Drzwiczki zamykają się po z góry określonym czasie. Brus idzie do swojej toalety, a następnie ponownie zaczyna drapać drzwiczki. Drzwiczki otwierają się automatycznie i Brus ponownie wchodzi do domu. Następnie drzwiczki zamykają się automatycznie. Jeśli Brus drapie w drzwi, lecz zostanie wewnątrz domu (bądź na zewnątrz), to może je podrapać ponownie, by drzwiczki się otworzyły.

W większości sposobów zapisu przypadków użycia ich oczywiste znaczenie nie jest podawane jawnie; a zatem będziesz musiał określić je samemu.

ia, Analizując takie przypadki użyc musisz zwrócić baczną uwagę na Jeśli wskazanie warunku zakończenia. żki w przypadku występują jakieś ście nek alternatywne, to zazwyczaj waru zakończenia nie będzie podany w ostatnim zdaniu opisu. Warunek rozpoczęcia oraz zewnętrzny czynnik inicjujący są zazwyczaj elementami pierwszego kroku przypadku użycia.

Drzwiczki dla Kryśki i jej psa Brysi 1. Kryśka wpisuje kod na specjalnej klawiaturze. 2. Drzwiczki dla psa oraz wszystkie okna w domu blokują się.

Brysia nie może wyjść na zewnątrz, jeśli pani jej na to nie pozwoli. 128

Rozdział 2.

Brus może wychodzić na zewnątrz i załatwiać swoje potrzeby bez zmuszania Hanki do otwierania i zamykania drzwiczek (ani nawet do nasłuchiwania, czy Brus szczeka, czy nie).

Niemal zawsze warunek zakończenia jest ostatnim krokiem przypadku użycia.

Gromadzenie wymagań

u użycia głównym W tym przypadk będzie zewnętrzny ze ws za aktorem y. czynnik inicjując

Drzwiczki dla Janka i jego psa Techa Główny aktor: Tech Aktor drugoplanowy: Janek Warunki wstępne: Drzwiczki dla psa są otwarte, by Tech mógł w dowolnej chwili wyjść na zewnątrz. Cel: Tech załatwia swoje potrzeby i wraca do środka, jednak bez brudzenia domu zabłoconymi łapami.

Ścieżka główna 1. Tech wychodzi na zewnątrz. 2. Drzwiczki zamykają się automatycznie. 3. Tech załatwia swoje potrzeby. 4. Janek naciska przycisk na pilocie. 5. Drzwiczki otwierają się. 6. Tech wchodzi do domu. 7. Drzwiczki zamykają się automatycznie.

Rozszerzenia 3.1. Tech brudzi sobie łapy błotem. 3.2. Janek czyści łapy Techa. Istotny jest ostatni kro ścieżki głównej, a nie k ostatni krok rozszerzenia.

jesteś tutaj  129

Potęga przypadków użycia

Zaostrz ołówek Jaka jest prawdziwa potęga przypadków użycia? Przekonałeś się już, w jaki sposób przypadki użycia pomagają Ci w tworzeniu wyczerpującej listy wymagań. Poniżej przedstawiliśmy kilka kolejnych przypadków użycia, które powinieneś sprawdzić. Twoim zadaniem jest sprawdzenie, czy listy wymagań przedstawione obok przypadków użycia są kompletne i wyczerpujące, czy też należy do nich dodać jeszcze jakieś wymagania.

Drzwiczki dla Kryśki i jej Brysi Przypadek użycia 1. Kryśka wpisuje kod na specjalnej klawiaturze. 2. Drzwiczki dla psa oraz wszystkie okna w domu blokują się.

Drzwiczki dla Kryśki i jej Brysi Lista wymagań Oto lista wymagań dla drzwiczek dla psa zamawianych przez Kryśkę. Czy po przeanalizowaniu przypadku użycia uważasz, że czegoś w niej brakuje lub któryś z jej punktów jest niekompletny? Jeśli tak, to dopisz do niej wszystkie dodatkowe wymagania, które według Ciebie powinny spełniać drzwiczki.

1. Klawiatura musi umożliwiać podanie czterocyfrowego kodu. 2. Klawiatura musi umożliwiać zablokowanie drzwiczek dla psa.

Czy pamiętasz Kr yśkę i jej Brysię?

130

Rozdział 2.

Gromadzenie wymagań

Hanka jest bardzo podekscytowana nowymi drzwiczkami dla psa. One po prostu muszą działać. Hanka wprost nie może się na nie doczekać!

Drzwiczki dla Hanki i jej psa Brusa Przypadek użycia 1. Brus podchodzi do drzwiczek i zaczyna je drapać. 2. Drzwiczki otwierają się. 3. Brus wychodzi na zewnątrz. 4. Drzwiczki zamykają się automatycznie. 5. Brus ponownie zaczyna drapać drzwiczki. 6. Drzwiczki ponownie się otwierają. 7. Brus wchodzi do domu. 8. Drzwiczki zamykają się automatycznie.

Drzwiczki dla Hanki i jej psa Brusa Lista wymagań 1. Drzwiczki muszą wykrywać, czy pies je drapie, czy nie. 2. Drzwiczki powinny się otwierać na polecenie (patrz punkt 1.). Czy czegoś tu nie brakuje? Teraz to Twoim zadaniem jest zadbanie o to, by Hanka była zadowolona ze swoich nowych drzwiczek dla psa.

2GSRZLHG]L]QDMG]LHV]QDVWURQLH jesteś tutaj 

131

Od przypadków użycia do wymagań

Zaostrz ołówek

Rozwiązanie

Jaka jest prawdziwa potęga przypadków użycia? Przekonałeś się już, w jaki sposób przypadki użycia pomagają Ci w tworzeniu wyczerpującej listy wymagań. Poniżej przedstawiliśmy kilka kolejnych przypadków użycia, które powinieneś sprawdzić. Twoim zadaniem jest sprawdzenie, czy listy wymagań przedstawione obok przypadków użycia są kompletne i wyczerpujące, czy też należy do nich dodać jeszcze jakieś wymagania.

Drzwiczki dla Kryśki i jej Brysi Przypadek użycia 1. Kryśka wpisuje kod na specjalnej klawiaturze. 2. Drzwiczki dla psa oraz wszystkie okna w domu blokują się.

Te wymagania nie są wyczerpujące… Kryśka chciałaby mieć możliwość zablokowania nie tylko drzwiczek dla psa, lecz także wszystkich okien w domu.

Drzwiczki dla Kryśki i jej Brysi Podanie tego wymagania było nieco trudniejsze… W przypadku użycia w ogóle nie poruszono problemu powrotu Brysi do domu, a zatem należałoby stwierdzić, że nie tylko lista wymagań była niekompletna, lecz także i sam przypadek użycia. Kryśka nie byłaby szczególnie zadowolona, gdyby okazało się, że nie może odblokować drzwiczek dla psa i okien w domu, nieprawdaż?

Lista wymagań 1. Klawiatura musi umożliwiać podanie czterocyfrowego kodu. 2. Klawiatura musi umożliwiać zablokowanie drzwiczek dla psa oraz wszystkich okien. 3. Klawiatura musi umożliwiać odblokowanie drzwiczek dla psa oraz wszystkich okien w domu.

Uważaj! Dobry przypadek użycia ułatwi tworzenie dobrej listy wymagań, jednak zły lub niekompletny przypadek użycia zwiększa prawdopodobieństwo, że stworzona na jego podstawie lista wymagań będzie ZŁA.

132

Rozdział 2.

Gromadzenie wymagań

Drzwiczki dla Hanki i jej psa Brusa Przypadek użycia 1. Brus podchodzi do drzwiczek i zaczyna je drapać. 2. Drzwiczki otwierają się. 3. Brus wychodzi na zewnątrz. 4. Drzwiczki zamykają się automatycznie. 5. Brus ponownie zaczyna drapać drzwiczki. 6. Drzwiczki ponownie się otwierają. 7. Brus wchodzi do domu. 8. Drzwiczki zamykają się automatycznie.

Drzwiczki dla Hanki i jej psa Brusa Lista wymagań 1. Drzwiczki muszą wykrywać, czy pies je drapie, czy nie. 2. Drzwiczki powinny się otwierać na polecenie (patrz punkt 1.). 3. Drzwiczki dla psa powinny zamykać się automatycznie. To samo wymaga podczas przygotonie pojawiło się dla psa zamawianwywania drzwiczek ych przez Tadka i Jankę.

jesteś tutaj  133

Przybornik projektanta

Narzędzia do naszego projektanckiego przybornika Analiza i projektowanie obiektowe służą do tworzenia wspaniałego oprogramowania, a takie oprogramowanie po prostu musi robić dokładnie to, czego chce użytkownik. W tym rozdziale poznałeś kilka narzędzi, które pomogą Ci zyskać pewność, że po prezentacji stworzonego systemu na twarzy klienta pojawi się szeroki uśmiech. Poniżej jeszcze raz przypomnieliśmy kilka narzędzi, które warto mieć pod ręką:

Wymagania Dobre wymagania zapewniają, że system będzie działać zgodnie z oczekiwaniami klienta.

Oto niektóre spośród najważniejszych narzędzi, jakie poznałeś w tym rozdziale.

Upewnij się, że wymagania obejmują wszystkie kroki przypadków użycia przygotowanych dla danego systemu. Przeanalizuj przypadki użycia, by spróbować odszukać informacje, o których klient zapomniał Ci powiedzieć. Przygotowane przypadki użycia wskażą wszelkie brakujące lub niekompletne wymagania, które będziesz musiał dodać do tworzonego systemu.

W kolejnych rozdziałach do tych dwóch kategorii* dodamy znacznie więcej narzędzi.

CELNE SPOSTRZEŻENIA Q

Wymagania to czynności, które system musi wykonywać, by można stwierdzić, że działa prawidłowo.

Q

Początkowe wymagania są zazwyczaj podawane przez klienta.

Q

Aby upewnić się, że dysponujemy dobrym zbiorem wymagań, należy opracować przypadki użycia dla tworzonego systemu.

Q

Przypadek użycia szczegółowo określa, co powinien robić system.

Q

Przypadek użycia ma tylko ściśle określony, jeden cel; niemniej jednak do tego celu może prowadzić wiele ścieżek.

Q

Dobry przypadek użycia posiada warunek rozpoczęcia oraz warunek zakończenia oraz oczywiste znaczenie dla użytkownika.

Q

Przypadek użycia to zwyczajna opowieść opisująca działanie systemu.

Q

Dla każdego celu, jakiemu ma służyć tworzony system, powinieneś napisać co najmniej jeden przypadek użycia.

Q

Po zakończeniu pracy nad przypadkami użycia będziesz mógł powtórnie przeanalizować i uzupełnić wymagania.

Q

Lista wymagań, która sprawia, że przypadki użycia są możliwe do zrealizowania, jest dobrym zbiorem wymagań.

Q

Tworzony system musi działać w rzeczywistym świecie, a nie wyłącznie w sytuacjach gdy wszystko będzie przebiegało zgodnie z oczekiwaniami.

Q

System powinien dysponować ścieżkami alternatywnymi, które będą wykonywane w przypadku, gdy sprawy przybiorą zły obrót.

Podstawy projektowania obiektowego Zasady projektowania obiektowego

* Czytelnicy książki Wzorce projektowe. Rusz głową! bez problemu rozpoznaje te kategorie… a to dlatego, iż analiza i projektowanie obiektowe są ściśle powiązane z wzorcami projektowymi.

134

Rozdział 2.

Gromadzenie wymagań

Magnesiki przypadków użycia. Rozwiązania 6NRĚF]\OLĤP\SUDFHQDGNODVċ'RJ'RRUD]DWHP]RVWDMH&LMHG\QLHQDSLVDQLH NODV\UHSUH]HQWXMċFHMSLORWDGRGU]ZLF]HN3RQLİHM]DF]ĐOLĤP\SLVDþNRGWHM NODV\MHGQDN7ZRLP]DGDQLHPMHVWJRGRNRĚF]\þ8]XSHâQLMNRGNODV\5HPRWH Xİ\ZDMċFZW\PFHOXPDJQHVLNyZXPLHV]F]RQ\FKXGRâXVWURQ\ 8ZDİDM«PRİHVLĐRND]DþİHQLHZV]\VWNLHPDJQHVLNL]NRGHPVċSRWU]HEQH

SXEOLFFODVV5HPRWH^

'RJ'RRU SULYDWHBBBBBBBBBBBBBBBBGRRU GRRU 'RJ'RRU SXEOLF5HPRWH BBBBBBBBBBBBBBBBBBBBBBBBBBB ^ WKLVGRRU GRRU ` SXEOLFYRLGSUHVV%XWWRQ ^ 6\VWHPRXWSULQWOQ ĵ1DFLĂQLÚWRSU]\FLVNQDSLORFLHĵ 

LV2SHQ GRRU LI BBBBBBBBBBBBBBBBBBBB ^ FORVH GRRUBBBBBBBBBB  LV2SHQ

`HOVH^

` ` `

ERROHDQ LV2SHQ WUXH WUXH IDOVH WUXH RSHQ IDOVH FORVH ERROHDQ 'RJ'RRU

RSHQ GRRUBBBBBBBBB 

Oto magnesiki, któ nie zostały użyt re e.

jesteś tutaj  135

136

Rozdział 2.

:\PDJDQLDXOHJDMÈ]PLDQRP

Kocham cię, jesteś doskonały… A teraz — zmień się Cóż ja na Boga w nim widziałam? Właśnie się dowiedziałam, że on nawet nie interesuje się wyścigami NASCAR.

Sądzisz, że dowiedziałeś się już wszystkiego o tym, czego chciał klient? Nie tak szybko… A zatem przeprowadziłeś rozmowy z klientem, zgromadziłeś wymagania, napisałeś przypadki użycia, napisałeś i dostarczyłeś klientowi odlotową aplikację. W końcu nadszedł czas na miłego, relaksującego drinka, nieprawdaż? Pewnie… aż do momentu gdy klient uzna, że tak naprawdę chce czegoś innego niż to, co Ci powiedział. Bardzo podoba mu się to, co zrobiłeś — poważnie! — jednak obecnie nie jest już w pełni usatysfakcjonowany. W rzeczywistym świecie wymagania zawsze się zmieniają; to Ty musisz sobie z tymi zmianami poradzić i pomimo nich zadbać o zadowolenie klienta.

to jest nowy rozdział  137

Witamy w raju

Jesteś bohaterem! Pyszna pina colada do picia, słońce przyjemnie ogrzewające Twoje ciało, zwitek Jesteś zmęczony zachowaniem Twojego pupilka? studolarowych banknotów wciśnięty za kąpielówki… właśnie tak wygląda życie Czy jesteś gotów wynająć osobę do wyprowadzania Twojego ulubieńca? programisty, który sprawił, że interesy firmy PsieOdrzwia kwitną. Drzwiczki, Masz dosyć drzwiczek dla psów, które zacinają się za każdym razem, gdy je otworzysz? które stworzyliście dla Tadka i Janki, okazały się niesamowitym sukcesem Sprzedano już ponad i obecnie Darek sprzedaje je klientom na całym świecie. 10 Nadszedł 000 czas by zadzwonić do firmy sztuk!

Darek zarabia na Twoim kodzie naprawdę duże pieniądze.

PsieOdrzwia 9 Profesjonalny montaż wykonywany u klienta przez naszych ekspertów. 9 Opatentowana stalowa konstrukcja. 9 Możliwość wyboru koloru i napisów.

Lecz wtem dzwoni telefon… Cześć, słuchaj… Wasze drzwiczki dla psa działają rewelacyjnie, ale chcielibyśmy, żebyście jeszcze trochę nad nimi popracowali…

9 Możliwość dostosowania wielkości.

Zadzwoń do nas już dziś:

0-800-998 9989

Ty: O rany, czy coś jest nie tak z drzwiczkami? Tadek i Janka: Nie, absolutnie nie. Drzwiczki działają dokładnie tak, jak opisałeś. Ty: No, ale musi być jakiś problem, prawda? Może drzwiczki nie zamykają się

odpowiednio szybko? A może nie działa przycisk na pilocie? Tadek i Janka: Nie, co ty… Wszystko działa równie dobrze jak tego dnia, kiedy zainstalowałeś cały system i pokazałeś nam, jak wszystko funkcjonuje. Ty: No to może Azor przestał szczekać, żebyście go wypuścili na zewnątrz? A… a sprawdziliście baterie w pilocie? Tadek i Janka: Słuchaj, naprawdę z drzwiczkami jest wszystko w porządku. Po

prostu mamy kilka pomysłów na modyfikacje, które chcielibyśmy wprowadzić… Ty: Ale skoro wszystko dobrze działa, to w czym problem? Tadek i Janka, którzy oje Tw beztrosko przerywają je. kac wa

138

Rozdział 3.

Wymagania ulegają zmianom Męczy nas ciągła konieczność nasłuchiwania, czy Azor szczeka… Czasami nawet go nie słyszymy i Azor załatwia swoje potrzeby w kuchni…

No i ciągle się nam gubi pilot do drzwiczek albo zostawiamy go w innym pokoju. Męczy mnie już to ciągłe naciskanie przycisku, by otworzyć drzwiczki.

Drzwiczki dla psa, dla Tadka i Janki, wersja 2.0 Jak (obecnie) działają drzwiczki 1. Azor szczeka, by właściciele wypuścili go na spacer. 2. Tadek lub Janka słyszą, że Azor szczeka. 3. Tadek lub Janka naciskają przycisk na pilocie. 4. Drzwiczki dla psa otwierają się. 5. Azor wychodzi na zewnątrz. 6. Azor załatwia swoje potrzeby. 6.1 Drzwi zamykają się automatycznie. 6.2. Azor szczeka, by właściciele wpuścili go do domu. 6.3. Tadek lub Janka słyszą szczekanie Azora (znowu). 6.4. Tadek lub Janka naciskają przycisk na pilocie. 6.5. Drzwiczki dla psa otwierają się (znowu). 7. Azor wraca z powrotem. 8. Drzwi automatycznie się zamykają. Czy nie dałoby się zrobić tak, żeby drzwiczki otwierały się automatycznie, kiedy Azor zaszczeka? W takim przypadku nie musielibyśmy w ogóle niczego robić, żeby go wypuścić. Rozmawialiśmy na ten temat i oboje uważamy, że to jest DOSKONAŁY pomysł!

jesteś tutaj  139

Klient ma rację

No i wracamy do tablicy A zatem nadszedł czas, by zabrać się do pracy nad poprawieniem drzwiczek dla psa zamówionych przez Tadka i Jankę. Musimy określić, w jaki sposób należy otwierać drzwiczki za każdym razem, gdy Azor zaszczeka. Zacznijmy od…

Zaraz, chwileczkę… To jest totalnie do bani… Przecież już zrobiliśmy dla nich działające drzwiczki i powiedzieli, że są w porządku. A teraz okazuje się, że jeszcze coś mamy w nich poprawiać i to tylko dlatego, że akurat mieli taki pomysł?

Klient ma zawsze rację Nawet jeśli wymagania ulegną zmianie, musisz być przygotowany do zaktualizowania aplikacji i zadbania o to, by działała zgodnie z oczekiwaniami klienta. Kiedy klient wymyśli nową funkcję, Twoim zadaniem będzie zmienić aplikację i spełnić potrzeby klienta.

Darek uwielbia takie sytuacje, gdyżi może zainkasować od Tadka i Jank nową sumkę za zmiany w aplikacji, które Ty wprowadzisz.

WYSIL

SZARE KOMÓRKI Właśnie poznałeś jedyny pewnik występujący w analizie i projektowaniu obiektowym. Jak myślisz, co nim jest?

140

Rozdział 3.

Wymagania ulegają zmianom

Jedyny pewnik analizy i projektowania obiektowego* :SRU]ÈGNX]DWHPFRMHVWW\PMHG\Q\PSHZQLNLHPQDNWöU\]DZV]H PRĝHV]OLF]\ÊSLV]ÈFRSURJUDPRZDQLH" Niezależnie od tego, gdzie pracujesz, jakie oprogramowanie tworzysz oraz jakiego języka programowania używasz, jaka jest jedyna stała, która zawsze będzie Ci towarzyszyć?

ANAIMZ (użyj lusterka, by odczytać odpowiedź)

1LH]DOHĪQLHRGWHJRMDNGREU]H]DSURMHNWXMHV]VZRMąDSOLNDFMĊWRZUD]]XSá\ZHP F]DVX]DZV]HEĊG]LHVLĊRQDUR]ZLMDüL]PLHQLDü3R]QDV]QRZHUR]ZLą]DQLD Z\VWĊSXMąF\FKZQLHMSUREOHPyZXĪ\ZDQHMĊ]\NLSURJUDPRZDQLDEĊGą HZROXRZDü7ZRLPLOLNOLHQFLZ\P\ĞOąQRZH]ZDULRZDQHZ\PDJDQLDNWyUH7\ EĊG]LHV]PXVLDáÄSRSUDZLDü´

Wymagania zawsze ulegają zmianom. Jeśli jednak

Zaostrz ołówek

Wymagania cały czas ulegają zmianom… czasami w połowie realizacji projektu, a czasami w chwili, gdy sądziłeś, że wszystko już jest gotowe. Poniżej zapisz kilka potencjalnych przyczyn, dla których mogą ulec zmianie wymagania w aktualnie pisanej przez Ciebie aplikacji.

Mój klient zdecydował, że chciałby, by aplikacja działała w inny sposób. Mój szef uznał, że aplikacja spisywałaby się lepiej, gdyby została napisana jako aplikacja internetowa, a nie tradycyjna.

stworzyłeś dobre przypadki użycia, to zazwyczaj będziesz w stanie szybko zmodyfikować aplikację i dostosować ją do nowych wymagań.

* Jeśli czytałeś książkę Wzorce projektowe. Rusz głową!, to zapewne ta strona będzie wyglądała znajomo. Autorzy tej książki w tak doskonały sposób opisali modyfikacje, że postanowiliśmy „pożyczyć” ich pomysły i jedynie ZMIENIĆ kilka słów tu i ówdzie. Dziękujemy wam, Beth i Ericu!

jesteś tutaj 

141

Dodanie ścieżki alternatywnej

Rozszerzyć możliwości drzwiczek Tadka i Janki o rozpoznawanie szczekania. Zaktualizuj diagram i dodaj do niego ścieżkę alternatywną przedstawiającą sytuację, w której

Ćwiczenie Azor zaczyna szczekać. Nowy system rozpoznawania dźwięków, oferowany przez Darka,

rozpoznaje szczekanie i drzwiczki otwierają się automatycznie. Pilot do drzwiczek wciąż powinien funkcjonować, dlatego z diagramu nie powinieneś niczego usuwać; po prostu dodaj do niego nową ścieżkę prezentującą, jak system działa w przypadku rozpoznania szczekania Azora.

Janka, otwórz drzwiczki dla Azora, bo ten pies inaczej nie przestanie szczekać!

Hau, hauu

1

Azor szczeka, by właściciele wypuścili go na spacer. 2 Tadek lub Janka słyszą, że Azor szczeka. 3 Tadek lub Janka naciskają przycisk na

pilocie.

7 Azor wchodzi do środka. 8 Drzwi zamykają się automatycznie.

142

Rozdział 3.

Wymagania ulegają zmianom

Hau! Hau! 6.1 Drzwiczki zamykają się automatycznie.

Teraz czuję się znacznie lepiej!

4 Drzwiczki otwierają się. 5 Azor wychodzi

6.2 Azor szczeka, by właściciele

na zewnątrz.

wpuścili go do śrdodka Znowu szczeka… Niechże ktoś wpuści tego psa do środka.

6 Azor załatwia

swoje potrzeby. 6.3 Tadek lub Janka słyszą

(ponownie) szczekanie Azora.

6.4 6.5 Drzwiczki zamykają się

Tadek lub Janka naciskają przycisk na pilocie

automatycznie.

jesteś tutaj  143

Aby sprostać potrzebom Azora

Rozszerzyć możliwości drzwiczek Tadka i Janki o rozpoznawanie szczekania. Zaktualizuj diagram i dodaj do niego ścieżkę alternatywną przedstawiającą sytuację, w której Azor zaczyna szczekać. Nowy system rozpoznawania dźwięków, oferowany przez Darka, Rozwiązania rozpoznaje szczekanie i drzwiczki otwierają się automatycznie. Pilot do drzwiczek wciąż powinien ćwiczeń funkcjonować, dlatego z diagramu nie powinieneś niczego usuwać; po prostu dodaj do niego nową ścieżkę prezentującą, jak system działa w przypadku rozpoznania szczekania Azora.

Janka, otwórz drzwiczki dla Azora, bo ten pies inaczej nie przestanie szczekać! 2 Tadek lub Janka słyszą, że Azor szczeka.

Hau,

3 Tadek lub Janka naciskają przycisk

na pilocie.

1

Azor szczeka, by właściciele wypuścili go na spacer.

musimy Do drzwiczek dla psa tem sys ny ow cud dodać ania. rozpoznawania szczek

rozpoznawania dźwięków 2.1 System „słyszy” szczekanie. rozpoznawania 3.1 System dźwięków wysyła żądanie otworzenia drzwiczek.

Większość diagramu pozostaje taka sama… Potrzebujemy jedynie tych dwóch dodatkowych kroków.

8 Drzwi zamykają się automatycznie.

144

Rozdział 3.

dku pierwszej Podobnie jak w przypa także i teraz ej, wn aty ern alt i ścieżk punkty, by możemy zastosować pod eżka alternatywna. ści to t jes że , zaznaczyć

7 Azor wchodzi do środka.

Wymagania ulegają zmianom

Hau! Hau!

6.1 Drzwiczki zamykają się

automatycznie. Teraz z cz czuję się znacznie czn lepiej!

4 Drzwiczki otwierają się.

jemy j potrzebu Także tutatkowych kilku doda kroków.

6.3.1

5 Azor wychodzi

na zewnątrz. 6.4.1

y właściciele w e 6.2 Azor szczeka, by wpuścili go do środka od

System rozpoznawania dźwięku „słyszy” szczekanie Azora (ponownie). System rozpoznawania dźwięku wysyła żądanie otworzenia drzwiczek.

6 Azor załatwia

Znowu szczeka… sz … Niechże ktoś oś wpuści tego t psa do środka.

swoje potrzeby. znajdują się na Ponieważ te kroki już zatem będziemy ej, wn ścieżce alternaty poziomu potrzebowali kolejnego nowej ścieżce na k kro też o teg dla — my za pomocą alternatywnej oznaczyliś r. cyf aż dwóch

ank słyszą 6.3 Tadek lub Janka (ponownie) e) sszczekanie Azora.

6.4 4

Tadek lub przycisk na pilocie 6.5 Drzwiczki zamykają się automatycznie.

jesteś tutaj  145

Jaką ścieżką mam podążać? Ale teraz mój przypadek użycia jest totalnie pogmatwany i trudny do zrozumienia. Wszystkie te ścieżki alternatywne sprawiają, że określenie, co się właściwie dzieje, przysparza poważnych problemów.

Ścieżką oryginalną? Ścieżką alternatywną? Kto to wie? Drzwiczki dla psa, dla Tadka i Janki, wersja 2.1 Jak (obecnie) działają drzwiczki Teraz pojawiły się alternatywne kroki dla oryginalnych kroków numer 2. i 3.

Teraz mamy już nawet kroki alternatywne do wcześniejszych kroków alternatywnych.

146

1. Azor szczeka, by właściciele wypuścili go na spacer. 2. Tadek lub Janka słyszą, że Azor szczeka. 2.1. System rozpoznawania dźwięków „słyszy” szczekanie Azora. 3. Tadek lub Janka naciskają przycisk na pilocie. 3.1. System rozpoznawania dźwięków wysyła żądanie otworzenia drzwiczek. 4. Drzwiczki dla psa otwierają się. 5. Azor wychodzi na zewnątrz. 6. Azor załatwia swoje potrzeby. 6.1 Drzwi zamykają się automatycznie. 6.2. Azor szczeka, by właściciele wpuścili go do domu. 6.3. Tadek lub Janka słyszą szczekanie Azora (znowu). 6.3.1. System rozpoznawania dźwięków „słyszy” szczekanie Azora (ponownie). 6.4. Tadek lub Janka naciskają przycisk na pilocie. 6.4.1. System rozpoznawania dźwięków wysyła żądanie otworzenia drzwiczek. 6.5. Drzwiczki dla psa otwierają się (znowu). 7. Azor wraca z powrotem. 8. Drzwi automatycznie się zamykają.

Rozdział 3.

Przedstawiliśmy je jako podpunkty listy, trzeba jednak pamiętać, że w rzeczywistości stanowią one zupełnie nową ścieżkę alternatywną.

Te podpunkty stanowią dodatkowy zestaw kroków, które mogą być wykonane…

…a te podpunkty, stanowią w rzeczywistości zupełnie inną ścieżkę przejścia przez przypadek użycia.

Wymagania ulegają zmianom Ciągle uważam, że ten przypadek użycia jest trudny do zrozumienia i nieczytelny. Wygląda na to, że Tadek i Janka zawsze słyszą, kiedy Azor szczeka, ale urządzenie do rozpoznawana dźwięków słyszy go tylko czasami. Ale to nie jest to, czego by chcieli Tadek i Janka.

m mówi Czy rozumiesz, o czy i Janki Gerard? Pomysł Tadka więcej polegał na tym, by już czy ać, nie musieli nasłuchiw . nie czy a, zek szc Azor

Drzwiczki dla psa, dla Tadka i Janki, wersja 2.1 W rzeczywistości w nowym przypadku użycia chodzi nam o to, by pokazać, że może być wykonany krok 2. lub krok 2.1…

…a następnie krok 3. lub krok 3.1.

Tutaj może być wykonany krok 6.3 lub krok 6.3.1…

ok 6.4 …a tu kr . .1 .4 6 b lu

Jak (obecnie) działają drzwiczki 1. Azor szczeka, by właściciele wypuścili go na spacer. 2. Tadek lub Janka słyszą, że Azor szczeka. 2.1. System rozpoznawania dźwięków „słyszy” szczekanie Azora. 3. Tadek lub Janka naciskają przycisk na pilocie. 3.1. System rozpoznawania dźwięków wysyła żądanie otworzenia drzwiczek. 4. Drzwiczki dla psa otwierają się. 5. Azor wychodzi na zewnątrz. 6. Azor załatwia swoje potrzeby. 6.1 Drzwi zamykają się automatycznie. 6.2. Azor szczeka, by właściciele wpuścili go do domu. 6.3. Tadek lub Janka słyszą szczekanie Azora (znowu). 6.3.1. System rozpoznawania dźwięków „słyszy” szczekanie Azora (ponownie). 6.4. Tadek lub Janka naciskają przycisk na pilocie. 6.4.1. System rozpoznawania dźwięków wysyła żądanie otworzenia drzwiczek. 6.5. Drzwiczki dla psa otwierają się (znowu). 7. Azor wraca z powrotem. 8. Drzwi automatycznie się zamykają. jesteś tutaj  147

Zapisz to w dowolnej postaci

Przypadki użycia muszą być zrozumiałe i użyteczne przede wszystkim dla Ciebie Jeśli masz problemy ze zrozumieniem przygotowanego przypadku użycia, to po prostu zapisz go w jakiś inny sposób. Istnieją setki różnych sposobów zapisywania przypadków użycia, jednak najważniejsze jest to, by był on pojmowalny dla Ciebie, Twojego zespołu oraz osób, którym musisz go wytłumaczyć. Spróbujmy zatem zapisać przypadek użycia ze strony 147 w bardziej przejrzysty i zrozumiały sposób.

Teraz dodaliśmy nagłówek informujący, że wszystkie kroki umieszczone w lewej kolumnie należą do ścieżki głównej.

Jeśli istnieje tylko jeden krok, to podczas realizacji sekwencji czynności opisanych przez przypadek użycia krok ten zawsze zostanie wykonany.

Kroki podrzędne są opcjonalne… Możesz ich użyć, choć nie jest to wcale wymagane. Pomimo to umieściliśmy je w lewej kolumnie, gdyż nie stanowią one zamienników dla innych kroków ścieżki głównej. Niezależnie od tego, w jaki sposób zostanie wykonany przypadek użycia, zawsze jego realizacja zakończy się na kroku 8. należącym do ścieżki głównej.

148

Rozdział 3.

Wszystkie kroki, które mogą zostać wykonane zamiast jakichś kroków ścieżki głównej, umieściliśmy z prawe j strony.

Drzwiczki dla psa, dla Tadka i Janki, wersja 2.2 Jak działają drzwiczki Ścieżki alternatywne

Ścieżka główna 1. Azor szczeka, by właściciele wypuścili go na spacer. 2. Tadek lub Janka słyszą, że Azor szczeka.

2.1. System rozpoznawania dźwięków „słyszy” szczekanie Azora. 3.1. System rozpoznawania dźwięków wysyła żądanie otworzenia drzwiczek.

3. Tadek lub Janka naciskają przycisk t nieco bardziej Ten sposób zapisu jes : możemy na pilocie. iały zum zro i przejrzysty krok 2.1; wykonać krok 2. LUB B krok 3.1. 4. Drzwiczki dla psa otwierają się. a następnie krok 3. LU 5. Azor wychodzi na zewnątrz. 6. Azor załatwia swoje potrzeby. 6.1 Drzwi zamykają się automatycznie. 6.2. Azor szczeka, by właściciele wpuścili go do domu. 6.3. Tadek lub Janka słyszą 6.3.1. System rozpoznawania szczekanie Azora (znowu). dźwięków „słyszy” szczekanie Azora (ponownie). 6.4.1. System rozpoznawania 6.4. Tadek lub Janka naciskają dźwięków wysyła żądanie przycisk na pilocie. 6.5. Drzwiczki dla psa otwierają się (znowu). otworzenia drzwiczek. 7. Azor wraca z powrotem. Kroki przedstawione z prawej stro krok 6.3 oraz 6.4. Podczas reali ny mogą zastąpić zacj 8. Drzwi automatycznie się zamykają. opisywanego przez przypadek użyc i procesu wykonać tylko jeden z wariantów: ia można z lewej LUB krok zapisany z praw krok zapisany ej strony.

Jeśli naprawdę możemy tworzyć przypadki użycia w dowolny sposób, to czy możemy zmodyfikować je w taki sposób, by system rozpoznawania szczekania stał się elementem głównej ścieżki? Bo przecież chcemy, by w większości przypadków system działał właśnie w taki sposób, nieprawdaż?

Wymagania ulegają zmianom

Doskonały pomysł! Ścieżka główna reprezentuje ten sposób działania systemu, jaki ma być realizowany w większości przypadków. Tadek i Janka zapewne życzyliby sobie, by system rozpoznawania dźwięku otwierał drzwiczki dla Azora częściej niż oni przy użyciu pilota, dlatego też takie zmodyfikowanie ścieżki głównej jest dobrym rozwiązaniem:

Drzwiczki dla psa, dla Tadka i Janki, wersja 2.3 Jak działają drzwiczki

Teraz kroki związane z wykorzystaniem systemu rozpoznawania dźwięku zostały usunięte ze ścieżki alternatywnej i dołączone do ścieżki głównej.

Ścieżka główna Ścieżki alternatywne 1. Azor szczeka, by właściciele 2.1. Tadek lub Janka słyszą, wypuścili go na spacer. że Azor szczeka. 2. System rozpoznawania dźwięków 3.1. Tadek lub Janka naciskają „słyszy” szczekanie Azora. przycisk na pilocie. 3. System rozpoznawania dźwięków wysyła żądanie otworzenia drzwiczek. 4. Drzwiczki dla psa otwierają się. Obecnie Tadek i Janka będą używ pilota raczej sporadycznie, dlate ali go 5. Azor wychodzi na zewnątrz. umieszczenie wszystkich kroków związanych z pilotem w ścieżkac h 6. Azor załatwia swoje potrzeby. alternatywnych będzie prawidłow ym rozwiązaniem. 6.1. Drzwi zamykają się automatycznie. 6.2. Azor szczeka, by właściciele wpuścili go do domu. 6.3. System rozpoznawania 6.3.1. Tadek lub Janka słyszą dźwięków „słyszy” szczekanie szczekanie Azora (znowu). Azora (ponownie). 6.4. System rozpoznawania dźwięków 6.4.1. Tadek lub Janka naciskają wysyła żądanie otworzenia drzwiczek. przycisk na pilocie. 6.5. Drzwiczki dla psa otwierają się (znowu). 7. Azor wraca z powrotem. 8. Drzwi automatycznie się zamykają. jesteś tutaj  149

Docieramy do celu

Od startu do mety: jeden scenariusz Uwzględniając wszystkie ścieżki alternatywne zamieszczone w nowym przypadku użycia, istnieje wiele sposobów na wypuszczenie Azora na spacer i późniejsze wpuszczenie go do domu. Poniżej przedstawiliśmy jeden z możliwych scenariuszy.

Wykorzystajmy tę alternatywną i po ścieżkę Tadkowi i Jance zwólmy drzwiczki przy użotworzyć yciu pilota.

Drzwiczki dla psa, dla Tadka i Janki, wersja 2.3 Jak działają drzwiczki Wszystkie możliwe ścieżki realizacji tego przypadku użycia rozpoczynają się od kroku 1.

Poruszanie się zgodnie z kolejnością wyznaczaną przez strzałki pozwala na przejście danego przypadku użycia konkretną ścieżką. Taka ścieżka jest nazywana scenariuszem. W jednym przypadku użycia można zazwyczaj wyróżnić kilka scenariuszy.

150

Ścieżka główna Ścieżki alternatywne 1. Azor szczeka, by właściciele wypuścili go na spacer. 2. System rozpoznawania dźwięków 2.1. Tadek lub Janka słyszą, „słyszy” szczekanie Azora. że Azor szczeka. 3. System rozpoznawania dźwięków 3.1. Tadek lub Janka naciskają wysyła żądanie otworzenia drzwiczek przycisk na pilocie. 4. Drzwiczki dla psa otwierają się. 5. Azor wychodzi na zewnątrz. W tym przypadku eżkę, zastosujemy tę oto ściję, uac syt cą ują ent rez 6. Azor załatwia swoje potrzeby. rep na w której Azor pozostaje cznym 6.1. Drzwi zamykają się automatycznie. zewnątrz po automaty . zamknięciu drzwiczek 6.2. Azor szczeka, by właściciele wpuścili go do domu. 6.3. System rozpoznawania 6.3.1. Tadek lub Janka słyszą dźwięków „słyszy” szczekanie szczekanie Azora (znowu). Azora (ponownie). 6.4. System rozpoznawania 6.4.1. Tadek lub Janka naciskają dźwięków wysyła żądanie przycisk na pilocie. otworzenia drzwiczek. ścieżkę 6.5. Drzwiczki dla psa otwierają się (znowu). Także teraz zastosujemyTadek i Janka alternatywną, w której otwierają drzwiczki pilotem. 7. Azor wraca z powrotem. 8. Drzwi automatycznie się zamykają.

Rozdział 3.

Realizacja przypadku użycia zaws zakończy się na kroku 8. — Azor ze zawsze bezpiecznie wróci do dom u.

Wymagania ulegają zmianom Nie istnieją

głupie pytania

P: Rozumiem ścieżkę główną naszego

P: A zatem jak nazwać sytuację,

P: Czy w jednym przypadku

przypadku użycia, ale czy możecie mi jeszcze raz wytłumaczyć, czym jest ścieżka alternatywna?

w której będą istnieć aż dwie różne ścieżki prowadzące przez pewien fragment przypadku użycia?

użycia może być więcej niż jedna ścieżka alternatywna?

O: Ścieżka alternatywna to jeden lub kilka

O: Cóż, tak naprawdę to jest to

kroków, które mogą, lecz nie muszą, zostać wykonane lub które tworzą alternatywny sposób przejścia przez przypadek użycia. Ścieżki alternatywne mogą zawierać dodatkowe kroki dołączane do ścieżki głównej bądź też kroki pozwalające na dotarcie do celu w sposób całkowicie odmienny niż ten, jaki zapewnia ścieżka główna.

P: A zatem, kiedy Azor wyjdzie na zewnątrz i utknie tam, będzie to element ścieżki alternatywnej?

tylko inny rodzaj ścieżki alternatywnej. Kiedy Azor zacznie szczekać, to jedna ścieżka reprezentuje sytuację, w której Tadek lub Janka usłyszą go i otworzą drzwiczki, a druga — sytuację, w której drzwiczki automatycznie otworzy system rozpoznawania szczekania. Jednak system jest zaprojektowany w taki sposób, iż może zostać wykonana tylko jedna ścieżka, czyli drzwiczki dla psa zostaną otworzone albo przy użyciu pilota, albo przez system rozpoznawania dźwięków, lecz nigdy przez oba te zdarzenia jednocześnie.

O: Oczywiście. W jednym przypadku

użycia może być kilka ścieżek alternatywnych udostępniających dodatkowe kroki oraz wiele różnych ścieżek od warunku rozpoczęcia do warunku zakończenia. Może także istnieć ścieżka alternatywna prowadząca do szybszego zakończenia przypadku użycia… Jednak w przypadku drzwiczek dla psa zamówionych przez Tadka i Jankę nie musimy uciekać się aż do tak złożonych rozwiązań.

O: Owszem. W naszym przypadku użycia

kroki 6.1, 6.2, 6.3, 6.4 oraz 6.5 tworzą ścieżkę alternatywną. Są to kroki dodatkowe, które system może wykonać; są one potrzebne wyłącznie w przypadku, gdy Azor zostanie na zewnątrz po automatycznym zamknięciu się drzwiczek. Jednak jest to ścieżka alternatywna, gdyż Azor nie zawsze załatwia swoje potrzeby aż tak długo — system może przejść bezpośrednio z kroku 6. do kroku 7.

Kompletna ścieżka prowadząca przez cały przypadek użycia, od jego pierwszego do ostatniego kroku, jest nazywana scenariuszem.

P: I do tego celu używamy kroków podrzędnych, oznaczonych jako 6.1 i 6.2?

Większość przypadków użycia

O: Dokładnie. Dzieje się tak, gdyż ścieżka

posiada kilka różnych scenariuszy,

alternatywna zawierająca kroki dodatkowe jest po prostu zbiorem czynności, które mogą zostać wykonane jako fragmenty innego kroku ścieżki głównej. Kiedy Azor zostaje na zewnątrz zbyt długo, to na ścieżce głównej zostają wykonane kroki 6. i 7., dlatego też ścieżka alternatywna zaczyna się od kroku o numerze 6.1 i kończy krokiem 6.5. Wszystkie one są opcjonalnymi elementami kroku 6.

jednak wszystkie one realizują ten sam cel użytkownika.

jesteś tutaj 

151

Ścieżki alternatywne są opcjonalne

:\dcZKNUS_řcMSKLOd^KTOWXSM W tym tygodniu:

Wyznanie Ścieżki Alternatywnej Rusz głową!: Witamy, Ścieżko Alternatywna. Słyszeliśmy, że ostatnio nie jesteś zbyt szczęśliwa. Powiedz nam, co takiego się dzieje? Ścieżka Alternatywna: Po prostu czasami mam wrażenie, że nie jestem dość często włączana w bieg zdarzeń. Chodzi mi o to, że trudno beze mnie stworzyć dobry przypadek użycia, jednak wygląda na to, że niemal zawsze jestem ignorowana. Rusz głową!: Ignorowana? Ale sama właśnie powiedziałaś, że znajdujesz się niemal w każdym przypadku użycia. Zabrzmiało to tak, jakbyś była naprawdę ważna! Ścieżka Alternatywna: Może i zabrzmiało. Jednak nawet jeśli jestem fragmentem przypadku użycia, to i tak mogę zostać pominięta i zastąpiona jakimś innym zbiorem kroków. To naprawdę paskudne… To tak, jakby mnie tam w ogóle nie było! Rusz głową!: Czy możesz nam to wytłumaczyć na jakimś przykładzie? Ścieżka Alternatywna: Właśnie kilka dni temu byłam fragmentem przypadku użycia opisującego kupowanie płyt CD w nowym internetowym sklepie muzycznym — Muzykologia. Byłam tym tak bardzo poruszona… A okazało się, że obsługuję sytuacje, w których karta kredytowa klienta została odrzucona. Rusz głową!: Hm… ale to chyba naprawdę ważne zadanie! Zatem w czym problem? Ścieżka Alternatywna: Cóż… może. Sądzę, że to faktycznie istotne zadanie, jednak okazuje się, że zawsze jestem pomijana. Wygląda to tak, jak gdyby wszyscy składali zamówienia, a ich karty kredytowe były zawsze akceptowane. Chociaż byłam częścią przypadku użycia, to nie należałam do najczęściej realizowanych scenariuszy. Rusz głową!: Aha, rozumiem. Czyli jeśli czyjaś karta kredytowa nie została odrzucona, to w ogóle nie byłaś wykonywana. Ścieżka Alternatywna: Właśnie! A specjaliści od finansów i bezpieczeństwa wprost mnie uwielbiali; wciąż zachwycali się, jak bardzo jestem ważna dla firmy… Ale kto by chciał siedzieć cały czas na stołku i próżnować? Rusz głową!: Chyba zaczynam rozumieć. Niemniej jednak wciąż pomagasz temu przypadkowi użycia, prawda? Nawet jeśli nie jesteś nieustannie używana, to jednak od czasu do czasu musisz wkroczyć do akcji. Ścieżka Alternatywna: To prawda, wszyscy mamy ten sam cel. Po prostu nie zdawałam sobie sprawy z tego, że mogę być tak ważna dla przypadku użycia, a jednocześnie niemal całkowicie ignorowana. Rusz głową!: Cóż, tylko pomyśl… Przypadek użycia nigdy nie byłby kompletny bez ciebie. Ścieżka Alternatywna: No tak… Kroki 3.1 i 4.1 wciąż mi to powtarzają. Oczywiście, one są częścią ścieżki alternatywnej wykonywanej wtedy, gdy klienci mają już założone konto w naszym sklepie, i dlatego są wykonywane cały czas. Łatwo im mówić! Rusz głową!: Trzymaj się. Wszyscy wiemy, że jesteś bardzo ważną częścią przypadku użycia.

152

Rozdział 3.

Wymagania ulegają zmianom

Zaostrz ołówek Ile scenariuszy można wyróżnić w przypadku użycia opisującym drzwiczki dla Tadka i Janki? Na ile sposobów można przejść przypadek użycia opisujący działanie drzwiczek zamówionych przez Tadka i Jankę? Pamiętaj, że czasami konieczne jest wykorzystanie jednej z istniejących ścieżek alternatywnych, a niekiedy wszystkie ścieżki alternatywne można w ogóle pominąć.

Drzwiczki dla psa, dla Tadka i Janki, wersja 2.3 Jak działają drzwiczki

1.

1, 2.1, 3.1, 4, 5, 6.1, 6.2, 6.3.1, 6.4.1, 6.5, 7, 8

2.1. Tadek lub Janka słyszą, że Azor szczeka. 3.1. Tadek lub Janka naciskają przycisk na pilocie.

6.3.1. Tadek lub Janka słyszą szczekanie Azora (znowu). 6.4.1. Tadek lub Janka naciskają przycisk na pilocie.

6SUDZGěQDV]HRGSRZLHG]LSRGDQHQDQDVWÚSQHMVWURQLH

Aby pomóc Ci w rozwiązaniu tego zadania, zapisaliśmy wszystkie kroki, jakie są wykonywane w scenariuszu przedstawionym na powyższym rysunku.

Ścieżki alternatywne

Ścieżka główna 1. Azor szczeka, by właściciele wypuścili go na spacer. 2. System rozpoznawania dźwięków „słyszy” szczekanie Azora. 3. System rozpoznawania dźwięków wysyła żądanie otworzenia drzwiczek. 4. Drzwiczki dla psa otwierają się. 5. Azor wychodzi na zewnątrz. 6. Azor załatwia swoje potrzeby. 6.1 Drzwi zamykają się automatycznie. 6.2. Azor szczeka, by właściciele wpuścili go do domu. 6.3. System rozpoznawania dźwięków „słyszy” szczekanie Azora (ponownie). 6.4. System rozpoznawania dźwięków wysyła żądanie otworzenia drzwiczek. 6.5. Drzwiczki dla psa otwierają się (znowu). 7. Azor wraca z powrotem. 8. Drzwi automatycznie się zamykają.

5.

2.

6.

3.

7.

4.

8. Być może nie będziesz potrzebował wszystkich tych pustych miejsc.

jesteś tutaj  153

Jeden przypadek użycia, wiele scenariuszy

Zaostrz ołówek

Rozwiązanie

Ile scenariuszy można wyróżnić w przypadku użycia opisującym drzwiczki dla Tadka i Janki?

Na ile sposobów można przejść przypadek użycia opisujący działanie drzwiczek zamówionych przez Tadka i Jankę? Pamiętaj, że czasami konieczne jest wykorzystanie jednej z istniejących ścieżek alternatywnych, a niekiedy wszystkie ścieżki alternatywne można w ogóle pominąć.

Drzwiczki dla psa, dla Tadka i Janki, wersja 2.3 Jak działają drzwiczki Ścieżka główna 1. Azor szczeka, by właściciele wypuścili go na spacer. 2. System rozpoznawania dźwięków „słyszy” szczekanie Azora. 3. System rozpoznawania dźwięków wysyła żądanie otworzenia drzwiczek. 4. Drzwiczki dla psa otwierają się. 5. Azor wychodzi na zewnątrz. 6. Azor załatwia swoje potrzeby. 6.1 Drzwi zamykają się automatycznie. 6.2. Azor szczeka, by właściciele wpuścili go do domu. rozpoznawania dźwięków System 6.3. „słyszy” szczekanie Azora (ponownie). 6.4. System rozpoznawania dźwięków wysyła żądanie otworzenia drzwiczek. 6.5. Drzwiczki dla psa otwierają się (znowu). 7. Azor wraca z powrotem. 8. Drzwi automatycznie się zamykają. główna To jest ścieżka ia. yc uż u dk pa przy

Te dwa scenariusze nie wykorzystują ścieżki alternatywnej reprezentującej sytuację, gdy Azor zostaje na zewnątrz po zamknięciu drzwiczek.

154

Ścieżki alternatywne 2.1. Tadek lub Janka słyszą, że Azor szczeka. 3.1. Tadek lub Janka naciskają przycisk na pilocie.

6.3.1. Tadek lub Janka słyszą szczekanie Azora (znowu). 6.4.1. Tadek lub Janka naciskają przycisk na pilocie.

iesz Jeśli wykonasz krok 6.3.1, to będz . także musiał wykonać krok 6.4.1

1.

1, 2.1, 3.1, 4, 5, 6.1, 6.2, 6.3.1, 6.4.1, 6.5, 7, 8

5.

1, 2, 3, 4, 5, 6, 6.1, 6.2, 6.3.1, 6.4.1, 6.5, 7, 8

2.

1, 2, 3, 4, 5, 6, 7, 8

6.

1, 2.1, 3.1, 4, 5, 6, 6.1, 6.2, 6.3.1, 6.4.1, 6.5, 7, 8

3.

1, 2.1, 3.1, 4, 5, 6, 7, 8

7.

1, 2, 3, 4, 5, 6, 6.1, 6.2, 6.3, 6.4, 6.5, 7, 8

4.

1, 2.1, 3.1, 4, 5, 6, 6.1, 6.2, 6.3, 6.4, 6.5, 7, 8

8.

Rozdział 3.

ze Jeśli wykonasz krok 2.1, to zaws krok 3.1. e takż nać wyko iał mus iesz będz

Wymagania ulegają zmianom

Przygotujmy się do kodowania… Teraz, kiedy dokończyliśmy prace nad przypadkiem użycia i określiliśmy wszystkie możliwe scenariusze korzystania z drzwiczek dla psa, jesteśmy już gotowi, by napisać kod, który spełni wszystkie nowe wymagania Tadka i Janki. Zastanówmy się, co ten kod powinien robić…

Uważam, że powinniśmy jeszcze raz sprawdzić naszą listę wymagań. Skoro zmieniły się wymagania Tadka i Janki, to także nasza lista wymagań mogła ulec modyfikacji, nieprawdaż?

Jakakolwiek zmiana w przypadku użycia powoduje, że konieczne będzie ponowne sprawdzenie listy wymagań. Pamiętaj, że podstawowym celem tworzenia dobrych przypadków użycia jest utworzenie dobrej listy wymagań. A zatem zmiana przypadku użycia może oznaczać także zmianę listy wymagań. Przyjrzyjmy się więc naszej dotychczasowej liście wymagań i sprawdźmy, czy nie powinniśmy jej uzupełnić.

Drzwiczki dla psa, dla Tadka i Janki, wersja 2.2 Lista wymagań 1. Górna krawędź otworu drzwiczek musi być umieszczona co najmniej na wysokości 30 centymetrów. 2. Naciśnięcie przycisku na pilocie powoduje otworzenie drzwiczek, jeśli te są zamknięte, lub ich zamknięcie, jeśli są otwarte. 3. Po otworzeniu drzwiczek powinny one zostać automatycznie zamknięte, jeśli wcześniej nie zostaną zamknięte przez użytkownika.

Dopisz tu wszelkie dodatkowe wymagania, które określiłeś podczas analizy różnych scenariuszy działania drzwiczek dla psa, przedstawionych na stronie 154.

jesteś tutaj  155

Modyfikacja listy wymagań

Uzupełnienie listy wymagań A zatem musimy obsłużyć kilka nowych ścieżek alternatywnych, co będzie wymagało dodania do naszej dotychczasowej listy wymagań kilku nowych punktów. Z przedstawionego poniżej przypadku użycia wykreśliliśmy wszystkie punkty, które już są obsługiwane przez wymagania znajdujące się na liście. Wygląda na to, że będziemy musieli dodać do listy wymagań kilka nowych punktów.

Drzwiczki dla psa, dla Tadka i Janki, wersja 2.3 Jak działają drzwiczki Te punkty wiążą się tak naprawdę z dwoma wymaganiami: „usłyszeniem” szczekania oraz otworzeniem drzwiczek dla psa.

To są zupełnie inne kroki niż 2. i 3., jednak wiążą się z nimi dokładnie takie same wymagania.

Ścieżka główna

Ścieżki alternatywne

1. Azor szczeka, by właściciele wypuścili go na spacer. 2. System rozpoznawania dźwięków 2.1. Tadek lub Janka słyszą, że Azor szczeka. Azora. ie szczekan „słyszy” w lub Janka naciskają dźwiękó Tadek 3.1. awania rozpozn 3. System na pilocie. przycisk k. drzwicze ia otworzen wysyła żądanie się. ą 4. Drzwiczki dla psa otwieraj Wymagania związane 5. Azor wychodzi na zewnątrz. z większością kroków znajdujących 6. Azor załatwia swoje potrzeby. się na ścieżce głównej określiliśmy już w poprzednim 6.1 Drzwi zamykają się automatycznie. rozdziale. 6.2. Azor szczeka, by właściciele wpuścili go do domu. 6.3.1. Tadek lub Janka słyszą 6.3. System rozpoznawania dźwięków szczekanie Azora (znowu). „słyszy” szczekanie Azora (ponownie). w dźwiękó lub Janka naciskają awania Tadek rozpozn 6.4.1. 6.4. System k. drzwicze ia otworzen wysyła żądanie przycisk na pilocie. 6.5. Drzwiczki dla psa otwierają się (znowu). 7. Azor wraca z powrotem. 8. Drzwi automatycznie się zamykają.

Pamiętaj, że te kroki, znajdujące się obecnie na ścieżce alternatywnej, w przypadku użycia przedstawionym w poprzednim rozdziale znajdowały się na ścieżce głównej…

…dlatego związane z nimi wymagania już są na naszej liście.

Drzwiczki dla psa, dla Tadka i Janki, wersja 2.3 Lista wymagań

Oto dwa nowe wymagania, które musimy dodać do list y.

156

Rozdział 3.

1. Górna krawędź otworu drzwiczek musi być umieszczona co najmniej na wysokości 30 centymetrów. 2. Naciśnięcie przycisku na pilocie powoduje otworzenie drzwiczek, jeśli te są zamknięte, lub ich zamknięcie, jeśli są otwarte. 3. Po otworzeniu drzwiczek, powinny one zostać automatycznie zamknięte, jeśli wcześniej nie zostaną zamknięte przez użytkownika. 4. System rozpoznawania dźwięków musi być w stanie określić, kiedy pies szczeka. 5. System rozpoznawania dźwięków musi być w stanie otworzyć drzwiczki, kiedy pies zacznie szczekać.

Wymagania ulegają zmianom

W końcu ponownie możemy zacząć pisać kod obsługujący drzwiczki :UD]]QRZ\PLZ\PDJDQLDPLPXVLVLĊSRMDZLüQRZ\NRG3RWU]HEQHQDPEĊG]LH V]F]HNDQLH$]RUDV\VWHPUR]SR]QDZDQLDGĨZLĊNyZNWyU\EĊG]LHQDVáXFKLZDü LUR]SR]QDZDüWRV]F]HNDQLHRUD]ZRGSRZLHG]LQDQLHRWZLHUDüGU]ZLF]NL

Hau, hauu

To jest nasza nowa metoda, któr a ma być wywoływana za każdym razem, gdy sprzęt Darka „usłyszy ” szczekanie. FODVV 'RJ'RRU 6LP^ `

Podobnie jak w przypadku urządzen do rozpoznawania szczekania, takż ia i same drzwiczki dla psa składają e się ze sprzętu oraz oprogramowa nia.

UHFRJQL]H

DogDoorSimulator.java FODVV %DUN5HF RJQL]HU ^ XSGDWH `

jest Pamiętaj, że Azor nie z znajduje lec elementem systemu, nie będzie się poza nim, dlatego reprezentujący nam potrzebny obiekt w klasie Azora. Wystarczy, że ymulujemy DogDoorSimulator zas . ora Az e ani szczek

BarkRecognizer.java

Musimy jeszcze napisać kod klasy BarkRecognizer — zrobimy to na następnej stronie.

RSHQ

potrzebujemy W tej klasie nie Udostępnia już ona . go niczego nowe rą może wywołać metodę open(), któ nia szczekania. wa system rozpozna klasy w ogóle nie A zatem kod tej . musi się zmieniać

FODVV 'RJ'RRU ^ RSHQ `

DogDoor.java

Choć wciąż pracujemy zmodyfikowaniem nas nad zeg oprogramowania, by spe o ono wymagania klienta łniało brak modyfikacji jest , to jednak sygnałem informującym, że stw orzony przez nas projekt systemu jest właściwy. Dobra robota!

jesteś tutaj  157

Rozpoznawanie szczekania

Czy nie słyszałem jakiegoś „hau”? Potrzebujemy pewnego kodu, który mógłby zostać wykonany, kiedy sprzęt Darka „usłyszy” szczekanie. Utwórzmy zatem klasę %DUN5HFRJQL]HU, a w niej metodę, która będzie odpowiadać na szczekanie.

BarkRecognizer.java

W tej zmiennej przechowamy obie z którymi będzie współpracować kt drzwiczek, systemu rozpoznawania szczekanten egzemplarz ia.

SXEOLFFODVV%DUN5HFRJQL]HU^ SULYDWH'RJ'RRUGRRU

FODVV %DUN5HF RJQL]HU ^ UHFRJ `





SXEOLF%DUN5HFRJQL]HU 'RJ'RRUGRRU ^ WKLVGRRU GRRU ` SXEOLFYRLGUHFRJQL]H 6WULQJEDUN ^





Konstruktor klasy BarkRecognizer musi wiedzieć, jakie drzwiczki należy  otwierać. Za każdym ra usłyszy szczekzem gdy sprzęt tę metodę, pr anie, wywoła niej usłyszan zekazując do e dźwięki.

6\VWHPRXWSULQWOQ Ĵ%DUN5HFRJQL]HU8Vï\V]DQRIJĵ EDUNĴijĵ 



GRRURSHQ  `



`

W tym przypadku mu simy jedynie wyświetlić komunikat , wiedział, że zarejestr by system owaliśmy szczekanie…

worzyć …a następnie ot a. ps dla ki drzwicz

Nie istnieją

głupie pytania

P: Tylko tyle? Faktycznie, wygląda na to, że klasa

P: Ale co się stanie, jeśli zacznie szczekać inny pies

BarkRecognizer nie ma zbyt wiele do roboty.

niż Azor? Czy przed otworzeniem drzwiczek klasa BarkRecognizer nie powinna upewnić się, że szczeka Azor, a nie jakieś inne zwierzę?

O: W obecnej chwili faktycznie nie ma. Wymagania aplikacji są

bardzo proste — jak usłyszysz szczekanie, otwórz drzwiczki — więc także kod jest prosty. Za każdym razem gdy układy sprzętowe systemu rozpoznawania dźwięku usłyszą szczekanie, zostanie wywołana metoda UHFRJQL]H klasy %DUN5HFRJQL]HU, która z kolei otworzy drzwiczki. Pamiętaj, by starać się zachować jak największą prostotę: nie komplikuj rozwiązania, jeśli nie jest to konieczne.

158

Rozdział 3.

O: Bardzo interesujące pytanie! Klasa %DUN5HFRJQL]HU słyszy

każde szczekanie, ale my raczej nie chcemy, by otwierała drzwiczki, wpuszczając do domu każdego psa, nieprawdaż? Może wrócimy do tego problemu i rozwiążemy go później. A może powinieneś dokładniej wszystko przemyśleć podczas testowania systemu?

Wymagania ulegają zmianom

Myślę, że po dodaniu tej nowej klasy BarkRecognizer mamy już wszystko, czego nam potrzeba. Przetestujmy ją i sprawdźmy, czy będziemy w stanie ponownie uszczęśliwić Tadka i Jankę.

W pierwszej kolejności upewnijmy się, czy zadbaliśmy o spełnienie wszystkich wymagań, jakie Tadek i Janka postawili przed nową wersją drzwiczek dla psa.

Drzwiczki dla psa, dla Tadka i Janki, wersja 2.3 Lista wymagań 1. Górna krawędź otworu drzwiczek musi być umieszczona co najmniej na wysokości 30 centymetrów. 2. Naciśnięcie przycisku na pilocie powoduje otworzenie drzwiczek, jeśli te są zamknięte, lub ich zamknięcie, jeśli są otwarte. 3. Po otworzeniu drzwiczek powinny one zostać automatycznie zamknięte, jeśli wcześniej nie zostaną zamknięte przez użytkownika. 4. System rozpoznawania dźwięków musi być w stanie określić, kiedy pies szczeka. 5. System rozpoznawania dźwięków musi być w stanie otworzyć drzwiczki, kiedy pies zacznie szczekać. To jest wymaganie sprzętowe przeznaczone raczej dla Darka. Na razie możemy użyć symulatora, by wygenerować szczekanie, które system rozpoznawania dźwięku mógłby wykryć. Takie rozwiązanie pozwoli nam przetestować nową wersję systemu.

To jest kod, który właśnie napisaliśmy… Za każdym razem gdy system rozpoznawania dźwięku usłyszy szczekanie, otworzy drzwiczki.

to nasz system Hmm… tak naprawdę, nie „rozpoznaje” u ięk rozpoznawania dźw Otwiera szczekania, nieprawdaż? KAŻDEGO u eni ysz usł po zki drzwic później trzeba szczekania. Być może blemem. pro tym się ąć będzie zaj

jesteś tutaj  159

Test systemu

Podłączamy nowe drzwiczki do prądu

FODVV 'RJ'RRU 6LP^ `

Oto efekt finalny napisania nowego przypadku użycia i nowego kodu. Sprawdźmy, czy wszystko działa tak, jak powinno.

1

DogDoorSimulator.java

Aktualizacja kodu źródłowego klasy DogDoorSimulator: SXEOLFFODVV'RJ'RRU6LPXODWRU^ Tworzymy obiekt ymy BarkRecognizer, kojarz z obiektem drzwiczek na dla psa i pozwalamy ania. nasłuchiwanie szczek

SXEOLFVWDWLFYRLGPDLQ 6WULQJ>@DUJV ^ 'RJ'RRUGRRU QHZ'RJ'RRU  %DUN5HFRJQL]HUUHFRJQL]HU QHZ%DUN5HFRJQL]HU GRRU  5HPRWHUHPRWH QHZ5HPRWH GRRU  6\PXOXMHP\ĝHV\VWHPVSU]ÚWRZ\Vï\V]\V]F]HNDQLH Nie dysponujemy faktycznym sprzętem, zatem jedynie symulujemy, że sprzęt usłyszy i rozpozna szczekanie*.

6\VWHPRXWSULQWOQ ĵ$]RUV]F]HNDE\Z\MĂÊQD]HZQÈWU]ĵ  UHFRJQL]HUUHFRJQL]H ĵ+DXĵ 









6\VWHPRXWSULQWOQ ĵ?Q$]RUZ\V]HGïQD]HZQÈWU]ĵ 

To właśnie w tym miejscu nasz nowy obiekt BarkRecognizer wkracza do akcji.

6\VWHPRXWSULQWOQ ĵ?Q$]RU]DïDWZLïVZRMHSRWU]HE\ĵ  WU\^

W tym miejscu 7KUHDGFXUUHQW7KUHDG VOHHS   symulujemy upływ dłuższego `FDWFK ,QWHUUXSWHG([FHSWLRQH ^` okresu czasu.

6\VWHPRXWSULQWOQ ĵDOHE\ïQD]HZQÈWU]]E\WGïXJRĵ   6\PXOXMHP\ĝHV\VWHPVSU]ÚWRZ\Vï\V]\V]F]HNDQLH SRQRZQLH 6\VWHPRXWSULQWOQ ĵ?Q$]RU]DF]\QDV]F]HNDÊĵ  



UHFRJQL]HUUHFRJQL]H ĵ+DXĵ 

Testujemy proces, w którym Azor pozostaje dłużej na zewnątrz; chcemy bowiem upewnić się, że wszystko działa zgodnie z naszymi oczekiwaniami.

6\VWHPRXWSULQWOQ ĵ?Q$]RU]SRZURWHPZV]HGïGRGRPXĵ  ` `

Zauważ, że ani Tadek, ani Janka nie muszą już naciskać przycisku na pilocie. * Autorzy niniejszej książki naprawdę chcieli dołączyć do niej odpowiednie urządzenie sprzętowe, które byłoby w stanie usłyszeć szczekanie psa… Jednak goście z działu marketingu upierali się, że nikt by nie kupił tej książki za cenę 900 zł. Ciekawe, czy mieli rację!

160

Rozdział 3.

Wymagania ulegają zmianom 2

Ponownie skompiluj wszystkie pliki źródłowe aplikacji. javac FODVV 'RJ'RRU ^ RSHQ ` FODVV 5HPRWH^ SUHVV DogDoor.java %XWWRQ ` FODVV %DUN5HF RJQL]HU Remote.java ^ UHF FODVV ` 'RJ'RRU 6LP^ BarkRecognizer.java `

*.java

DogDoorSimulator.java

3

DogDoor.class Remote.class BarkRecognizer.class

DogDoorSimulator.class

Uruchom aplikację i obserwuj, jak drzwiczki dla psa działają bez żadnej interwencji ze strony człowieka.

Pomiędzy pojawieniem się tych napisów mija kilka sekund, w czasie których Azor załatwia swoje potrzeby.

Zaostrz ołówek Który scenariusz testujemy?

WYS

SZAR

IL

E KO W nas zy MÓR poważ m kodzie w KI ystępu ny pro b je urucho le mieniu m, który uw pewien wskaz ać? Co symulatora. idocznił się po Cz byś zro bił, by y potrafisz g go roz wiązać o ?

Czy jesteś w stanie określić, który scenariusz naszego przypadku użycia aktualnie testujemy? Zapisz wszystkie kroki wykonywane przez symulator (zajrzyj na stronę 149, by przypomnieć sobie, jak wygląda nasz aktualnie używany przypadek użycia).

jesteś tutaj 

161

Odpowiedzi i otwieranie drzwiczek

Zaostrz ołówek

Rozwiązanie

WYSILOMÓRKI SZARE

K

uje pewien zie występ idocznił się po d o k m y sz W na tóry uw problem, k ra. Czy potrafisz go poważny to la u niu sym związać? uruchomie byś zrobił, by go ro Co wskazać?

Który scenariusz testujemy?

Czy jesteś w stanie określić, który scenariusz naszego przypadku użycia aktualnie testujemy? Oto wykonane przez nas kroki przypadku użycia przedstawionego na stronie 161: 1, 2, 3, 4, 5, 6, 6.1, 6.2, 6.3, 6.4, 5.6, 7, 8

Czy określiłeś, co było nie w porządku z ostatnią wersją naszego systemu?

W naszej nowej wersji systemu drzwiczki nie zamykają się automatycznie! Poniżej przedstawiliśmy fragment kodu z poprzedniej wersji systemu — w którym drzwiczki otwierało naciśnięcie przycisku — odpowiadający za automatyczne zamykanie drzwiczek po upływie określonego czasu:

SXEOLFYRLGSUHVV%XWWRQ ^ 6\VWHPRXWSULQWOQ ĵ1DFLĂQLÚWRSU]\FLVNQDSLORFLHĵ  LI GRRULV2SHQ ^ GRRUFORVH  `HOVH^ GRRURSHQ  ILQDO7LPHUWLPHU QHZ7LPHU  Kiedy Tadek lub Janka nacisną przycisk na pilocie, to ten fragment kodu uruchamia także licznik czasu, który automatycznie zamknie drzwiczki.

WLPHUVFKHGXOH QHZ7LPHU7DVN ^ SXEOLFYRLGUXQ ^ GRRUFORVH   WLPHUFDQFHO 



Pamiętasz zapewne, że ten licznik czasu czeka 5 sekund, a następnie wysyła żądanie zamknięcia drzwiczek.







` `  ` `

FODVV 5HPRWH SUHVV %XWWRQ `

Remote.java

162

Rozdział 3.

Wymagania ulegają zmianom Ale w klasie %DUN5HFRJQL]HU otwieramy drzwiczki i nigdy ich nie zamykamy:

SXEOLFYRLGUHFRJQL]H 6WULQJEDUN ^ 6\VWHPRXWSULQWOQ ķ%DUN5HFRJQL]HU 8Vï\V]DQRIJĵ EDUNĵijĵ  GRRURSHQ  `

Otwieramy lecz ich nie drzwiczki, zamykamy.

FODVV %DUN5HF RJQL]HU ^ XSGDWH `

BarkRecognizer.java

firmy Darek, szef , zdecydował, ia zw dr eO asz si P e wie, co m że dokładni zrobić.

Nawet ja to potrafię wymyślić. Wystarczy dodać do klasy BarkRecognizer licznik czasu, — taki sam, jakiego użyliśmy w klasie Remote. To powinno wystarczyć, by system ponownie zaczął działać poprawnie. W końcu wiesz… Tadek i Janka czekają!

A co TY myślisz o pomyśle Darka?

jesteś tutaj  163

Powtarzający się kod jest do bani

Według mnie Darek to lamer. Nie mam zamiaru umieszczać tego samego kodu w dwóch miejscach — w klasie obsługującej pilota i systemie rozpoznawania dźwięków.

Powielanie kodu jest bardzo złym pomysłem. Ale w takim razie, gdzie należałoby umieścić fragment kodu odpowiadający za automatyczne zamykanie drzwi?

Cóż, za zamykanie drzwiczek powinny chyba odpowiadać same drzwiczki, a nie jakiś pilot lub system rozpoznawania dźwięków. Dlaczego zatem nie umieścimy tego kodu w klasie DogDoor?

Niech zatem drzwiczki zawsze zamykają się automatycznie.

to zja projektowa, Choć jest to decy zaliczyć do grupy ją m jednak możemy h z doprowadzenie zadań związanyc do takiego stanu, ia an i oprogramow nie z oczekiwaniam by działało zgod że nie ma aj, klienta. Pamięt używaniu dobrego niczego złego w prac nad projektu podczas systemu. funkcjonalnością

164

Rozdział 3.

Ponieważ Janka nie chce, by drzwiczki dla psa w ogóle zostawały otwarte, możemy je zawsze zamykać automatycznie. A zatem możemy przenieść kod z licznikiem odpowiadającym za automatyczne zamykanie drzwiczek do klasy 'RJ'RRU. Dzięki temu niezależnie od tego, kto lub co otworzy drzwiczki, zawsze zamkną się one automatycznie.

Wymagania ulegają zmianom FODVV 'RJ'RRU ^ RSHQ `

Aktualizacja klasy drzwiczek A zatem skopiujmy kod odpowiadający za automatyczne zamykanie drzwiczek z klasy 5HPRWH i przenieśmy go do klasy 'RJ'RRU:

DogDoor.java

 SXEOLFYRLGRSHQ ^ 6\VWHPRXWSULQWOQ ĵ'U]ZLF]NLRWZLHUDMÈVLÚĵ  RSHQ WUXH To jest dokładnie ten sam kod,

ILQDO7LPHUWLPHU QHZ7LPHU   który wcze  śniej był używany w klasie Remote.java. WLPHUVFKHGXOH QHZ7LPHU7DVN ^ SXEOLFYRLGUXQ ^ FORVH       Teraz drzwi zamykają się WLPHUFDQFHO  same… nawet jeśli dodamy nowe urządzenia, które będą ` mogły je otwierać. Super! `  `

Nie możesz zapomnieć o dodaniu instrukcji importujących klasy java.util. Timer oraz java. util.TimerTask.

SXEOLFYRLGFORVH ^ 6\VWHPRXWSULQWOQ Ĵ'U]ZLF]NL]DP\NDMÈVLÚĵ  RSHQ IDOVH ` `

Uproszczenie kodu obsługującego pilota Teraz musisz usunąć kod odpowiadający za automatyczne zamknięcie z klasy 5HPRWH, gdyż te możliwości funkcjonalne zostały przeniesione do klasy 'RJ'RRU.

SXEOLFYRLGSUHVV%XWWRQ ^ 6\VWHPRXWSULQWOQ ĵ1DFLĂQLÚWRSU]\FLVNQDSLORFLHĵ  LI GRRULV2SHQ ^ GRRUFORVH  `HOVH^ GRRURSHQ  ILQDO7LPHUWLPHU QHZ7LPHU  WLPHUVFKHGXOH QHZ7LPHU7DVN ^ SXEOLFYRLGUXQ ^ GRRUFORVH     WLPHUFDQFHO  ` `  `





FODVV 5HPRWH^ SUHVV %XWWRQ `

Remote.java

jesteś tutaj  165

Testowanie drzwiczek

Ostateczny test drzwiczek Wprowadziłeś naprawdę sporo zmian do drzwiczek dla psa zamówionych przez Tadka i Jankę. Nadszedł czas, by przetestować, czy wszystko działa, jak należy. Wprowadź ostatnie zmiany w plikach 5HPRWHMDYD oraz 'RJ'RRUMDYD, tak by drzwiczki zamykały się automatycznie, skompiluj wszystkie pliki źródłowe, po czym uruchom symulator:

Tak! Teraz drzwiczki zamykają się same.

WYSIL

SZARE KOMÓRKI A co by się stało, gdyby Tadek i Janka zdecydowali, że chcą drzwiczek, w których okres poprzedzający automatyczne zamknięcie drzwiczek jest dłuższy? Albo krótszy? Sprawdź, czy potrafisz wyobrazić sobie, w jaki sposób należałoby zmienić klasę DogDoor, tak by to klient mógł określać ilość czasu, po jakim drzwiczki mają się zamknąć.

166

Rozdział 3.

Wymagania ulegają zmianom

Czasami zmiana wymagań może uwidocznić problemy występujące w systemie, których istnienia nawet nie podejrzewałeś. Zmiany są pewnikiem, a Twój system powinien być coraz lepszy, ilekroć go poprawiasz.

Zaostrz ołówek Napisz swoją własną zasadę projektową! W tym rozdziale wykorzystałeś bardzo ważną zasadę projektową, związaną z powielającym się kodem i samoczynnym zamykaniem się drzwiczek dla psa. Zastanów się, co to mogła być za zasada, i postaraj się ją podsumować w kilku słowach.

Zasada projektowa

jdziesz W tym rozdziale nie zna ę, jednak adk zag tę na zi ied odpow do tego problemu mamy zamiar wrócić to spróbuj imo Pom j. nieco późnie zgadnąć!

jesteś tutaj  167

Przybornik projektanta

Kolejne Narzędzia do naszego projektanckiego przybornika W tym rozdziale zdobyłeś naprawdę sporo nowej wiedzy, a teraz nadszedł czas, by dodać zgromadzone informacje do Twojego projektanckiego przybornika. Przyjrzyj się wszystkim informacjom zamieszczonym na tej stronie i dobrze je zapamiętaj.

CELNE SPOSTRZEŻENIA Q

Wraz z postępem prac nad projektem wymagania zawsze będą się zmieniać.

Q

Wraz ze zmianami wymagań system musi ewoluować tak, by spełniać nowe wymagania.

Q

Jeśli system będzie musiał pracować w nowy lub zmieniony sposób, zacznij od aktualizacji przypadku użycia.

Q

Scenariusz jest konkretną ścieżką pozwalającą na przejście całego przypadku użycia od jego początku aż do końca.

Q

Jeden przypadek użycia może posiadać więcej scenariuszy, o ile tylko każdy z nich będzie spełniać ten sam cel klienta.

Q

Ścieżki alternatywne mogą składać się z kroków wykonywanych tylko czasami, mogą także pozwalać na przejście fragmentów przypadku użycia w całkowicie odmienny sposób.

Q

Jeśli pewien krok przypadku użycia jest opcjonalny bądź jeśli stanowi alternatywną ścieżkę przejścia przez przypadek użycia, to do jego oznaczenia należy użyć podpunktów, np.: 3.1, 4.1 i 5.1 lub 2.1.1, 2.2.1 i 2.3.1.

Q

Niemal zawsze należy starać się unikać powielania kodu. Może ono bowiem ogromnie utrudnić utrzymanie i rozwijanie oprogramowania, a samo jego wystąpienie sygnalizuje problemy lub usterki w projekcie aplikacji.

Wymagania Dobre wymagania gwarantują, że system będzie działał zgodnie z oczekiwaniami klienta. Upewnij się, że wymagania obejmują wszystkie kroki przypadku użycia opracowanego dla tworzonego systemu. Wykorzystaj przypadki użycia, by dowiedzieć się o wszystkich rzeczach, o których klient zapomniał Ci powiedzieć. Przypadki użycia ujawnią wszystkie niekompletne lub brakujące wymagania, które zapewne będziesz musiał dodać do tworzonego systemu.

W tym rozdziale poznałeś tylko jedną nową zasadę związaną z wymaganiami, niemniej jednak jest to bardzo ważna zasada!

Wraz z upływem czasu Twoje wymagania zawsze będą się zmieniać (i powiększać).

o Zasady projektowania obiektoweg Hermetyzuj to, co się zmienia.

Hermetyzacja pozwala nam uświadomić sobie, że drzwiczki dla psa same powinny obługiwać własne zamykanie. Oddzieliliśmy zachowanie drzwi od reszty kodu aplikacji.

168

Rozdział 3.

$QDOL]D

Zaczynamy używać naszych aplikacji w rzeczywistym świecie Uważam, że w końcu jestem gotowa!

Czas zdać ostatnie egzaminy i zacząć stosować nasze aplikacje w rzeczywistym świecie. Twoje aplikacje muszą robić nieco więcej, niż jedynie działać prawidłowo na komputerze, którego używasz do ich tworzenia — komputerze o dużej mocy i doskonale skonfigurowanym; Twoje aplikacje muszą działać w takich warunkach, w jakich rzeczywiści klienci będą ich używali. W tym rozdziale zastanowimy się, jak zyskać pewność, że nasze aplikacje będą działać w rzeczywistym kontekście. Dowiesz się w nim, w jaki sposób analiza tekstowa może przekształcić stworzony wcześniej przypadek użycia w klasy i metody, które na pewno będą działać zgodnie z oczekiwaniami klienta. A kiedy skończysz lekturę tego rozdziału, także i Ty będziesz mógł powiedzieć: „Dokonałem tego! Moje oprogramowanie jest gotowe do zastosowania w rzeczywistym świecie!”.

to jest nowy rozdział  169

Ten świat kocha psy

Jeden pies, dwa psy, trzy psy, cztery… W firmie PsieOdrzwia wszystko przebiega zgodnie z planem. Nowa wersja drzwiczek, do której oprogramowanie napisałeś w poprzednim rozdziale, sprzedaje się jak przysłowiowe „ciepłe bułeczki”… Jednak wraz z coraz większą liczbą sprzedanych drzwiczek zaczęło się także pojawiać coraz więcej skarg:

Bardzo podoba mi się ten nowy model drzwiczek z systemem rozpoznawania szczekania. Jednak teraz, kiedy zainstalowaliście takie drzwiczki w moim domu, otwierają się one za każdym razem, gdy zaszczeka pies sąsiadów. Nie tego oczekiwałam, wydając pieniądze na tę nową wersję drzwiczek!

Hanka

Hrauu! Hrauu!

Drzwiczki dla psa zamontowane w domu Hanki powinny się ie otwierać wyłącznie na szczekan Brusa… ...ale otwierają się tak jeśli w sąsiedztwie zasże, jakikolwiek inny pies. zczeka

Brus

170

Rozdział 4.

Hiau! Hiau!

Hauuuuuuu

Wrrrrr! Hau!

Analiza

Twoje oprogramowanie ma kontekst Dotychczas prace nad naszym oprogramowaniem były prowadzone w swoistej „próżni” i nie zastanawialiśmy się raczej nad kontekstem, w jakim będzie ono działać. Innymi słowy, myśleliśmy o naszym oprogramowaniu w następujący sposób:

W idealnym świecie każdy używa naszego oprogramowania tak, jak sobie tego życzymy.

Analiza

FODVV 'RJ'RRU ^ RSHQ `

DogDoor.java

Idealny Świat

W tym przypadku wszyscy są spokojni i zrelaksowani, a w sąsiedztwie nie ma żadnych innych psów.

Jednak nasze oprogramowanie musi działać w rzeczywistym, a nie w idealnym świecie. A to oznacza, że musimy myśleć o naszym oprogramowaniu w zupełnie innym kontekście: rzeczy W tym kontekście rót ob zły ją ra bie zy pr j. znacznie częście

FODVV 'RJ'RRU ^ RSHQ `

DogDoor.java

pomaga nam uzyskać pewność, że nasz system będzie działać w rzeczywistym kontekście.

W rzeczywistym świecie spotykamy inne psy, koty, gryzonie oraz całą masę innych problemów; a wszystkie te czynniki mają tylko jeden cel — doprowadzić do awarii naszego oprogramowania.

Rzeczywisty Świat Kluczem do uzyskania pewności, że aplikacja działa prawidłowo, a wszystkie destrukcyjne czynniki, jakie występują w rzeczywistym świecie, nie doprowadzą do jej awarii, jest analiza — wykrywanie potencjalnych problemów i rozwiązywanie ich, zanim nasza aplikacja zacznie być stosowana w praktyce.

jesteś tutaj 

171

Gdzie tkwi problem

Określ przyczynę problemu Pierwszym krokiem dobrej analizy jest wskazanie potencjalnych problemów. Wiemy już, że problemy z drzwiczkami pojawiają się, kiedy w sąsiedztwie pojawi się więcej psów.

FODVV 'RJ'RRU ^ RSHQ `

DogDoor

FODVV 5HPRWH^ SUHVV %XWWRQ `

Remote

Dysponujemy już wszystkimi klasami tworzącymi nasz system.

open ()

Hanka może użyć pilota do otworzenia drzwiczek. W tym przypadku żadne problemy nie występują.

open () Hrauu! Hrauu! FODVV %DUN5HF RJQL]HU ^ XSGDWH `

BarkRecognizer System rozpoznawania dźwięku „słyszy” szczekanie Brusa i otwiera drzwiczki, zgodnie z tym, czego by sobie życzyła Hanka.

Ale jest pewien problem… system rozpoznawania dźwięków „słyszy” również inne psy i także w tym przypadku drzwiczki są otwierane.

172

Rozdział 4.

Hauuuuuuu

Wrrrrr! Hau! Hiau! Hiau!

Analiza

Zaplanuj rozwiązanie Wygląda na to, że konieczna będzie jakaś zmiana sposobu działania naszego systemu. Czy wiesz, na czym ona będzie polegać? Poniżej zamieściliśmy fragment diagramu przedstawiający sposób działania systemu drzwiczek dla psa:

Brus zajął miejsce Azora… a zatem lepiej będzie, jeśli nieco zmodyfikujesz swój diagram.

W najnowszej wersji systemu pilot jest elementem ścieżki alternatywnej.

Brus, już otwieram drzwiczki… poczekaj sekundę.

2.1 Hanka słyszy, że Brus

zaczyna szczekać.

3.1

Hanka naciska przycisk na pilocie.

Hrauu! Hrauu!

1

Brus szczeka, by wyjść na zewnątrz.

System rozpoznawania 2 dźwięku jest elementem ścieżki głównej i wpuszcza do domu wszystkie psy, a nie tylko psa właścicieli.

4 Drzwiczki dla psa

System rozpoznawania dźwięku „słyszy” szczekanie. 3 System rozpoznawania dźwięku wysyła żądanie otworzenia drzwiczek.

otwierają się. 5 Brus wchodzi do domu.

Zaostrz ołówek Jaki problem występuje na tym diagramie? Twoim zadaniem jest określenie, w jaki sposób należy poprawić drzwiczki. Możesz dodawać nowe kroki, usuwać kroki już istniejące na diagramie albo dowolnie je zmieniać… masz pełną dowolność. Poniżej zapisz, co według Ciebie należy zmienić, a następnie wprowadź odpowiednie modyfikacje na powyższym diagramie.

jesteś tutaj  173

Dodawanie brakującego kroku

Zaostrz ołówek

Jaki problem występuje na tym diagramie?

Rozwiązanie

Brus, już otwieram drzwiczki… poczekaj sekundę. Hanka słyszy, że Brus 2 2.1 zaczyna szczekać.

na pilocie.

Hrauu! Hrauu!

1

Brus szczeka, by wyjść na zewnątrz.

System rozpoznawania dźwięków „słyszy” wszystkie psy… Ten krok jest w porządku, problemy pojawiają się w następnym.

4 Drzwiczki dla psa

otwierają się.

2 System rozpoznawania

dźwięku „słyszy” szczekanie.

W kroku 3. system rozpoznawania dźwięków powinien przetworzyć usłyszane szczekanie i sprawdzić, czy to szczeka Brus, czy jakiś inny pies.

174

3.1 Hanka naciska przycisk

Rozdział 4.

5 Brus wchodzi do domu. 3

3

System rozpoznawania dźwięku wysyła żądanie otworzenia drzwiczek. Jeśli to Brus szczeka, to system rozpoznawania dźwięku wysyła żądanie otworzenia drzwiczek.

Jeśli to szczeka Brus, to system rozpoznawania dźwięków będzie mógł wysłać żąda otworzenia drzw nie iczek.

Analiza Nie istnieją

głupie pytania

P: Wymyśliłem inne rozwiązanie. Czy to oznacza, że mój pomysł jest zły?

Swoje własne przypadki

O: Nie, o ile tylko Twoje rozwiązanie umożliwia dowolne

użycia pisz w taki sposób,

wchodzenie i wychodzenie z domu Brusowi, a jednocześnie zatrzymuje na zewnątrz wszystkie inne psy. To właśnie z tego powodu dyskutowanie o oprogramowaniu jest takie trudne: zazwyczaj istnieje więcej niż jedno rozwiązanie konkretnego problemu i przeważnie żadne z nich nie jest tym „jedynie słusznym”.

P: W moim rozwiązaniu zastąpiłem istniejący wcześniej krok 3. oryginalnego przypadku użycia dwoma nowymi krokami. Gdzie popełniłem błąd?

O: Nie popełniłeś żadnego błędu. Wspominaliśmy już, że niemal

każdy problem można rozwiązać na kilka sposobów; na tej samej zasadzie każde z tych rozwiązań da się różnorako zapisać w przypadku użycia. Jeśli w swoim przypadku użycia zastosowałeś więcej niż jeden dodatkowy krok, a system rozpoznawania dźwięków działa poprawnie i nie dopuszcza, by obce psy wchodziły do domu, to należy wysnuć wniosek, że stworzyłeś poprawny, działający przypadek użycia.

P: A zatem te przypadki użycia nie są aż tak

abyś bez problemu sam mógł je zrozumieć i wytłumaczyć szefowi oraz klientom. Analiza oraz przypadki użycia pozwolą Ci pokazać klientom, menedżerom i innym programistom,

precyzyjne, nieprawdaż?

jak system działa

O: W rzeczywistości przypadki użycia są bardzo precyzyjne.

w kontekście

Jeśli przypadek użycia nie opisuje szczegółowo sposobu, w jaki powinien działać system, to istnieje niebezpieczeństwo, że pominiesz jedno lub kilka ważnych wymagań, a w efekcie klient nie będzie zadowolony. Niemniej jednak przypadki użycia nie muszą być zbytnio formalne; innymi słowy, Twoje przypadki użycia mogą się różnić od naszych, a nasze mogą być inne od przypadków narysowanych przez kogoś innego. Najważniejsze jest to, by przypadki użycia były zrozumiałe dla Ciebie i abyś mógł je wyjaśnić swoim współpracownikom, szefowi i klientom.

rzeczywistego świata.

WYSIL

SZARE KOMÓRKI Oprócz zmian przedstawionych na stronie 174 w naszym systemie drzwiczek dla psa konieczne będzie wprowadzenie jeszcze jednej, bardzo ważnej modyfikacji. Czy wiesz, o co może chodzić?

jesteś tutaj  175

Dbaj, by przypadki użycia były aktualne

Aktualizuj przypadki użycia Ponieważ zmieniliśmy diagram działania drzwiczek dla psa, musimy wrócić do przypadku użycia naszego systemu i zaktualizować go, uwzględniając nowe kroki. Następnie, na kilku kolejnych stronach postaramy się określić, jakie zmiany musimy wprowadzić w kodzie naszej aplikacji.

Pa, pa, Azorku. Od teraz będziemy używali określenia „pies właściciela”.

Oto zaktualizowany krok, który obsługuje otwieranie drzwiczek tylko i wyłącznie wtedy, gdy szczeka pies właściciela.

Nie zapomnij zmodyfikować także i tego kroku.

176

Usunęliśmy wszystkie odwołania konkretnych właścicieli i psów, do dzię czemu w obecnej postaci ten przy ki użycia będzie można stosować dlapadek wszystkich klientów firmy Darka.

Niesamowite drzwiczki dla psa, wersja 3.0 Jak działają drzwiczki Ścieżka główna Ścieżki alternatywne 1. Pies właściciela szczeka, by wyjść na spacer. 2.1. Właściciel słyszy, że jego 2. System rozpoznawania dźwięków pies szczeka. „słyszy” szczekanie Azora. 3. Jeśli system rozpozna, że szczeka pies 3.1. Właściciel naciska przycisk na pilocie. właściciela, to wysyła żądanie otworzenia drzwiczek. 4. Drzwiczki dla psa otwierają się. Zamiast Tadk Hanki będzie a i Janki lub m 5. Pies właściciela wychodzi na zewnątrz. zwrotu „właśc y używać iciel”. 6. Pies właściciela załatwia swoje potrzeby. 6.1. Drzwiczki zamykają się automatycznie. 6.2. Pies właściciela szczeka, by wejść do domu. 6.3. System rozpoznawania dźwięków 6.3.1. Właściciel słyszy szczekanie „słyszy” szczekanie (ponownie). swojego psa (znowu). 6.4. Jeśli system rozpozna, że szczeka 6.4.1. Właściciel naciska przycisk pies właściciela, to wysyła żądanie na pilocie. otworzenia drzwiczek. 6.5. Drzwiczki dla psa otwierają się (znowu). 7. Pies właściciela wraca z powrotem. 8. Drzwi automatycznie się zamykają.

Rozdział 4.

Analiza

Czy nie musimy przechowywać w naszych drzwiczkach informacji o sposobie szczekania psa właściciela? W przeciwnym razie nie będziemy mieli z czym porównać szczekania, które „usłyszał" system rozpoznawania dźwięku.

Potrzebujemy nowego przypadku użycia, który umożliwi przechowywanie informacji o sposobie szczekania psa właściciela. Analiza pokazała nam, że musimy wprowadzić pewne zmiany do naszego przypadku użycia — a te zmiany z kolei oznaczają, że trzeba będzie nieco zmienić nasz system. Skoro mamy porównywać szczekanie, które wychwycił system rozpoznawania dźwięku, ze szczekaniem psa właściciela, to oczywistym jest, że gdzieś musimy przechowywać informacje o sposobie szczekania psa właściciela. A to oznacza, że będziemy potrzebowali dodatkowego przypadku użycia.

Zaostrz ołówek Dodaj nowy przypadek użycia związany z przechowywaniem informacji o sposobie szczekania psa właściciela. A zatem będzie Ci potrzebny nowy przypadek użycia określający sposób przechowywania informacji o szczekaniu psa osoby zamawiającej drzwiczki. Załóżmy, że informacje te — w postaci dźwięków — będą przechowywane w samych drzwiczkach (specjaliści zajmujący się w firmie Darka sprzętem twierdzą, że dla nich nie stanowi to żadnego problemu). Napisz zatem nowy przypadek użycia dla tego zadania, używając przy tym szablonu przedstawionego poniżej. Ten przypadek użycia będzie się składać wyłącznie z dwóch kroków, poza tym nie będziesz musiał się przejmować jakimikolwiek ścieżkami alternatywnymi.

Niesamowite drzwiczki dla psa, wersja 3.0 Zapisywanie informacji o sposobie szczekania

1.

Ponieważ jest to nasz drugi przypadek użycia, zatem nadajmy mu tytuł odpowiadający czynnościom, jakie opisuje.

2. jesteś tutaj  177

Nowy przypadek użycia

Zaostrz ołówek

Rozwiązanie

Dodaj nowy przypadek użycia związany z przechowywaniem informacji o sposobie szczekania psa właściciela.

A zatem będzie Ci potrzebny nowy przypadek użycia określający sposób przechowywania informacji o szczekaniu psa osoby zamawiającej drzwiczki. Załóżmy, że informacje te — w postaci dźwięków — będą przechowywane w samych drzwiczkach (specjaliści zajmujący się w firmie Darka sprzętem twierdzą, że dla nich nie stanowi to żadnego problemu). Napisz zatem nowy przypadek użycia dla tego zadania, używając przy tym szablonu przedstawionego poniżej. Szczegółowe informacje na temat tego kroku raczej nas nie interesują, gdyż są one związane z samym sprzętem.

Niesamowite drzwiczki dla psa, wersja 3.0 Zapisywanie informacji o sposobie szczekania

1. Pies właściciela szczeka „do” drzwiczek. 2. Drzwiczki rejestrują dźwięki, zdobywając informacje o sposobie szczekania psa

Właśnie tym musimy się zająć… dodaniem do klasy DogDoor metody pozwalającej na zapisanie w obiekcie informacji o sposobie szczekania psa.

Nie istnieją

głupie pytania

P: Czy naprawdę potrzebujemy

P: Czy to naprawdę jest wynik

P: W jakiej postaci będziemy

całego nowego przypadku użycia tylko po to, by zapisać informacje o sposobie szczekania psa właściciela?

przeprowadzenia dobrej analizy, czy też coś, co powinniśmy wymyślić już wcześniej — podczas prac nad systemem, przedstawionych w dwóch poprzednich rozdziałach?

przechowywali informacje o sposobie szczekania psa?

O: Tak. Każdy przypadek użycia powinien

zawierać szczegółowe informacje dotyczące wyłącznie jednego celu użytkownika. Celem użytkownika, jaki realizował nasz oryginalny przypadek użycia, było wypuszczenie psa na spacer i wpuszczenie go z powrotem do domu, bez używania w tym celu toalety. Z kolei w tym przypadku celem użytkownika, jaki opisuje nasz nowy przypadek użycia, jest zapisanie informacji o sposobie szczekania psa właściciela. Ponieważ oba te cele nie są ze sobą powiązane, dlatego będziemy potrzebowali dwóch przypadków użycia.

178

Rozdział 4.

O: Pewnie i jedno, i drugie. Zapewne

to prawda, że już wcześniej powinniśmy się domyślić, iż będziemy musieli przechowywać informacje o sposobie szczekania psa właściciela, ale w rzeczywistości właśnie taki jest cel analizy: upewnić się, że nie zapomniałeś o niczym, co będzie potrzebne do zagwarantowania poprawnego działania systemu w rzeczywistym świecie.

O: I to jest dobre pytanie… Co więcej,

już zaraz będziesz musiał znaleźć na nie odpowiedź…

Analiza

=DJDGNDSURMHNWRZD Wiesz, jakimi klasami już dysponujesz, napisałeś też dwa nowe przypadki użycia, które informują Cię, co Twój kod musi być w stanie zrobić. A teraz Twoim zadaniem będzie określenie, jakie modyfikacje należy wprowadzić w kodzie aplikacji:

7ZRMH]DGDQLH 1

Dodaj wszelkie nowe obiekty, jakich będzie potrzebował system obsługujący drzwiczki dla psa.

2

Do klasy 'RJ'RRU dodaj nową metodę, która będzie zapisywać w obiekcie informacje o sposobie szczekania psa właściciela, oraz drugą nową metodę, która pozwoli innym obiektom na pobieranie tych informacji.

3

Jeśli musisz wprowadzić zmiany w jakichkolwiek innych klasach lub metodach, zapisz je w diagramach klas zamieszczonych poniżej.

4

Do diagramów klas dodaj notatki, które później przypomną Ci, do czego służą ich atrybuty i metody oraz jak one powinny działać.

Diagramów klas używaliśmy w rozdziale 1.; przedstawiają one podstawowe elementy, z jakich składa się kod aplikacji. Pamiętaj, że to są atrybuty klasy, które zazwyczaj odpowiadają zmiennym składowym klasy…

DogDoor open: boolean

Remote door: DogDoor

Zaktualizuj klasę DogDoor, by obsługiwała nowe operacje opisane w przypadku użycia przedstawionym na stronie 178.

open() close() isOpen(): boolean

pressButton

BarkRecognizer door: DogDoor je …to z kolei są operac wykonywane przez j klasę, którym zazwycza odpowiadają publiczne metody klasy.

recognize(String) Pamiętaj, sprzęt Darka przesyła do tej metody informacje o „usłyszanym” szczeknięciu.

jesteś tutaj  179

Poprawianie drzwiczek dla psa

Opowieść o dwóch programistach Istnieje wiele sposobów rozwiązania zagadki projektowej, którą zamieściliśmy na stronie 179. W rzeczywistości zarówno Radek, jak i Szymek — dwóch programistów zatrudnionych niedawno przez firmę PsieOdrzwia — mają dobre pomysły. Niemniej jednak w ich pojedynku jest do stracenia znacznie więcej niż jedynie duma programisty — otóż Darek obiecał programiście, który przygotuje lepszy projekt, nowiutkiego notebooka MacBook Pro! 17 cali smakowitego o Macintosha doładowaneg mocą Intela.

Radek: proste jest najlepsze, prawda? Radek nie traci czasu na tworzenie jakiegokolwiek niepotrzebnego kodu. Zaczął od razu zastanawiać się, w jaki sposób może porównywać szczekanie usłyszane przez system z zarejestrowanym wcześniej szczekaniem psa właściciela:

Dźwięki wydawane podczas szczekania można zapisać w formie łańcucha znaków; a zatem informacje o sposobie szczekania psa właściciela zapiszę w klasie DogDoor jako String i dodam do tego kilka prostych metod. Łatwizna!

Do swojej klasy DogDoor Radek dodał zmien ną allowedBark

FODVV'RJ'RRU^ SULYDWHERROHDQRSHQ SULYDWH6WULQJDOORZHG%DUN  SXEOLF'RJ'RRU ^ RSHQ IDOVH ` SXEOLFYRLGVHW$OORZHG%DUN 6WULQJEDUN ^ WKLVDOORZHG%DUN EDUN Ta metoda obsługuje cji zapamiętanie informa a ani zek szc ie sob o spo ściciela, czyli SXEOLF6WULQJJHW$OORZHG%DUN ^ psa wła operacji, na której UHWXUQDOORZHG%DUN koncentrował się nasz . nowy przypadek użycia

`

`

GDOV]\FLÈJNRGX `

DogDoor Przy użyciu tej metody inne klasy mogą pobierać informacje o sposobie szczekania psa właściciela.

Door Oto diagram klasy Dog w wersji Radka. Radek

180

Rozdział 4.

open: boolean allowedBark: String open() close() isOpen(): boolean setAllowedBark(String) getAllowedBark(): String

Analiza

Szymek: zapalony miłośnik obiektów Ja jestem panem mocy obiektów!

Być może Szymek nie jest aż tak szybki jak Radek, jednak gorąco kocha swoje obiekty, więc zauważył, że nowa klasa poświęcona sposobowi szczekania będzie dla niego przepustką do sukcesu:

Nowa klasa Bark stworzona przez Szymka.

Bark sound: String getSound(): String equals(Object bark): boolean

Szymek planuje przechowywać informacje o sposobie szczekania psa w formie łańcucha znaków zapisywanego w obiekcie jego nowej klasy Bark… …ta metoda będzie zwracać dźwięk zapisany w obiekcie Bark…

al() pozwoli …natomiast metoda equ ównywanie por na om ekt obi ym inn reprezentowanych sposobów szczekania rk. Ba y ekt przez obi Szymek

Zaostrz ołówek Pisanie kodu na podstawie diagramów klas jest śmiesznie proste. Przekonałeś się już, że diagramy klas zawierają bardzo wiele informacji na temat atrybutów klasy oraz operacji, jakie klasy te mogą wykonywać. Twoim zadaniem będzie napisanie kodu klasy Bark na podstawie jej diagramu. Podaliśmy już fragmenty jej kodu, aby ułatwić Ci rozwiązanie zadania. SXEOLFFODVVBBBBBBBBBB^ SULYDWHBBBBBBBBBBBBBBBBBBBBB SXEOLFBBBBBBBB BBBBBBBBBBBBBBB ^ WKLVBBBBBBBBB BBBBBBBBB ` SXEOLFBBBBBBBBBBBBBBBBBBB ^ BBBBBBBBBBBBBBBBBBB ` BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBB ^ LI BBBBBBBBLQVWDQFHRIBBBBBBBB ^ %DUNRWKHU%DUN  BBBBBBB BBBBBBBBBB LI WKLVBBBBBBBBHTXDOV,JQRUH&DVH BBBBBBBBBBBBBBBBBBB ^ UHWXUQBBBBBBBBB ` ` UHWXUQBBBBBBBBBB ` `

jesteś tutaj 

181

Pisanie kodu klas Bark i DogDoor

Zaostrz ołówek

Rozwiązanie Podobnie jak Radek, także i Szymek zapisuje informacje o sposobie szczekania w postaci łańcucha znaków…

…jednak Szymek umieścił je w zupełnie nowym obiekcie.

Pisanie kodu na podstawie diagramów klas jest śmiesznie proste. Twoim zadaniem było napisanie kodu klasy Bark Szymka na podstawie przygotowanego przez niego diagramu. Oto w jaki sposób my rozwiązaliśmy to zadanie:

Diagram klasy Bark opracowanej przez Szymka.

Bark SXEOLFFODVVBBBBBBBBBB^ SULYDWHBBBBBBBBBBBBBBBBBBBBB String sound

Bark sound: String

SXEOLFBBBBBBBB BBBBBBBBBBBBBBB ^ String sound Bark WKLVBBBBBBBBB BBBBBBBBB sound sound

getSound(): String equals(Object bark): boolean

` SXEOLFBBBBBBBBBBBBBBBBBBB ^ String getSound BBBBBBBBBBBBBBBBBBB return sound

Szymek planuj klasy delegow e, by inne porównania dźały zadanie metody equalswięków do () klasy Bark

.

`

BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBB ^ public boolean equals Object bark

Bark LI BBBBBBBBLQVWDQFHRIBBBBBBBB ^ bark %DUNRWKHU%DUN  BBBBBBB BBBBBBBBBB Bark bark

Metoda ta musi się upewnić, że dysponuje drugim obiektem klasy Bark, gdyż w przeciwnym razie porównanie nie byłoby możliwe…

LI WKLVBBBBBBBBHTXDOV,JQRUH&DVH BBBBBBBBBBBBBBBBBBB ^ otherBark sound sound UHWXUQBBBBBBBBB true `

…dopiero później porównuje spos szczekania reprezentowane prze oby z oba obiekty.

`

false UHWXUQBBBBBBBBBB ` `

Szymek: aktualizacja klasy DogDoor Szymek stworzył nowy obiekt %DUN, a zatem przyjął nieco inne rozwiązanie niż Radek, wymagające wprowadzenia odmiennych modyfikacji do kodu klasy 'RJ'RRU:

DogDoor (wersja Szymka) open: boolean allowedBark: Bark open() close() isOpen(): boolean setAllowedBark(Bark) getAllowedBark(): Bark

182

Rozdział 4.

DogDoor Wersja klasy a przez Szymka, an ow ik yf od zm chowywany w której prze pu Bark, a nie ty kt ie ob jest . łańcuch znaków

Staszek będzie zapisywał i odczytywał z obiektu DogDoor obiekty Bark, a nie łańcuchy znaków.

DogDoor (wersja Radka) open: boolean allowedBark: String open() close() isOpen(): boolean setAllowedBark(String ) getAllowedBark(): String

Analiza

Porównywanie sposobów szczekania Jedyną rzeczą, jaką jeszcze musimy zrobić, to porównywanie dwóch sposobów szczekania. Operacja ta będzie wykonywana w metodzie UHFRJQL]H klasy %DUN5HFRJQL]HU:



Radek: Ja po prostu porównam dwa łańcuchy znaków Kiedy klasa %DUN5HFRJQL]HU otrzyma od urządzenia sprzętowego sygnał o wykryciu szczekania, wraz z nim zostaną przekazane informacje o zarejestrowanych dźwiękach; dzięki temu możliwe będzie porównanie tego szczekania z zapamiętanym wcześniej sposobem szczekania psa właściciela: SXEOLFFODVV%DUN5HFRJQL]HU^ SXEOLFYRLGUHFRJQL]H 6WULQJEDUN ^    6\VWHPRXWSULQWOQ Ĵ%DUN5HFRJQL]HU8Vï\V]DQRIJĵ EDUNĴijĵ  LI GRRUJHW$OORZHG%DUN HTXDOV EDUN ^   GRRURSHQ  `HOVH^ 6\VWHPRXWSULQWOQ Ĵ7HPXSVXQLHZROQRZHMĂÊGRGRPXĵ  ` `

Argument przeka do metody recognzywany to łańcuch znakówize() reprezentujący sp szczekania zarejesosób trowany przez system rozpoznawania dź więków.

Dźwięki zarejestrowane przez urządzenie  zętowe porównujemy spr z zapamiętanymi wcześniej informacjami o sposobie szczekania  właściciela. psa

GDOV]DF]ÚĂÊNRGX `

Szymek: A ja deleguję porównanie

Szymek zadb ał o to, by specjaliści Szymek używa obiektu Bark, który wykonuje wszystkie od sprzętu czynności związane z porównywaniem dźwięków. przekazywali do jego metod SXEOLFFODVV%DUN5HFRJQL]HU^ recognize() ob y Bark, a nie iekt zw yczajn SXEOLFYRLGUHFRJQL]H %DUNEDUN ^    y łańcuch znaków.

pozwala, Kod w wersji Szymka rk Ba ekt obi sam by to Jego realizował porównanie. deleguje n tio gni eco obiekt BarkR ównania wykonanie operacji por do obiektu Bark.

6\VWHPRXWSULQWOQ Ĵ%DUN5HFRJQL]HU8Vï\V]DQRIJĵ EDUNJHW6RXQG Ĵijĵ  LI GRRUJHW$OORZHG%DUN HTXDOV EDUN ^ GRRURSHQ  `HOVH^ 6\VWHPRXWSULQWOQ Ĵ7HPXSVXQLHZROQRZHMĂÊGRGRPXĵ  ` ` `

jesteś tutaj  183

Objazd w delegacji

Delegowanie w kodzie Szymka — analiza szczegółowa W swoich klasach %DUN oraz 'RJ'RRU Szymek zastosował podobne rozwiązanie. Przyjrzyjmy się mu dokładniej: 1

Obiekt BarkRecognizer tworzy obiekt Bark, który należy sprawdzić. System rozpoznawania dźwięków słyszy szczekanie psa, rejestruje te dźwięki i zapisuje je w obiekcie %DUN, który następnie przekazuje do metody UHFRJQL]H .

FODVV %DUN5HF RJQL]H ` XSGDWH `

Bark

Hiau!

UHFRJQL]H

Hiau!

BarkRecognizer

Obiekt Bark jest przekazywany do metody recognize().

Sprzęt Darka słyszy rzy szczekanie psa i two nowy obiekt Bark.

2 Obiekt BarkRecognizer pobiera z obiektu DogDoor

informacje o sposobie szczekania psa właściciela. Metoda UHFRJQL]H wywołuje metodę JHW$OORZHG%DUN obiektu drzwiczek, z którymi współpracuje, a w rezultacie otrzymuje obiekt %DUN reprezentujący sposób szczekania psa właściciela.

FODVV %DUN5HF RJQL]H ` XSGDWH `

FODVV 'RJ'RRU

JHW$OORZHG%DUN

UHFRJQL]H

`

BarkRecognizer

DogDoor

Bark Hiau!

184

Rozdział 4.

Obiekt drzwiczek zwraca obiekt Bark reprezentujący sposób szczekania psa właściciela.

Objazd w delegacji

Do rywalizacji pomięd zy i Szymkiem o notebooka Radkiem wrócimy już niebawem MacBook Pro , gdy dokładniej przyjrzymy się i poznam y zagadnienie delegowania.

3

Obiekt BarkRecognizer deleguje porównanie dźwięków do obiektu Bark. Metoda UHFRJQL]H prosi obiekt %DUN, zawierający informacje o sposobie szczekania psa sąsiadów, o sprawdzenie, czy jest on równy z obiektem %DUN dostarczonym przez sprzęt Darka. Porównanie to ma być zrealizowane przy użyciu metody %DUNHTXDOV . Hej, witam zmienną allowedBark. Czy mogłabyś sprawdzić, czy ten inny obiekt Bark, który właśnie zarejestrowałem, pasuje do ciebie? Ja w ogóle nie wiem, jak określić, czy dźwięki szczekania są sobie równe, czy nie, ale mogę się założyć, że ty wiesz świetnie. FODVV %DUN5HF RJQL]H ` XSGDWH `

UHFRJQL]H

HTXDOV

To do obiektu Bark należy określenie, czy dwa sposoby szczekania są identyczne.

Bark Hrauu!

Bark

BarkRecognizer

Hiau!

Metoda recognize() wywołuje metodę equals() obiektu reprezentującego zarejestrowany wcześniej sposób szczekania psa właściciela, przekazując do niej obiekt Bark reprezentujący dźwięki zarejestrowane przez sprzęt Darka.

4

Obiekt Bark decyduje, czy reprezentowany przez niego sposób szczekania jest taki sam jak dźwięki zarejestrowane przez urządzenie Darka. Obiekt %DUN reprezentujący sposób szczekania psa właściciela określa, czy jest równy obiektowi %DUN utworzonemu przez urządzenie Darka… abstrahując od tego, w jaki sposób operację tę należy wykonać. Czy faktycznie jedynie jeden obiekt Bark może zrozumieć inny obiekt tej samej klasy? Jeśli tak, to sprawdźmy, czy naprawdę jesteśmy sobie równi. Masz całkowitą rację. Porównajmy nasze właściwości.

Bark Hrauu!

To obiekt Bark powinien dokonać porównania… i przekazać obiektowi, który wywołał metodę equals(), sam rezultat — czy oba szczekania są identyczne, czy nie.

Szczegóły związane ze sposobem ji wykonania tej operac są niedostępne dla wszystkich obiektów wchodzących w skład aplikacji, z wyjątkiem obiektu Bark.

Bark Hiau!

jesteś tutaj  185

Objazd w delegacji

Potęga aplikacji, których elementy są ze sobą luźno powiązane W rozdziale 1. podaliśmy, że delegowanie pomaga zachować luźne powiązania pomiędzy poszczególnymi elementami aplikacji. Oznacza to, że poszczególne obiekty używane w aplikacji nie będą od siebie wzajemnie zależne; a to z kolei oznacza, że zmiana jednego obiektu nie pociągnie za sobą konieczności wprowadzania modyfikacji w kilku kolejnych obiektach. Dzięki delegowaniu porównania do obiektu klasy %DUN mogliśmy usunąć z klasy %DUN5HFRJQL]HU wszelkie informacje o tym, co sprawia, że dwa sposoby szczekania są identyczne. Przyjrzyj się jeszcze raz fragmentowi kodu klasy %DUN, w którym wywoływana jest metoda HTXDOV :

SXEOLFYRLGUHFRJQL]H %DUNEDUN ^ 6\VWHPRXWSULQWOQ ĵ%DUN5HFRJQL]HU8Vï\V]DQRIJĵ EDUNJHW6RXQG ĵijĵ  LI GRRUJHW$OORZHG%DUN HTXDOV EDUN ^ GRRURSHQ  `HOVH^ 6\VWHPRXWSULQWOQ Ĵ7HPXSVXQLHZROQRZHMĂÊGRGRPXĵ  ` `

A teraz załóżmy, że zaczniemy przechowywać zarejestrowany sposób szczekania psa w klasie %DUN, jako plik WAV. W takim przypadku konieczne byłoby zmodyfikowanie kodu metody HTXDOV klasy %DUN i uwzględnienie w nim bardziej zaawansowanych opcji oraz obsługi danych zapisanych w formacie WAV. Niemniej jednak, ponieważ metoda UHFRJQL]H deleguje wykonanie operacji porównania do klasy %DUN, zatem nie musimy wprowadzać jakichkolwiek zmian w kodzie klasy %DUN5HFRJQL]HU. A zatem, dzięki delegowaniu i tworzeniu aplikacji, których elementy są ze sobą luźno powiązane, możesz zmienić implementację jednego obiektu, takiego jak %DUN, bez konieczności wprowadzania zmian w jakimkolwiek innym obiekcie wchodzącym w skład aplikacji. Twoje obiekty są chronione przed zmianami implementacji innych obiektów.

HTXDOV

Bark

Bark Hrauu!

Hiau! Szczegółowe informacje o sposobie działania metody equals() są ukryte i chronione przed metodą recognize().

Delegowanie ochrania obiekty przed zmianami implementacji innych obiektów tworzących tę samą aplikację.

186

Rozdział 4.

Analiza

Wróćmy do Szymka, Radka i ich rywalizacji… Pamiętaj, że w ko informacje o szcz dzie Radka przekazywane są ekaniu zwyczajnego łań w formie cu natomiast w kodz cha znaków, — jako obiekt. ie Szymka

Sprawdźmy, jak działają dwie nowe wersje aplikacji opracowane przez Szymka i Radka:

Hrauu! Hrauu!

m kodzie Radek w swoi te os pr wykonuje łańcuchów porównywanie . ów ak zn LI GRRUJHW$OORZHG%DUN HTXDOV EDUN ^ GRRURSHQ  `

LI GRRUJHW$OORZHG%DUN HTXDOV EDUN ^ GRRURSHQ  `

Hauuuuuuu

W kodzie Szymka do wykonania tego samego zadanie używane są obiekty i delegowanie.

Wrrrrr! Hau!

Hiau! Hiau!

Radek i Szymek: To działa! Zarówno Radek, jak i Szymek napisali działające aplikacje, które wypuszczają i wpuszczają do domu jedynie psa właściciela.

Obaj napisaliśmy dobre aplikacje? Zatem kto dostanie notebooka?

jesteś tutaj  187

Drzwiczki dla psa i świat rzeczywisty

Notebooka MacBook Pro wygrała Maria Ku wielkiemu zaskoczeniu Radka i Szymka, Darek ogłosił, że notebooka dostanie Maria — początkująca programistka zatrudniona w firmie na letnią praktykę studencką. panowie, Oto Maria. Spróbujcie, ć… może ubi pol ją chę tro ż cia cho MacBooka Pro, pożyczy wam swojego je… kac wa na ie edz kiedy poj

5DGHN To jest żałosne. Moje rozwiązanie działało! To ja powinienem dostać tego notebooka, a nie jakaś praktykantka. 6]\PHN I co z tego, stary? Moje rozwiązanie także działało, a ja dodatkowo użyłem obiektów. Nie czytałeś Java. Rusz głową! Wydanie II? Należy stosować rozwiązania obiektowe. To mnie powinien przypaść notebook. 0DULD Halo, panowie, nie chciałabym przeszkadzać, ale nie jestem przekonana, czy aplikacja w wersji któregokolwiek z was naprawdę działała dobrze. 6]\PHN O co ci chodzi? Przetestowaliśmy je. Brus szczekał „Hrauu!” i drzwiczki się otwierały… a kiedy zaczynały szczekać inne psy, drzwiczki były zamknięte. Według mnie właśnie tak miały działać drzwiczki. 0DULD Ale czy przeprowadziliście jakąkolwiek analizę swoich rozwiązań? Czy wasze drzwiczki naprawdę działają prawidłowo w rzeczywistym świecie? 5DGHN O czym ty mówisz? Czy jesteś jakimś magistrem filozofii? O czym ta gadka w stylu „czy to łyżka czy idea łyżki”? 0DULD Nie, bynajmniej. Tylko tak się zastanawiałam… co by się stało, gdyby Brus zaczął szczekać w inny sposób, wydawał inne dźwięki… na przykład: „Houuu!” albo „Wrrrau!”? 6]\PHN Inne dźwięki? Na przykład takie, jak gdyby był głodny?

Wrrrau!

5DGHN …albo podekscytowany… 0DULD…albo… jak gdyby naprawdę musiał wyjść na zewnątrz załatwić swoje potrzeby. Właśnie tak wszystko może wyglądać w rzeczywistym świecie.

Hrauu! Hrauu! Houuu!

5DGHNL6]\PHN Hmm… chyba o tym nie pomyśleliśmy… liwym Brus jest skomplikowanym, wraż z otoczeniem się ym ując unik kom iem, zwierzak szczeknięć, poprzez całą gamę różnorodnych śność wykorzystującym intonację, dono ć sens i długość szczeknięć, by przekaza swej wypowiedzi.

188

Rozdział 4.

Analiza

Czym zatem różniło się rozwiązanie Marii? Maria zaczęła w sposób podobny do Szymka. A zatem stworzyła obiekt %DUN, reprezentujący szczekanie danego psa.

Wiedziałem, że obiekty i delegowanie mają duże znaczenie!

Bark sound: String getSound(): String equals(Object bark): boolean

Maria wiedziała, musiała zastosowże będzie delegowanie i po ać Szymek stworzy dobnie jak ła metodę equals(). w tym celu

Jednak Maria poszła jeszcze dalej: przewidziała, że pies może szczekać w różny sposób, i zdecydowała, że obiekt drzwiczek musi przechowywać większą liczbę obiektów %DUN. Dzięki temu niezależnie od tego, w jaki sposób będzie szczekać pies właściciela, drzwiczki zawsze go wypuszczą na zewnątrz:

DogDoor open: boolean allowedBarks: Bark [*] open() close() isOpen(): boolean addAllowedBark(Bark) getAllowedBark(): Bark [*]

Właśnie w tym miejscu rozwiąza nie zaproponowane przez Marię zacz yna poważnie różnić się od rozwiązań Radka i Szymka. Maria zdecydowała, że obiekt drzw musi przechowywać więcej niż jedeiczek reprezentujący szczeknięcie, gdyż n obiekt sam pies może szczekać na wiel ten e różnych sposobów.

Zastanawiasz się, co oznacza ta gwiazdka? Sprawdź…

UML pod lupą Do naszego diagramu klasy dodaliśmy coś nowego:

allowedBarks: Bark[*] rks jest Atrybut allowedBa rk. Ba pu ty

Za każdym razem gdy zobaczysz nawiasy kwadratowe, będą one oznaczać wielokrotność: ile danych pewnego typu będzie można przechowywać w atrybucie.

A ta gwiazdka oznacza, że w atrybucie allowedBarks będzie można zapisać nieograniczoną liczbę obiektów Bark.

jesteś tutaj  189

Przypadek użycia informuje, co należy zrobić Skąd, do licha, wiedziałaś, że trzeba przechowywać informacje o różnych szczeknięciach? Nigdy by mi nie przyszło do głowy, że pies może szczekać na różne sposoby.

Radek nie jest zachwycony faktem utraty notebooka, niemniej jednak zdał sobie sprawę z tego, że Maria może być jego przepustką do wygrania następnego konkursu.

Wszystko jest tu… w przypadku użycia…

Koncentrujemy się na naszym podstawowym przypadku użycia, na nowym, który opracowaliśmy a nie we wcześniejszej części tego rozdziału .

Niesamowite drzwiczki dla psa, wersja 3.0 Jak działają drzwiczki Ścieżki alternatywne Ścieżka główna 1. Pies właściciela szczeka, by wyjść na spacer. W tym 2.1. Właściciel słyszy, że jego 2. System rozpoznawania dźwięków przypadku pies szczeka. koncentrujemy „słyszy” szczekanie Azora. się na psie, Niesamowite drzwiczki dlakpsa, wersja 3.0 a przycis a nie na 3. Jeśli system rozpozna, że szczeka pies 3.1. Właściciel nacisk konkretnym na pilocie. informacji o sposobie szczekania Zapisywanie sposobie właściciela, to wysyła żądanie szczekania. otworzenia drzwiczek. 1. Pies właściciela szczeka „do” drzwiczek. 4. Drzwiczki dla psa otwierają się. 5. Pies właściciela wychodzi na zewnątrz. Drzwiczki rejestrują dźwięki, zdobywając 6. Pies właściciela załatwia swoje potrzeby. 2. informacje o sposobie szczekania psa 6.1. Drzwiczki zamykają się automatycznie. 6.2. Pies właściciela szczeka, by wejść do domu. 6.3.1. Właściciel słyszy szczekanie 6.3. System rozpoznawania dźwięków swojego psa (znowu). „słyszy” szczekanie (ponownie). 6.4.1. Właściciel naciska przycisk 190 Rozdział6.4. 4. Jeśli system rozpozna, że szczeka na pilocie. pies właściciela, to wysyła żądanie k.

Analiza

Zwracaj uwagę na rzeczowniki występujące w przypadku użycia Maria domyśliła się czegoś naprawdę ważnego: rzeczowniki występujące w przypadku użycia zazwyczaj reprezentują klasy, które należy napisać i na których należy się koncentrować podczas prac nad systemem. Zaostrz ołówek

Twoim zadaniem jest zakreślenie wszystkich rzeczowników (czyli osób, miejsc i rzeczy) występujących w poniższym przypadku użycia. Następnie w pustych liniach umieszczonych u dołu strony zapisz wszystkie odszukane rzeczowniki (każdy z nich zapisz tylko jeden raz; żaden z rzeczowników nie powinien się powtarzać). Koniecznie wykonaj to ćwiczenie, zanim zajrzysz na następną stronę!

Niesamowite drzwiczki dla psa, wersja 3.0 Jak działają drzwiczki

„pies” jest rzeczownikiem (ewentualnie mógłbyś także zakreślić słowa „pies właściciela”).

Ścieżki alternatywne Ścieżka główna 1. Pies właściciela szczeka, by wyjść na spacer. 2.1. Właściciel słyszy, że jego 2. System rozpoznawania dźwięków pies szczeka. „słyszy” szczekanie Azora. pies szczeka że naciska przycisk rozpozna, Właściciel 3.1. 3. Jeśli system na pilocie. właściciela, to wysyła żądanie otworzenia drzwiczek. 4. Drzwiczki dla psa otwierają się. 5. Pies właściciela wychodzi na zewnątrz. 6. Pies właściciela załatwia swoje potrzeby. 6.1. Drzwiczki zamykają się automatycznie. 6.2. Pies właściciela szczeka, by wejść do domu. 6.3.1. Właściciel słyszy szczekanie 6.3. System rozpoznawania dźwięków swojego psa (znowu). „słyszy” szczekanie (ponownie). 6.4.1. Właściciel naciska przycisk 6.4. Jeśli system rozpozna, że szczeka na pilocie. pies właściciela, to wysyła żądanie drzwiczek. otworzenia W tych pustych 6.5. Drzwiczki dla psa otwierają się (znowu). zapisz rzeczownikliniach 7. Pies właściciela wraca z powrotem. zaznaczyłeś w pr i, jakie zypadku użycia. 8. Drzwi automatycznie się zamykają.

jesteś tutaj 

191

Analiza rzeczowników

Zaostrz ołówek

Rozwiązanie

Twoim zadaniem było zaznaczenie wszystkich rzeczowników (czyli osób, miejsc oraz rzeczy) występujących na zamieszczonym przypadku użycia. Poniżej przedstawiliśmy rozwiązanie tego zadania.

Niesamowite drzwiczki dla psa, wersja 3.0 Jak działają drzwiczki Ścieżki alternatywne Ścieżka główna 1. Pies właściciela szczeka, by wyjść na spacer. 2.1. Właściciel słyszy, że jego 2. System rozpoznawania dźwięków pies szczeka. „słyszy” szczekanie Azora. pies 3.1. Właściciel naciska przycisk 3. Jeśli system rozpozna, że szczeka na pilocie. właściciela, to wysyła żądanie otworzenia drzwiczek. 4. Drzwiczki dla psa otwierają się. 5. Pies właściciela wychodzi na zewnątrz. 6. Pies właściciela załatwia swoje potrzeby. 6.1. Drzwiczki zamykają się automatycznie. 6.2. Pies właściciela szczeka, by wejść do domu. 6.3.1. Właściciel słyszy szczekanie 6.3. System rozpoznawania dźwięków swojego psa (znowu). (ponownie). szczekanie „słyszy” 6.4.1. Właściciel naciska przycisk 6.4. Jeśli system rozpozna, że szczeka na pilocie. pies właściciela, to wysyła żądanie otworzenia drzwiczek. 6.5. Drzwiczki dla psa otwierają się (znowu). 7. Pies właściciela wraca z powrotem. 8. Drzwiczki automatycznie się zamykają.

pies (właściciela)

system rozpoznawania

drzwiczki dla psa

właściciel przycisk

żądanie zewnątrz / do domu

pilot szczekanie

Oto wszystkie rzeczowniki, jakie zakreśliliśmy w naszym przypadku użycia.

192

Rozdział 4.

Analiza No dobrze… rozumiem. Niemal wszystkie z tych rzeczowników są klasami w mojej wersji systemu.

0DULD To dobrze. To właśnie dzięki tej metodzie ustaliłam, że należy stworzyć klasę %DUN… Pojawiła się jako rzeczownik w przypadku użycia w krokach 2. oraz 6.3. A zatem napisałam taką klasę. 5DGHN Ach… więc to w tym miejscu się pomyliłem… Gdybym przeanalizował przypadek użycia i zaznaczył w nim wszystkie rzeczowniki, to też bym wiedział, że należy napisać klasę %DUN. 0DULD Prawdopodobnie. Bardzo często, nawet jeśli uważam, że stworzyłam już wszystkie niezbędne klasy, porównuję moje pomysły z rzeczownikami w przypadku użycia, by upewnić się, że niczego nie przegapiłam. 6]\PHN Jednak dla niektórych spośród rzeczowników występujących w naszym przypadku użycia nie będzie konieczne tworzenie klas. Chodzi mi, na przykład, o takie rzeczowniki, jak: „właściciel”, „żądanie” czy też „wnętrze”. 0DULD Tak, to prawda. Oczywiście będziesz musiał się wykazać pewnym wyczuciem i zrozumieniem tworzonego systemu. Pamiętaj, że potrzebne Ci są klasy tylko dla tych elementów systemu, które chcesz reprezentować w programie. Nie będą Ci zatem potrzebne klasy dla „właściciela”, „wnętrza” czy też „zewnętrza”. 5DGHN Ale zapewne będziemy potrzebowali klasy dla „przycisku”, gdyż stanowi on jedną z części tworzących pilota — a my już mamy klasę reprezentującą pilota. 6]\PHN Wszystko jest super, ale tak sobie właśnie myślałem… Ja także wymyśliłem, żeby stworzyć klasę %DUN, a wcale nie musiałem do tego celu analizować przypadku użycia. 0DULD Tak… ale w efekcie nie udało ci się stworzyć systemu, który by działał w pełni poprawnie, nieprawdaż? 6]\PHN No cóż… ale to tylko dlatego, że ty przechowywałaś w obiekcie drzwiczek więcej niż jeden obiekt %DUN. A co to ma wspólnego z przypadkiem użycia?

Poszukiwanie rzeczowników (i czasowników) w przypadku użycia, w celu określenia klas i metod, nazywamy analizą tekstową.

jesteś tutaj  193

Rzeczowniki są klasami

Wszystko jest ściśle powiązane z przypadkiem użycia Przyjrzyj się dokładniej krokowi 3. w przypadku użycia i dokładnie zobacz, jakie klasy są w nim używane:

ikiem, „pies (właściciela)” jest rzeczown a by któr jednak nie potrzebujemy klasy, faktu, go reprezentowała; wynika to z do że pies jest aktorem i nie należy systemu.

3. Jeśli system rozpozna, że szczeka pies właściciela, to wysyła żądanie otworzenia drzwiczek.

BarkRecognizer door: DogDoor recognize(Bark)

To żądanie, będące kol rzeczownikiem, dla któ ejnym aplikacji nie utworzyliśrego w kodzie w rzeczywistości reprezmy klasy, jest wywołanie metody ope entowane przez która zostaje wykonanan() drzwiczek, rozpoznawania dźwięk przez system ów.

DogDoor open: boolean allowedBarks: Bark [*] open() close() isOpen(): boolean addAllowedBark(Bark) getAllowedBarks(): Bark [*]

Tu nie ma żadnej klasy Bark W przedstawionym powyżej kroku 3. używane są następujące klasy: BarkRecognizer oraz DogDoor… ale nie Bark!

194

Rozdział 4.

Analiza

Chwileczkę… Coś mi się nie podoba. A co jeśli używam trochę innego słownictwa?

3. Jeśli szczekanie psa właściciela pasuje do szczekania usłyszanego przez system rozpoznawania dźwięków, to drzwiczki powinny się otworzyć.

użycia Oto krok 3. z przypadku na potrzeby dka Ra ez prz o neg napisa jego wersji systemu. czekanie” W tym przypadku „sz jest rzeczownikiem.

Krok 3. zapisany przez Radka w jego przypadku użycia opisującym działanie drzwiczek dla psa jest bardzo podobny do kroku 3. naszego przypadku użycia, z tą różnicą, iż koncentruje się on na rzeczowniku „szczekanie”, a nie „pies właściciela”. Czy zatem Radek ma rację? Czy ta cała analiza tekstowa psuje się, jeśli tylko w przypadku użycia zostaną zastosowane niewłaściwe słowa?

A jak Ty myślisz? Wskazówka: Przy podanemu przez jrzyj się dokładnie krokowi 3., działający dokładnRadka. Czy opisuje on system przedstawiony na ie tak samo jak system stronie 194?

jesteś tutaj  195

W przypadkach użycia słowa mają znaczenie

Te dwie rzeczy nie są do siebie podobne… Wygląda na to, że krok 3. z przypadku użycia napisanego przez Radka jest nieco inny niż krok 3. naszego oryginalnego przypadku użycia… Zatem w którym miejscu Radek popełnił błąd? ego Oto krok 3. z naszego oryginaln my przypadku użycia, który napisaliś w rozdziale 3.

A oto i krok 3. z przypadku użycia napisanego przez Radka.

3. Jeśli system rozpozna, że szczeka pies właściciela, to wysyła żądanie otworzenia drzwiczek.

3. Jeśli szczekanie psa właściciela pasuje do szczekania usłyszanego przez system rozpoznawania dźwięków, to drzwiczki powinny się otworzyć.

Koncentrujemy się na: psie właściciela

Koncentrujemy się na: sposobie szczekania psa właściciela

1DV]RU\JLQDOQ\NURNNRQFHQWUXMHVLĊQDSVLH ZáDĞFLFLHOD«EH]Z]JOĊGXQDWRMDNEĊG]LHRQ V]F]HNDü$]DWHPMHĞOLSLHVZáDĞFLFLHODV]F]HND JáRĞQRÄ+UDXX´MHGQHJRGQLDDGUXJLHJRGQLD FLFKRVNRZ\F]HÄ+DXXXX´WRV\VWHPRWZRU]\ GU]ZLF]NLZREXW\FKSU]\SDGNDFK:\QLNDWR ]IDNWXĪHNRQFHQWUXMHP\VLĊQDSVLHDQLHQD NRQNUHWQ\PVSRVRELHV]F]HNDQLD

3U]\SDGHNXĪ\FLDQDSLVDQ\SU]H]5DGND NRQFHQWUXMHVLĊQDVSRVRELHV]F]HNDQLDSVD ZáDĞFLFLHOD«&RVLĊMHGQDNVWDQLHZSU]\SDGNX JG\SLHVEĊG]LHV]F]HNDüQDNLONDUyĪQ\FK VSRVREyZ"$FRMHĞOLGZDSV\EĊGąV]F]HNDá\ ZQDSUDZGĊEDUG]RSRGREQ\VSRVyE"7HQNURN Z\JOąGDEDUG]RSRGREQLHGRQDV]HJRRU\JLQDOQHJR NURNXMHGQDNE\QDMPQLHMQLHMHVWRQWDNLVDP

Hrauu! Hrauu! Houuu!

Dzięki poprawnie zapisanemu krokowi 3. drzwiczki będą otwierane niezależnie od tego, jak Brus zaszczeka.

196

Rozdział 4.

Hrauu! Hrauu!

Wrrrau!

Houuu!

Wrrrau! Ze względu na niewła ści napisany krok 3. tylko wie ze szczeknięć Brusa jedno będzie mu pozwalało na otw orzenie drzwiczek i wyjście na spacer lub powrót do domu.

Analiza Nie istnieją

głupie pytania

P: Zatem twierdzicie, że o ile tylko

P: Wygląda na to, że ta metoda

P: A co jeśli popełnię taki błąd jak

będę pisać przypadki użycia, to moje oprogramowanie będzie działać prawidłowo?

analizy bazująca na wyróżnianiu rzeczowników i czasowników jest dosyć trudna. A moja znajomość gramatyki nie jest rewelacyjna. Co zatem mogę zrobić?

Radek i użyję jakiegoś rzeczownika z mojego przypadku użycia, którego w rzeczywistości nie powinienem użyć?

O: Cóż, bez wątpienia przypadki użycia

są dobrym punktem wyjściowym do pisania dobrego oprogramowania. Jednak absolutnie same przypadki użycia nie wystarczą. Pamiętaj, że dzięki analizie, na podstawie przypadku użycia, możesz określić klasy, jakie powinieneś stworzyć; z kolei w następnym rozdziale poświęcimy nieco czasu na przedstawienie dobrych zasad projektowych, pozwalających poprawnie pisać te klasy.

P: Nigdy wcześniej nie korzystałem

O: Tak naprawdę nie musisz się

koncentrować na gramatyce. Po prostu napisz przypadek użycia w stylu luźnej konwersacji (w dowolnym języku, jakiego używasz). Potem wskaż występujące w nim „rzeczy” — zazwyczaj będą to właśnie rzeczowniki. Następnie dla każdego z tych rzeczowników zastanów się, czy w systemie będzie on musiał być reprezentowany przez klasę. To dobry sposób na rozpoczęcie praktycznej analizy tworzonego systemu.

z przypadków użycia i nigdy nie miałem żadnych problemów. Czy twierdzicie, że pisanie przypadków użycia jest warunkiem koniecznym do tworzenia dobrego oprogramowania?

O: Błąd popełniony przez Radka —

polegający na zastosowaniu rzeczownika „szczekanie” w 3. kroku jego przypadku użycia — nie miał nic wspólnego z jego znajomością gramatyki. Radek nie przemyślał dokładnie przypadku użycia oraz działania jego systemu w rzeczywistym świecie. Zamiast koncentrować się na wpuszczeniu psa właściciela do domu, Radek zajmował się konkretnym sposobem szczekania. Po prostu Radek skoncentrował się nie na tej rzeczy, na jakiej powinien. Pisząc przypadki użycia, kilkakrotnie je przeczytaj i upewnij się, że mają one sens. Być może warto, byś pokazał je także kilku znajomym lub współpracownikom i upewnił się, że będą one prawidłowo działać w rzeczywistym świecie, a nie jedynie w kontrolowanym środowisku.

O: Nie, absolutnie nie. Jest bardzo

wielu programistów, którzy piszą dobre programy, a nawet nie wiedzą, że istnieje coś takiego jak przypadki użycia. Jeśli jednak zależy Ci na zwiększeniu prawdopodobieństwa, że Twoje oprogramowanie zadowoli klienta, i jeśli chciałbyś zmniejszyć ilość poprawek koniecznych do zapewnienia prawidłowego działania kodu, to przypadki użycia na pewno pomogą Ci w przygotowaniu odpowiednich wymagań… jeszcze zanim popełnisz błędy, które ośmieszą Cię przed szefem i klientem.

Dobry przypadek użycia w przejrzysty i dokładny sposób wyjaśnia działanie systemu i jest zapisany w sposób łatwy do zrozumienia. Po napisaniu dobrego przypadku użycia analiza tekstowa pozwoli Ci w szybki i prosty sposób wskazać klasy, które powinny się znaleźć w systemie. jesteś tutaj  197

Ogromne możliwości analizy No dobrze, już rozumiem, na czym polegał błąd Radka: skoncentrował się na szczekaniu, a nie na psie właściciela. Jednak nawet w prawidłowym przypadku użycia nie mamy czegoś takiego jak obiekt reprezentujący psa — na przykład Dog. Czy zatem stosowanie tej metody jest celowe, skoro analiza nie jest nam w stanie powiedzieć, jakie klasy powinniśmy stworzyć i jakich używać?

Analiza tekstowa pokazuje, na czym powinieneś się skoncentrować, a nie jedynie jakie klasy masz stworzyć. Chociaż nie mamy klasy Dog, to jednak analiza tekstowa daje nam ważną podpowiedź dotyczącą tego, co w rzeczywistości powinien robić nasz system: wpuszczać i wypuszczać z domu psa właściciela, niezależnie od tego, jak on szczeka. Innymi słowy, analiza pomogła nam zrozumieć, na czym powinniśmy się skoncentrować… i bynajmniej nie jest to jeden, konkretny sposób szczekania. Kiedy już do tego dojdziesz, warto, byś zastanowił się nad tym, co w rzeczywistości robi pies. Czy zawsze szczeka w taki sam sposób? To właśnie w tym momencie Maria opracowała swoje rozwiązanie systemu: zdała sobie sprawę, że jeśli pies właściciela może szczekać na kilka różnych sposobów, a celem całego systemu jest wypuszczanie go na spacer i ponowne wpuszczanie do domu, to drzwiczki muszą przechowywać wszystkie możliwe sposoby szczekania psa, a nie tylko jeden z nich. Jednak Maria nigdy by na to nie wpadła, gdyby nie przeprowadziła analizy swojego przypadku użycia.

Zaostrz ołówek

Dlaczego nie ma klasy 'RJ?

Kiedy we wcześniejszej części rozdziału zaznaczałeś rzeczowniki występujące w przypadku użycia, jednym z nich był „pies (właściciela)”. Jednak Maria nie zdecydowała się utworzyć klasy 'RJ. Dlaczego? Poniżej zapisz trzy przyczyny, dla których Maria mogła zrezygnować z tworzenia klasy 'RJ w swojej wersji systemu. 1. 2. 3.

2GSRZLHG]L]QDMG]LHV]QDVWURQLH

198

Rozdział 4.

Analiza

Pamiętaj: zwracaj uwagę na rzeczowniki Nawet jeśli rzeczowniki występujące w przypadku użycia nie stają się klasami w tworzonym systemie, to i tak zawsze mają duże znaczenie dla zapewnienia jego prawidłowego działania.

wa „pies W tym przypadku sło raktować właściciela” należy potw systemie jako rzeczownik, choć łcony na zta nie zostaje on przeks klasę…

…co więcej, chociaż słowo „szczeka” nie jest rzeczownikiem, to w systemie używamy klasy Bark*.

3. Jeśli system rozpozna, że szczeka pies właściciela, to wysyła żądanie otworzenia drzwiczek.

Zwracaj uwagę na rzeczowniki występujące w przypadku użycia, nawet jeśli w systemie nie zostaną one przekształcone na klasy.

Najważniejsze jest to, iż rzeczowniki są tymi elementami przypadku użycia, na których należy się skoncentrować. Jeśli w analizowanym przez nas kroku 3. skoncentrujesz się na psie, to bez problemu zorientujesz się, że musisz zadbać o to, by to pies był wypuszczany na zewnątrz i wpuszczany do domu — niezależnie od tego, czy będzie szczekać na jeden, czy na kilka sposobów.

Zastanów się, w jaki sposób klasy należące do systemu mogą wspomóc

DogDoor open: boolean allowedBarks: Bark [*]

Ta kolekcja obiektów Bark w zasadzie reprezentuje konkretnego psa… Stanowi ona tę część przypadku użycia, która jest bezpośrednio związana ze szczekaniem.

open() close() isOpen(): boolean addAllowedBark(Bark) BarkRecognizer getAllowedBark(): Bark [*] door: DogDoor recognize(Bark)

zachowania opisane w przypadku użycia. Chociaż do tej metody jest jeden obiekt Bark, przekazywany przeznaczeniem jest okr to jednak jej szczekał. Metoda ta poześlenie, który pies informacje o wszystki wala przeanalizować ch sposobach szczekania, zapamiętanych to, które zarejestrował by sprawdzić, czy rozpoznawania dźwięk właśnie system u, należy do psa właściciela.

* bark — ang.: szczekać

jesteś tutaj  199

Czasowniki są operacjami Wygląda na to, że podobnie jak rzeczowniki wyróżnione w przypadku użycia, które zazwyczaj stają się klasami, tak czasowniki z przypadku użycia stają się metodami. Czy to nie brzmi sensownie?

Czasowniki występujące w przypadku użycia stają się (zazwyczaj) metodami obiektów używanych w tworzonym systemie. Przekonałeś się już, w jaki sposób rzeczowniki występujące w przypadku użycia stanowią zazwyczaj doskonały punkt wyjściowy do określania NODV, które mogą być potrzebne w tworzonym systemie. Jeśli natomiast przyjrzysz się czasownikom występującym w przypadku użycia, to zazwyczaj mogą Ci one pomóc w określeniu PHWRG, którymi te obiekty powinny dysponować:

Niesamowite drzwiczki dla psa, wersja 3.0 Jak działają drzwiczki

Klasa DogDoor musi udostępniać metody open() oraz close(), wykonujące operacje określone przez te czasowniki.

200

Ścieżki alternatywne Ścieżka główna 1. Pies właściciela szczeka, by wyjść na spacer. 2. System rozpoznawania dźwięków 2.1. Właściciel słyszy, że jego „słyszy” szczekanie Azora. pies szczeka. 3. Jeśli system rozpozna, że szczeka pies 3.1. Właściciel naciska przycisk właściciela, to wysyła żądanie na pilocie. otworzenia drzwiczek. 4. Drzwiczki dla psa otwierają się. 5. Pies właściciela wychodzi na zewnątrz. 6. Pies właściciela załatwia swoje potrzeby. 6.1. Drzwiczki zamykają się automatycznie. 6.2. Pies właściciela szczeka, by wejść do domu. 6.3. System rozpoznawania dźwięków 6.3.1. Właściciel słyszy szczekanie swojego psa (znowu). „słyszy” szczekanie (ponownie). 6.4. Jeśli system rozpozna, że szczeka 6.4.1. Właściciel naciska przycisk pies właściciela, to wysyła żądanie na pilocie. otworzenia drzwiczek. 6.5. Drzwiczki dla psa otwierają się (znowu). 7. Pies właściciela wraca z powrotem. 8. Drzwi automatycznie się zamykają.

Rozdział 4.

Oto kolejne wyrażenie zawierające czasownik: „naciska przycisk”. Nasza klasa Remote dysponuje już metodą pressButton(), która doskonale pasuje do tego wyrażenia.

Analiza

Magnesiki z kodem 1DGV]HGâF]DVQDNROHMQċSRUFMĐDQDOL]\WHNVWRZHM3RQLİHMSU]HGVWDZLOLĤP\SU]\SDGHNXİ\FLD RSLVXMċF\G]LDâDQLHV\VWHPXGU]ZLF]HNGODSVDQDGNWyU\PMXİRGMDNLHJRĤF]DVXSUDFXMHP\ 8GRâXVWURQ\XPLHĤFLOLĤP\PDJQHVLNL]DZLHUDMċFHQD]Z\SU]HZDİDMċFHMZLĐNV]RĤFLNODVLPHWRG MDNLHVWZRU]\OLĤP\GRWHMSRU\7ZRLP]DGDQLHPMHVWGRSDVRZDQLHPDJQHVLNyZ]QD]ZDPLNODVGR U]HF]RZQLNyZZSU]\SDGNXXİ\FLDRUD]PDJQHVLNyZ]QD]ZDPLPHWRGGRF]DVRZQLNyZ6SUDZGĮ ZMDNLPVWRSQLXQD]Z\PHWRGRGSRZLDGDMċF]DVRZQLNRP]DSLVDQ\PZSU]\SDGNXXİ\FLD

Niesamowite drzwiczki dla psa, wersja 3.0 Jak działają drzwiczki

Umieściliśmy tu całkiem sporo magnesików z nazwami klas i metod, więc nie spiesz się.

Ścieżki alternatywne Ścieżka główna 1. Pies właściciela szczeka, by wyjść na spacer. 2. System rozpoznawania dźwięków 2.1. Właściciel słyszy, że jego „słyszy” szczekanie Azora. pies szczeka. 3. Jeśli system rozpozna, że szczeka pies 3.1. Właściciel naciska przycisk właściciela, to wysyła żądanie na pilocie. otworzenia drzwiczek. 4. Drzwiczki dla psa otwierają się. 5. Pies właściciela wychodzi na zewnątrz. 6. Pies właściciela załatwia swoje potrzeby. 6.1. Drzwiczki zamykają się automatycznie. 6.2. Pies właściciela szczeka, by wejść do domu. 6.3. System rozpoznawania dźwięków 6.3.1. Właściciel słyszy szczekanie „słyszy” szczekanie (ponownie). swojego psa (znowu). 6.4. Jeśli system rozpozna, że szczeka 6.4.1. Właściciel naciska przycisk pies właściciela, to wysyła żądanie na pilocie. otworzenia drzwiczek. 6.5. Drzwiczki dla psa otwierają się (znowu). 7. Pies właściciela wraca z powrotem. 8. Drzwi automatycznie się zamykają.

'RJ'RRU 5HPRWH 5HPRWH SUHVV%XWWRQ 'RRU 'RJ %DUN5HFRJQL]HU 5HPRWH UHFRJQL]H %DUN 'RJ'RRU WRQ SUHVV%XWSUHVV%XWWRQ FORVH JHW6RXQG %DUN %DUN %DUN 'RJ'RRU FORVH UHFRJQL]H SUHVV%XWWRQ 5HPRWH RSHQ %DUN5HFRJQL]HU RSHQ %DUN FORVH JHW$OORZHG%DUNV %DUN5HFRJQL]HU FO RV H JHW$OORZHG%DUNV RSHQ JHW6RXQG %DUN5HFRJQL]HU RSHQ UNV G%D RZH $OO JHW

jesteś tutaj  201

Analiza tekstowa

Magnesiki z kodem. Rozwiązania 1DGV]HGâF]DVQDNROHMQċSRUFMĐDQDOL]\WHNVWRZHM3RQLİHMSU]HGVWDZLOLĤP\SU]\SDGHNXİ\FLD RSLVXMċF\G]LDâDQLHV\VWHPXGU]ZLF]HNGODSVDQDGNWyU\PMXİRGMDNLHJRĤF]DVXSUDFXMHP\ 8GRâXVWURQ\XPLHĤFLOLĤP\PDJQHVLNL]DZLHUDMċFHQD]Z\SU]HZDİDMċFHMZLĐNV]RĤFLNODVLPHWRG MDNLHVWZRU]\OLĤP\GRWHMSRU\7ZRLP]DGDQLHPMHVWGRSDVRZDQLHPDJQHVLNyZ]QD]ZDPLNODVGR U]HF]RZQLNyZZSU]\SDGNXXİ\FLDRUD]PDJQHVLNyZ]QD]ZDPLPHWRGGRF]DVRZQLNyZ6SUDZGĮ ZMDNLPVWRSQLXQD]Z\PHWRGRGSRZLDGDMċF]DVRZQLNRP]DSLVDQ\PZSU]\SDGNXXİ\FLD

Niesamowite drzwiczki dla psa, wersja 3.0 Jak działają drzwiczki Ścieżka główna

Ścieżki alternatywne

1. Pies właściciela szczeka, by wyjść na spacer.

Zwróć uwagę na to, że większość kroków, w których nie umieściliśmy żadnych magnesików, odpowiada zdarzeniom zachodzącym poza systemem, na które system następnie reaguje.

%DUN5HFRJQL]HU 2. System rozpoznawania dźwięków %DUN „słyszy”UHFRJQL]H szczekanie Azora.

2.1. Właściciel słyszy, że jego pies szczeka.

RZHG%D UNV pies 3. Jeśli system JHW$OO rozpozna, że szczeka QL]HU FRJ N5H %DU właściciela, to wysyła żądanie 'RJ'RRU RSHQ otworzenia drzwiczek.

SUHV V%XWWRprzycisk 3.1. Właściciel naciska Q na pilocie. 5HPRWH

RSHQ się. 'RJ'RRU 4. Drzwiczki dla psa otwierają

5. Pies właściciela wychodzi na zewnątrz. 6. Pies właściciela załatwia swoje potrzeby. FORautomatycznie. VH 6.1. Drzwiczki zamykają się RRU 'RJ'

6.2. Pies właściciela szczeka, by wejść do domu. HU %DUN5HFRJQL]dźwięków 6.3. System %DUN rozpoznawania UHFRJQL]H „słyszy” szczekanie (ponownie).

FRJQL] HU że szczeka 6.4. Jeśli%DUN5H system rozpozna, RSHQ U RR J' 'R pies właściciela, to wysyła żądanie otworzenia drzwiczek. 'RJ'RRdla RSHQ się (znowu). U psa otwierają 6.5. Drzwiczki

7. Pies właściciela wraca z powrotem. VH 8. Drzwi automatycznie sięFOR zamykają. 'RJ'RRU

202

Rozdział 4.

6.3.1. Właściciel słyszy szczekanie swojego psa (znowu). XWWRQ SUHVV% 6.4.1. Właściciel naciska przycisk RWH na 5HP pilocie. tekstu Nawet po zastąpieniu użycia wciąż dek ypa prz i am sik magne i sensowny! To jest całkiem zrozumiały że nasze klasy y, jąc azu pok dobry znak dokładnie tak, i metody zachowują sięi że cały system y, iśm jak przypuszczal ces. na pewno odniesie suk

Analiza

Zaostrz ołówek

Rozwiązanie

Czasami może się zdarzyć, że takie rozwiązanie będzie potrzebne, jednak zazwyczaj ma to miejsce wyłącznie w sytuacjach, gdy konieczne jest prowadzenie interakcji z takimi rzeczami. W naszym przypadku nie musimy prowadzić żadnej interakcji z psem.

Dlaczego Maria nie stworzyła klasy Dog? Kiedy we wcześniejszej części rozdziału zaznaczałeś rzeczowniki występujące w przypadku użycia, jednym z nich był „pies (właściciela)”. Jednak Maria nie zdecydowała się utworzyć klasy Dog. Dlaczego? Poniżej zapisz trzy przyczyny, dla których Maria mogła zrezygnować z tworzenia klasy Dog w swojej wersji systemu.

1. Pies nie jest elementem należącym do systemu, a takich rzeczy zazwyczaj nie

musimy reprezentować w systemie. 2. Pies nie jest obiektem programowym (i nie powinien nim być)… Żywych istot

zazwyczaj nie reprezentuje się w formie klas, chyba że system będzie przez dłuższy czas przechowywać informacje na ich temat. 3. Nawet gdybyś dysponował klasą Dog, to i tak na niewiele by się przydała

w pozostałej części systemu. Na przykład „przechowywanie” obiektu psa w obiekcie drzwiczek tak naprawdę nie miałoby większego sensu.

ć referencję do obiektu Dog Oczywiście, można by przechowywa ób w rzeczywistości, można spos w klasie DogDoor, jednak w jaki ch? Pamiętaj, że to, co da się by przechowywać psa w drzwiczka nie zawsze może istnieć ia, stworzyć w formie oprogramowan się, by Twoje aplikacje w rzeczywistym świecie. Staraj wiadały rzeczywistości! w jak największym stopniu odpo

Bardzo często można spotkać takie klasy jak User (ang. użytko wn (ang. menedżer), jednak ik) lub Manager reprezentują one role w systemie bąd przechowywania informa ź służą do cji o numerach kart kredytowych lub adresów. W żaden sposób nie można do nich porównać psa.

Nie istnieją

głupie pytania

P: A zatem rzeczowniki występujące w przypadku użycia P: Wygląda na to, że rzeczowniki reprezentujące rzeczy stają się klasami, a czasowniki — metodami?

O: W przeważającej większości przypadków właśnie tak się dzieje.

Lecz w rzeczywistości należałoby raczej ująć to w następujący sposób: rzeczowniki są kandydatami na klasy… jednak nie każdy z nich stanie się klasą. Na przykład występujące w naszym przypadku użycia słowo „właściciel” jest rzeczownikiem (sprawdź kroki 2.1 oraz 3.1), jednak nie potrzebujemy klasy reprezentującej właściciela. A zatem choć „właściciel” jest kandydatem na klasę, to jednak w faktycznym systemie takiej klasy nie napiszemy. Analogicznie, czasowniki są kandydatami na operacje. Na przykład wyrażenie „załatwia swoje potrzeby” można by uznać za taki „czasownik”, trudno jednak by sobie wyobrazić zamienienie go na jakąś metodę. Mamy nadzieję, że zgodzisz się z naszymi decyzjami! Pomimo tych niejednoznaczności analiza tekstowa jest doskonałym punktem startowym do rozważań na temat klas i metod, jakie powinny się znaleźć w tworzonym systemie.

znajdujące się poza systemem nie są przekształcane na klasy. Czy zawsze się tak dzieje?

O: W przeważającej większości przypadków. Jedynym często

występującym wyjątkiem od tej reguły jest sytuacja, w której konieczne jest prowadzenie interakcji z obiektami spoza systemu — jeśli na przykład istnieje jakiś stan lub jakaś operacja, której system musi cyklicznie używać. W przypadku naszego systemu do obsługi drzwiczek dla psa klasa reprezentująca właściciela nie będzie potrzebna, gdyż wszystkie czynności, które w jakiś sposób mogą się wiązać z właścicielem, realizuje klasa 5HPRWH. Odpowiednią klasę moglibyśmy stworzyć, jeśli musielibyśmy śledzić stan właściciela, na przykład czy aktualnie śpi.

jesteś tutaj  203

Diagram klas Marii

Od dobrej analizy do dobrych klas… Kiedy już określiłam, jakich klas i operacji będę potrzebować, odpowiednio zaktualizowałam diagram klas.

Diagram klas systemu obsługi drzwiczek dla psa, przygotowany przez Marię Remote

BarkRecognizer

pressButton()

recognize(Bark)

door

A gdzie zniknął atrybut door z klasy Remote?

door 1

1

DogDoor open: boolean

Dlaczego atrybut allowedBarks został przeniesiony stąd…

open() close() isOpen(): boolean addAllowedBark(Bark) getAllowedBarks(): Bark [*]

...tutaj?

allowedBarks *

Bark sound: String getSound(): String equals(Object bark): boolean

204

Rozdział 4.

Już na pierw sz oka widać, że y rzut w swoim diag wprowadziła ramie Maria pewne nowe elementy.

Pamiętaj — gwiazdka oznacza, że metoda getAllowedBarks() może zwracać większą liczbę obiektów Bark.

Analiza

¥OHG]WZRZVSUDZLH80/ Maria naprawdę zwariowała na punkcie swoich diagramów UML… Czy sądzisz, że uda Ci się zrozumieć to, co narysowała? Do przedstawionego poniżej diagramu dodaj notatki opisujące wszystkie nowe, dodane przez Marię elementy, i postaraj się domyślić, jakie znaczenie mają wszystkie umieszczone na nim linie, liczby i słowa. Aby ułatwić Ci rozpoczęcie pracy, sami zapisaliśmy dwie notatki.

Remote

BarkRecognizer

pressButton()

Klasa Remote zawiera referencję do klasy DogDoor.

recognize(Bark)

door

door 1

1

DogDoor open: boolean open() close() isOpen(): boolean addAllowedBark(Bark) getAllowedBarks(): Bark [*]

allowedBarks Klasa DogDoor zawiera atrybut o nazwie allowedBarks.

*

Bark sound: String getSound(): String equals(Object bark): boolean

2GSRZLHG]LSRGDOLĂP\QDVWURQLH

jesteś tutaj  205

Asocjacje i liczebność

Diagramy klas bez tajemnic Diagramy klas to coś znacznie więcej niż jedynie prostokąty i trochę tekstu. Przekonajmy się, w jaki sposób kilka dodatkowych linii i strzałek może pozwolić nam umieścić na diagramie znacznie więcej informacji. ej klasy do drugiej Linia ciągła prowadząca od jedn ona, że jedna jest nazywana asocjacją. Oznacza na z drugą — iąza klasa jest w pewien sposób pow dziedziczenie czy , poprzez referencję, rozszerzenie w jakiś inny sposób.

Ta linia prowadzi od klasy źródłowej (Remote) do klasy docelowej (DogDoor). Oznacza to, że klasa źródłowa — Remote — zawiera atrybut typu DogDoor, czyli typu klasy docelowej.

Remote W przypadku używania asocjacji do reprezentowania atrybutów zazwyczaj nie zapisuje się w diagramie klasy nazwy atrybutu, który reprezentuje asocjacja. To właśnie z tego powodu w diagramie klasy Remote nie znajdziemy już atrybutu o nazwie door.

pressButton()

allowedBarks

Bark sound: String getSound(): String equals(Object bark): boolean 206

Klasa DogDoor zawiera atrybut o nazwie allowedBarks, który służy do przechowywania obiektów Bark.

Rozdział 4.

* Liczebność atrybu allowedBarks jes tu nieograniczona; t oz to, że atrybut te nacza zawierać nieogra n może liczbę obiektów niczoną Bark.

Analiza

Nazwa atrybutu klasy na wa źródłowej jest zapisy linii tutaj, na samym końcu zatem łączącej obie klasy. A atrybut klasa Remote zawiera Door. o nazwie door, typu Dog

door

1

Ta liczba określa liczebność asocjacji, czyli to, ile danych typu docelowego jest przechowywanych w atrybucie klasy źródłowej. W naszym przypadku atrybut door może przechowywać tylko jeden obiekt DogDoor.

DogDoor open: boolean

ze Porównaj ten diagram który rii, Ma s kla m ate schem onie przedstawiliśmy na str lne 204. Chociaż poszczegó e nieco klasy są rozmieszczon to t inaczej, to jednak jes TEN SAM diagram. A zatem sposób rozmieszczenia klas na nia. cze diagramie nie ma zna

open() close() isOpen(): boolean addAllowedBark(Bark) getAllowedBarks(): Bark [*] Odpowiedź znajdziesz na stronie 209.

Zaostrz ołówek Opierając się na powyższym diagramie klas, podaj, jakiego typu mógłby być atrybut DOORZHG%DUNV należący do klasy 'RJ'RRU. Swoje pomysły zapisz poniżej.

jesteś tutaj  207

Wciąż więcej UML-a

¥OHG]WZRZVSUDZLH80/]DNRñF]RQH Maria naprawdę zwariowała na punkcie swoich diagramów UML… Czy sądzisz, że uda Ci się zrozumieć to, co narysowała?

Te linie prowadzą od klas zawierających referencje do klas będących typami tych referencji.

sa Wygląda na to, że kla h Remote nie ma żadnyc nak atrybutów… kiedy jed jedna klasa odwołuje ie się do drugiej, to tak uje ent rez rep nie odwoła atrybut. A zatem klasa a Remote wciąż zawier jeden atrybut.

Remote pressButton()

Klasa Remote zawiera referencję do klasy DogDoor; do jej przechowywania służy atrybut o nazwie door. Atrybut door en przechowuje jed obiekt DogDoor.

Klasa DogDoor zawiera atrybut o nazwie allowedBarks; atrybut ten jest typu Bark.

e W atrybuci klasy ks allowedBar na oż DogDoor m ać yw ow ch ze pr ną liczbę nieograniczoark. B ów kt obie

208

Rozdział 4.

BarkRecognizer recognize(Bark)

door

door 1

1

DogDoor open: boolean

Klasa BarkR zawiera jedeecognizer atrybut o na n door typu D zwie atrybut ten ogDoor; do przechow służy referencji doywnaia obiektu DogDoor.

open() close() isOpen(): boolean addAllowedBark(Bark) getAllowedBarks(): Bark [*]

allowedBarks *

Ta gwiazdka oznacza „nieograniczoną liczbę”.

Bark sound: String getSound(): String equals(Object bark): boolean

Analiza

Rozwiązania ćwiczeń

Zaostrz ołówek

Rozwiązanie

Opierając się na powyższym diagramie klas, podaj, jakiego typu mógłby być atrybut allowedBarks należący do klasy DogDoor. Swoje pomysły zapisz poniżej.

List, Array, Vector itp. Mogłeś tu zapisać dowolny typ dany umożliwiający przechowywanie wiel ch, W języku Java warunek ten speł u obiektów… nia znakomita większość klas typu Collection.

door 1

Remote

DogDoor open: boolean

pressButton()

allowedBarks

Bark

open() close() isOpen(): boolean addAllowedBark(Bark) getAllowedBarks(): Bark [*]

*

sound: String getSound(): String equals(Object bark): boolean

Zwróć uwagę choć narysow na to, iż ten diagram, sposób, zawieany w całkowicie inny klasy i asocja ra dokładnie te same poprzedniej stcje co diagram na ronie.

jesteś tutaj  209

Dlaczego warto używać diagramów klas? Wciąż nie do końca rozumiem, do czego są mi potrzebne te wszystkie diagramy… 5DGHN Może i pominąłem klasę %DUN, jednak moje rozwiązanie nie było aż tak złe, a co więcej, nie marnowałem swojego czasu na rysowanie jakichś prostokątów i strzałek. 0DULD Czy nigdy nie słyszałeś, że jeden rysunek jest wart więcej niż tysiąc słów? Kiedy już narysowałam diagram klas, mogłam lepiej zrozumieć, jak będzie działać cały system. 5DGHN Cóż… faktycznie łatwo to zauważyć… ale ja także całkiem dobrze wiedziałem, jak system będzie działać. Tylko nie przelałem tej wiedzy na papier. 6]\PHN Wiesz co, Radku, a ja myślę, że się zainteresuję tym całym UML-em. Uważam, że skoro i tak już mamy przypadek użycia, to wykonanie analizy wydaje się naturalnym rozwiązaniem, podobnie jak przekształcenie rzeczowników na klasy. Wygląda na to, że dzięki temu nie trzeba będzie poświęcać tak dużo czasu na zastanawianie się nad zawartością poszczególnych klas. 0DULD Właśnie! Nie cierpię straty czasu na pisanie grupy klas tylko i wyłącznie po to, by po chwili zdać sobie sprawę, że czegoś w nich brakuje. Jeśli jednak popełnię jakiś błąd, kiedy korzystam z przypadków użycia i diagramów klas, to wystarczy wymazać jakiś fragment klasy i powtórnie go narysować. Czy pamiętasz, jak stwierdziliśmy, że analiza i projektowanie obiektowe pomoże Ci tworzyć wspaniałe oprogramowanie, i to za każdym razem? To właśnie jeden ze sposobów, w jaki analiza i projektowanie obiektowe pomaga Ci unikać popełniania błędów w tworzonym kodzie.

5DGHN Cóż, chyba macie rację. Przepisanie kodu zajmuje znacznie więcej czasu niż powtórne napisanie przypadku użycia lub narysowanie diagramu klas. 0DULD A poza tym, jeśli kiedykolwiek będziesz musiał współpracować z innymi osobami, to w jakiś sposób będziesz musiał wytłumaczyć im postać i działanie tworzonego systemu, nieprawdaż? 6]\PHNOna chyba ma rację! Widziałem twoją tablicę, kiedy próbowałeś wyjaśniać swoje pomysły… Co za bałagan! 5DGHN No dobrze, nawet ja nie mogę podważyć takich argumentów. Niemniej jednak cały czas uważam, że diagramy klas nie pokazują wszystkiego, co może być potrzebne. Na przykład, w jaki sposób nasz kod będzie porównywać sposoby szczekania różnych psów i na jakiej podstawie określi, czy należy otworzyć drzwiczki?

DogDoor Bark

open: boolean

BarkRecognizer Remote door: DogDoor

open() door: DogDoor close() ring isOpen(): boolean pressButton() getSound(): St ean ol bo ): rk ba t addAllowedBark(Bark) equals(Objec getAllowedBarks(): Bark [*]

sound: String

210

Rozdział 4.

recognize(Bark)

Analiza

Diagramy klas to nie wszystko Diagramy klas są doskonałym rozwiązaniem, pozwalającym uzyskać ogólny obraz systemu i przedstawiać jego fragmenty współpracownikom oraz innym programistom. Niemniej jednak wciąż jest sporo informacji, których na diagramach klas pokazać nie można.

Informacje o typach danych udostępniane przez diagramy klas są ograniczone

Bark sound: String

* allowedBarks

getSound(): String equals(Object bark): boolean wedBarks może Wiemy, że atrybut allo ektów Bark, obi le wie ać przechowyw u tego atrybutu… jednak nie znamy typ A może Vector? t? Lis Może jest to typ ego? A może jeszcze coś inn

DogDoor open: boolean open() close() isOpen(): boolean addAllowedBark(Bark) getAllowedBarks(): Bark [*]

Dokładnie ten sam problem występuje z typami wartości wynikowych… Jakiego typu jest wartość zwracana przez metodę getAllowedBarks()?

Diagramy klas nie podpowiadają, w jaki sposób tworzyć kod poszczególnych metod

BarkRecognizer door: DogDoor recognize(Bark)

Ten diagram nie zawier a żadnych informacji o tym, co powinna robić metoda recognize()… ani nawet nie tłumaczy, dlaczego jej jest pojedynczy obiekt argumentem typu Bark.

Diagramy klas prezentują jedynie bardzo ogólną postać systemu

Remote door: DogDoor pressButton()

nie ogólnie podać Powinieneś być w sta klasy Remote, a eni motywację utworz diagramu, jednak, na podstawie określić jej ie yjn cyz pre żna mo nie sposób działania przeznaczenia. Cel i eślić jedynie okr y żem mo tej klasy użycia oraz dku ypa prz na podstawie wymagań.

jesteś tutaj 

211

Czego brakuje na diagramie?

?

: 7

7

CZEGO BRAKUJE? 7

Diagramy klas doskonale nadają się do modelowania klas, które trzeba napisać, jednak nie są w stanie udzielić odpowiedzi na wszystkie pytania, jakie pojawią się podczas tworzenia kodu aplikacji. Już się przekonałeś, że diagram klas naszego systemu do obsługi drzwiczek dla psa nie zawiera wielu informacji na temat wartości wynikowych. Jak sądzisz, jakich innych informacji, niezbędnych podczas tworzenia kodu systemu, nie można w jasny i precyzyjny sposób określić na podstawie diagramu klas? Do poniższego diagramu dodaj notatki o dodatkowych informacjach, które będą potrzebne podczas tworzenia systemu. Aby ułatwić Ci rozpoczęcie ćwiczenia, dodaliśmy do schematu jedną notatkę, dotyczącą porównywania informacji o sposobach szczekania.

Remote

BarkRecognizer

pressButton()

recognize(Bark)

door 1

1

DogDoor open: boolean open() close() isOpen(): boolean addAllowedBark(Bark) getAllowedBarks(): Bark [*]

allowedBarks *

Bark sound: String getSound(): String equals(Object bark): boolean

212

Rozdział 4.

Ta metoda sprawdzić, musi do niej obieczy przekazany jest taki sa kt Bark jeden z obiem jak przechowyw któw Bark w obiekcie anych W jaki sposdrzwiczek. dokonać ta ób należy ki porównania? ego

2GSRZLHG]L]QDMG]LHV]QDVWURQLH

door

Analiza

W jaki zatem sposób ma działać metoda recognize()? Maria domyśliła się, że jej klasa %DUN5HFRJQL]HU powinna umożliwiać porównywanie informacji o sposobie szczekania, zarejestrowanych przez system rozpoznawania dźwięków, ze wszystkimi informacjami zapisanymi w obiekcie 'RJ'RRU; jednak diagram klas nie mówi nam, jak w praktyce powinniśmy napisać kod metody UHFRJQL]H . W tym celu musimy zatem przeanalizować nasz kod metody UHFRJQL]H klasy %DUN5HFRJQL]HU, napisany przez Marię — przedstawiliśmy go poniżej. Z obiektu drzwiczek Maria pobiera całą list ę obiektów Bark.

Iterator to specjalny obiekt, pozwalający nam na pobranie po kolei każdego elementu listy.

W kodzie Marii, podobnie jak w klasie BarkRecognizer napisanej przez Szymka, porównywanie informacji o szczekaniu delegowane jest do obiektu Bark.

SXEOLFYRLGUHFRJQL]H %DUNEDUN ^ 6\VWHPRXWSULQWOQ ĵ%DUN5HFRJQL]HUXVï\V]DQRIJĵ EDUNJHW6RXQG ĵijĵ  /LVWDOORZHG%DUNV GRRUJHW$OORZHG%DUNV  IRU ,WHUDWRUL DOORZHG%DUNVLWHUDWRU LKDV1H[W  ^ %DUNDOORZHG%DUN  %DUN LQH[W      ekt pobrany obi Każdy LI DOORZHG%DUNHTXDOV EDUN ^ z iteratora rzutujemy GRRURSHQ  do obiektu typu Bark. UHWXUQ   Ta inst  rukcja sprawia, że gdy odna pasujące obiekty Bark, wykonyw jdziemy ` anie pętli zostanie przerwane. ` 6\VWHPRXWSULQWOQ ĵ7HQSLHVQLHPRĝHZHMĂÊĵ  `

Analiza tekstowa wykonana przez Marię pomogła jej zrozumieć, że klasa BarkRecognizer powinna się skoncentrować na psie, który szczeka, a nie na samym sposobie szczekania.

DUNV

GRRUJHW$OORZHG%

entuje Ta metoda reprez zystkie całego psa — ws ekania, cz jego sposoby sz i itd. wydawane dźwięk

DOORZHG%DUNJHW6RXQG



Ta metoda koncentruje na konkretnym sposob się ie szczekania… na jednym wydawanym przez psa dźwięku , a nie na samym psie.

jesteś tutaj  213

Rozwiązanie zagadki

?

: 7

7

CZEGO BRAKUJE?

Rozwiązania ćwiczeń

7

Do poniższego diagramu dodaj notatki dotyczące informacji, które mogą Ci być potrzebne do napisania oprogramowania sterującego drzwiczkami dla psa.

Remote

BarkRecognizer

pressButton()

recognize(Bark)

oda Co robi met ()? on tt u sB pres

door 1

Czy w każdym systemie będzie tylko jeden obiekt DogDoor?

1

DogDoor open: boolean open() close() isOpen(): boolean addAllowedBark(Bark) getAllowedBarks(): Bark [*]

allowedBarks * Jakiego typu obiektu użyć do przechowywania dowolnej liczby obiektów Bark?

Bark sound: String

Ta metoda musi spraw czy przekaza dzić, Bark pasuje ny do niej obiekt do kt óregoś ze sposobów przechowyw szczekania an yc h w formie obiektów te w obiekcie go samego typu D sposób moż ogDoor. W jaki porównania?na dokonać takiego

Czy metody open() i close() jedynie zmieniają stan drzwiczek, czy robią jeszcze coś więcej? y robić Nie wiadomo, co miałyb h tyc z dej każ ory konstrukt być ich klas… ani jakie mają argumenty.

getSound(): String equals(Object bark): boolean

* To tylko kilka spośród wielu pytań, które można by zadać. Jeśli pomyślałeś o innych informacjach, których diagram klas nie pokazuje, to Twoje odpowiedzi mogą być całkowicie odmienne.

214

Rozdział 4.

Analiza

ę doczekać, by go k nie mogą si iałe Szymek i Radektóry pozbawił ich wspan zobaczyć kod, o. MacBooka Pr

A zatem kiedy w końcu będziemy mogli zobaczyć ostateczną wersję kodu napisanego przez Marię?

CELNE SPOSTRZEŻENIA Analiza pomaga uzyskać pewność, że nasze oprogramowanie będzie działać w rzeczywistym kontekście, a nie jedynie w środowisku doskonałym.

Q

Q

Diagramy klas są prostym sposobem przedstawienia systemu oraz konstrukcji jego kodu, jednak operują na bardzo wysokim poziomie.

Q

Przypadki użycia mają być zrozumiałe dla Ciebie, Twojego szefa, klientów i innych programistów.

Q

Atrybuty umieszczane na diagramach klas zazwyczaj stają się zmiennymi składowymi klas.

Q

Przypadki użycia powinny być pisane w sposób, który jest najbardziej zrozumiały i przydatny dla Ciebie oraz innych osób, które będą je analizować.

Q

Operacje umieszczane na diagramach klas zazwyczaj stają się metodami.

Q

Q

Dobry przypadek użycia precyzyjnie opisuje sposób działania systemu, jednak nie zawiera informacji o tym, jak mają być realizowane poszczególne operacje.

Diagramy klas nie zawierają wielu szczegółowych informacji, takich jak postać i działanie konstruktorów klas, niektóre informacje o typach oraz przeznaczenie operacji dostępnych w poszczególnych klasach.

Q

Każdy przypadek użycia powinien koncentrować się tylko na jednym celu użytkownika. Jeśli system ma spełniać kilka takich celów, to dla każdego z nich będziesz musiał napisać osobny przypadek użycia.

Q

Analiza tekstowa ułatwia przekształcenie przypadków użycia na klasy, atrybuty i operacje.

Q

Rzeczowniki umieszczane w przypadkach użycia są kandydatami na klasy, a czasowniki — na operacje udostępniane przez te klasy.

jesteś tutaj  215

Rozwiąż wszystkie problemy występujące w kodzie

=DJDGNDSURMHNWRZD

Założę się, że miałeś nadzieję znaleźć tu cały napisany przeze mnie kod, nieprawdaż? Też bym tak chciała… ale cały mój kod uległ zniszczeniu podczas przenoszenia go do mojego nowego MacBooka Pro. Czy możesz mi pomóc?

Stary komputer Marii zniszczył cały napisany przez nią kod aplikacji do sterowania drzwiczkami, uratował się jedynie plik 'RJ'RRU6LPXODWRUMDYD, którego zawartość przedstawiliśmy na następnej stronie. Wszystko, co pozostało z reszty, to fragmenty kodu zamieszczone we wcześniejszej części rozdziału, diagramy klas oraz to, czego się nauczyłeś o analizie, wymaganiach i projektowaniu obiektowym.

3UREOHP Musisz napisać kod aplikacji do sterowania drzwiczkami dla psa, tak by zaspokoiła potrzeby i oczekiwania nowych klientów firmy Darka (a jest szansa na sprzedanie wielu egzemplarzy systemu), zwłaszcza tych, którzy mieszkają w okolicy, gdzie jest wiele psów. Drzwiczki powinny działać w sposób opisany przez przypadki użycia przedstawione w tym rozdziale. 7ZRMH]DGDQLD 1

Zacznij od ponownego stworzenia aplikacji, w sposób opisany w rozdziale 3. Aby ułatwić sobie zadanie, możesz skopiować kod aplikacji z serwera FTP wydawnictwa Helion.

2

Skopiuj lub pobierz kod pliku 'RJ'RRU6LPXODWRUMDYD (przedstawiony na następnej stronie). To jedyny plik, jaki przetrwał awarię starego laptopa Marii.

3

Upewnij się, że Twój kod będzie odpowiadać diagramowi klas narysowanemu przez Marię i przedstawionemu na stronie 204.

4

Zabierz się za pisanie kodu! W pierwszej kolejności skoncentruj się na tym, by można było skompilować wszystkie klasy; jest to bowiem warunek konieczny do rozpoczęcia dalszych testów.

5

Skorzystaj z klasy 'RJ'RRU6LPXODWRU, by upewnić się, że system działa zgodnie z oczekiwaniami.

6

Analizuj i koduj, aż do momentu gdy wyniki generowane przez klasę testową będą odpowiadać wynikom przedstawionym na następnej stronie.

7

Kiedy już uznasz, że Twoja wersja systemu działa prawidłowo, porównaj swój kod z naszym — zamieszczonym na serwerze FTP wydawnictwa Helion. Będziemy czekać!

216

Rozdział 4.

Analiza

SXEOLFFODVV'RJ'RRU6LPXODWRU^ SXEOLFVWDWLFYRLGPDLQ 6WULQJ>@DUJV ^ 'RJ'RRUGRRU QHZ'RJ'RRU  GRRUDGG$OORZHG%DUN QHZ%DUN ĴKDXXĵ  GRRUDGG$OORZHG%DUN QHZ%DUN Ĵ+UDXXĵ  GRRUDGG$OORZHG%DUN QHZ%DUN ĴKDXKDXXĵ  GRRUDGG$OORZHG%DUN QHZ%DUN ĴKDXXXXXĵ  %DUN5HFRJQL]HUUHFRJQL]HU QHZ%DUN5HFRJQL]HU GRRU  5HPRWHUHPRWH QHZ5HPRWH GRRU  6\PXOXMHP\ĴXVï\V]HQLHĵV]F]HNDQLDSU]H]V\VWHP 6\VWHPRXWSULQWOQ Ĵ%UXV]DF]\QDV]F]HNDÊĵ  UHFRJQL]HUUHFRJQL]H QHZ%DUN ĴKDXXĵ 

FODVV 'RJ'RRU ^ RSHQ `

DogDoorSimulator.java

To jest klasa służąca do testowania aplikacji, uratowana ze starego laptopa Marii. Użyj jej do testowania swojej wersji systemu.

6\VWHPRXWSULQWOQ Ĵ?Q%UXVZ\V]HGïQD]HZQÈWU]ĵ  WU\^ 7KUHDGFXUUHQW7KUHDG VOHHS   `FDWFK ,QWHUUXSWHG([FHSWLRQH ^` 6\VWHPRXWSULQWOQ Ĵ?Q%UXV]DïDWZLïVZRMHSRWU]HE\ĵ  6\VWHPRXWSULQWOQ ĴDOHXWNQÈïQD]HZQÈWU]ĵ  6\PXOXMHP\ĴXVï\V]HQLHĵV]F]HNDQLD DOHQLH%UXVD %DUNVPDOO'RJ%DUN QHZ%DUN ĴKLDDDXĵ  6\VWHPRXWSULQWOQ Ĵ0Dï\SLHV]DF]ÈïV]F]HNDÊĵ  UHFRJQL]HUUHFRJQL]H VPDOO'RJ%DUN  WU\^ 7KUHDGFXUUHQW7KUHDG VOHHS   `FDWFK ,QWHUUXSWHG([FHSWLRQH ^` 6\PXOXMHP\ĴXVï\V]HQLHĵV]F]HNDQLDSU]H]V\VWHP SRQRZQLH 6\VWHPRXWSULQWOQ Ĵ?Q%UXV]DF]\QDV]F]HNDÊĵ  UHFRJQL]HUUHFRJQL]H QHZ%DUN ĴKDXXĵ  6\VWHPRXWSULQWOQ Ĵ?Q%UXV]QRZXMHVWZGRPXĵ  ` `

nerowane przez Oto wyniki wyge pokazujące, że y, program testow zają Brusa, ale drzwiczki wpuszc nie innego psa.

jesteś tutaj  217

Zdefiniuj mnie, proszę :

7

?

7

WSKAŻ MOJĄ DEFINICJĘ 7

Terminologia związana z diagramami UML oraz przypadkami użycia jest dosyć podobna, lecz nie taka sama jak terminologia programistyczna, którą na pewno doskonale znasz. Poniżej podaliśmy kilka terminów związanych z analizą i projektowaniem obiektowym oraz ich definicje… niestety wszystko się nam pomieszało. Połącz terminy z odpowiednimi definicjami i uporządkuj cały ten bałagan.

analiza rzeczowników

/LVWDZV]\VWNLFKNRQVWUXNFMLZUD]]LFKDWU\EXWDPLLRSHUDFMDPL

liczebność

7RWHUPLQMÚ]\ND80/NWöU\]D]Z\F]DMUHSUH]HQWXMHPHWRGÚ]GHILQLRZDQÈZMHGQHM]NODV

atrybut

3RPDJDZRNUHĂOHQLXNDQG\GDWöZQDPHWRG\RELHNWöZXĝ\ZDQ\FKZV\VWHPLH

diagram klas

:JUDILF]Q\VSRVöESRND]XMHLĝMHGQDNODVDMHVWZSHZLHQVSRVöESRZLÈ]DQD]LQQÈNODVÈEDUG]R F]ÚVWR]DSRĂUHGQLFWZHPDWU\EXWX

operacja

2GSRZLHGQLN]PLHQQHMVNïDGRZHMNODV\

asocjacja

2SLVXMHMDNZLHOHGDQ\FKSHZQHJRW\SXPRĝQD]DSLVDÊZDWU\EXFLHNODV\

analiza czasowników

-HVWRQDZ\NRQ\ZDQDQDSU]\SDGNXXĝ\FLDZFHOXRNUHĂOHQLDNODVMDNLHSRZLQQ\VLÚ]QDOHěÊ ZV\VWHPLH

218

Rozdział 4.

jesteś tutaj  219 :

7

?

7

WSKAŻ MOJĄ DEFINICJĘ

Rozwiązania ćwiczeń

7

Terminologia związana z diagramami UML oraz przypadkami użycia jest dosyć podobna, lecz nie taka sama jak terminologia programistyczna, którą na pewno doskonale znasz. Poniżej podaliśmy kilka terminów związanych z analizą i projektowaniem obiektowym oraz ich definicje… niestety wszystko się nam pomieszało. Połącz terminy z odpowiednimi definicjami i uporządkuj cały ten bałagan.

7RWHUPLQMÚ]\ND80/NWöU\]D]Z\F]DMUHSUH]HQWXMHPHWRGÚ]GHILQLRZDQÈZMHGQHM]NODV

liczebność

/LVWDZV]\VWNLFKNRQVWUXNFMLZUD]]LFKDWU\EXWDPLLRSHUDFMDPL

analiza rzeczowników

atrybut

3RPDJDZRNUHĂOHQLXNDQG\GDWöZQDPHWRG\RELHNWöZXĝ\ZDQ\FKZV\VWHPLH :JUDILF]Q\VSRVöESRND]XMHLĝMHGQDNODVDMHVWZSHZLHQVSRVöESRZLÈ]DQD]LQQÈNODVÈ EDUG]RF]ÚVWR]DSRĂUHGQLFWZHPDWU\EXWX

diagram klas operacja

2GSRZLHGQLN]PLHQQHMVNïDGRZHMNODV\

asocjacja

2SLVXMHMDNZLHOHGDQ\FKSHZQHJRW\SXPRĝQD]DSLVDÊZDWU\EXFLHNODV\ -HVWRQDZ\NRQ\ZDQDQDSU]\SDGNXXĝ\FLDZFHOXRNUHĂOHQLDNODVMDNLHSRZLQQ\VLÚ ]QDOHěÊZV\VWHPLH

analiza czasowników

Analiza

220

Rozdział 4.

 F]ÚĂÊ 'REU\SURMHNW HODVW\F]QHRSURJUDPRZDQLH

Nic nie pozostaje wiecznie takie samo

Malinko, mam nadzieję, że nigdy nie będziemy musieli dorosnąć. Na zawsze zostańmy takimi, jakimi jesteśmy teraz!

Zmiany są nieuniknione. Niezależnie od tego, jak bardzo podoba Ci się Twoje oprogramowanie w jego obecnej postaci, to najprawdopodobniej jutro zostanie ono zmodyfikowane. A im bardziej utrudnisz wprowadzanie modyfikacji w aplikacji, tym trudniej będzie Ci w przyszłości reagować na zmiany potrzeb klienta. W tym rozdziale mamy zamiar odwiedzić naszego starego znajomego oraz spróbować poprawić projekt istniejącego oprogramowania. Na tym przykładzie przekonamy się, jak niewielkie zmiany mogą doprowadzić do poważnych problemów. Prawdę mówiąc, jak się okaże, odkryte przez nas kłopoty będą tak poważne, że ich rozwiązanie będzie wymagało rozdziału składającego się aż z DWÓCH części!

to jest nowy rozdział  221

Nie ograniczajmy się do gitar

Instrumenty Strunowe

Firma Gitary Ryśka rozwija się Uzyskawszy płynność finansową po sprzedaniu trzech gitar zespołowi muzycznemu Augustana, firma Ryśka miewa się obecnie lepiej niż kiedykolwiek wcześniej, a podstawą obecnej prosperity jest aplikacja wyszukująca gitary, którą napisałeś w rozdziale 1.

Twoje oprogramowanie jest najlepsze — sprzedaję gitary na prawo i lewo. Mam jednak dużo klientów z Nashville, dlatego zastanawiam się nad tym, czy nie zacząć sprzedawać także i mandolin. Sądzę, że na tym będę mógł zrobić jeszcze lepszy interes.

Mandoliny przypominają gitary… zatem przystosowanie aplikacji do ich sprzedawania nie powinno być takie trudne, prawda?

Wypróbujmy naszą aplikację i jej projekt Zgodnie z tym, co już stwierdziliśmy, dobra analiza i projekt są kluczowymi czynnikami pozwalającymi na tworzenie oprogramowania, które będzie można łatwo rozszerzać i wielokrotnie stosować… A teraz wygląda na to, że będziemy musieli to naocznie udowodnić Ryśkowi. Przekonajmy się, jak trudne będzie zmodyfikowanie struktury jego aplikacji i przystosowanie jej do handlowania mandolinami.

222

Rozdział 5. (część 1.)

Dobry projekt = elastyczne oprogramowanie

Zaostrz ołówek Rozbuduj aplikację Ryśka o obsługę mandolin Poniżej przedstawiliśmy kompletny diagram klas aplikacji do wyszukiwania gitar, przygotowanej dla Ryśka; jest to ta sama, końcowa postać diagramu, stworzonego przez nas w rozdziale 1. Do Ciebie należy rozbudowanie tego diagramu w taki sposób, by Rysiek mógł zacząć sprzedawanie mandolin, a Twoja aplikacja powinna dopasowywać mandoliny do potrzeb i oczekiwań klientów — tak samo jak obecnie wyszukuje gitary.

Inventory e atkow u dod dla m a r g e o dia rystyczn nałeś śmy d kte oz Dodali nty chara L, które p e M m ele amów U ozdziale. diagr rzednim r w pop

Z prostokątów reprezentujących klasy usunęliśmy większość właściwości, które zastąpiliśmy asocjacjami.

addGuitar(String, double, GuitarSpec) getGuitar(String): Guitar search(GuitarSpec): Guitar [*]

*

inventory

spec

Guitar

GuitarSpec 1

serialNumber: String price: double getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): GuitarSpec

model: String numStrings: int getBuilder(): Builder getModel(): String getType(): Type getBackWood(): Wood getTopWood(): Wood getNumStrings(): int

builder 1

type 1

Builder toString(): String

Type toString(): String

Zwróć uwagę na to, blackWood że nazwy właściwości

1

topWood 1

Wood toString(): String

możemy umieścić przy dowolnym końcu linii reprezentującej asocjację… Nie ma jakiegoś jednego „właściwego” miejsca, w którym należałoby je zapisywać, a zatem możesz umieścić je tam, gdzie Ci to bardziej odpowiada.

jesteś tutaj  223

PODPOWIEDŹ: Czy klasy Guitar i Mandoline mają ze sobą coś wspólnego? Jeśli tak, to może przydałoby się zastosować troszkę dziedziczenia?

Aktualizacja diagramu klas aplikacji Ryśka

Zaostrz ołówek

Rozwiązanie częściowe

Ponieważ klasy Guitar oraz Mandoline mają tak wiele wspólnych właściwości, zatem stworzyliśmy nową, abstrakcyjną klasę bazową, w której umieściliśmy wszystkie te właściwości.

Rozbuduj aplikację Ryśka o obsługę mandolin Poniżej przedstawiliśmy kompletny diagram klas aplikacji do wyszukiwania gitar, przygotowanej dla Ryśka; jest to ta sama, końcowa postać diagramu, stworzonego przez nas w rozdziale 1. Do Ciebie należy rozbudowanie tego diagramu w taki sposób, by Rysiek mógł zacząć sprzedawanie mandolin, a Twoja aplikacja powinna dopasowywać mandoliny do potrzeb i oczekiwań klientów — tak samo jak obecnie wyszukuje gitary.

Instrument serialNumber: String price: double getSerialNumber(): String getPrice(): double setPrice(float)

Wygląda na to, że na diagramie znowu pojawiły się nowe elementy notacji UML… Przyjrzymy się im dokładniej na stronie 230.

Inventory addGuitar(String, double, GuitarSpec) getGuitar(String): Guitar search(GuitarSpec): Guitar [*]

*

inventory

spec

Mandolin

Guitar

getSpec(): MandolinSpec Stwórzmy nową klasę będzie — Mandolin — która liny. reprezentować mando y ym Już niebawem stworz pec, linS ndo Ma sę kla że tak e zawierającą wszystki . właściwości mandoliny

GuitarSpec 1

serialNumber: String price: double getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): GuitarSpec

Niemal wszystkie elementy klasy Guitar zostały przeniesione do klasy Instrument i są od niej dziedziczone. A zatem możemy się pozbyć większości z umieszczonych tu właściwości i metod.

model: String numStrings: int getBuilder(): Builder getModel(): String getType(): Type getBackWood(): Wood getTopWood(): Wood getNumStrings(): int

builder 1

type 1

Builder toString(): String

Type toString(): String

blackWood

1

topWood 1

Wood toString(): String

224

Rozdział 5. (część 1.)

Dobry projekt = elastyczne oprogramowanie

Czy zwróciłeś uwagę na abstrakcyjną klasę bazową? Bardzo dokładnie przyjrzyj się naszej nowej klasie ,QVWUXPHQW:

Instrument serialNumber: String price: double getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

Wszystkie atrybuty pólne i operacje, które są ws dla klas Guitar oraz y Mandolin, umieściliśm w klasie Instrument.

,QVWUXPHQW jest klasą abstrakcyjną — oznacza to, że nie można stworzyć instancji tej klasy. Konieczne jest zatem stworzenie klas pochodnych klasy ,QVWUXPHQW; w naszej aplikacji są to klasy *XLWDU oraz 0DQGROLQ.

Instrument serialNumber: String price: double getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

Guitar getSpec(): GuitarSpec

Klasy abstrakcyjne są „wzorcami” do tworzenia klas implementacji. Klasa abstrakcyjna definiuje zachowanie, natomiast jej klasy pochodne implementują to zachowanie.

Instrument jest dla klas Mandoli klasą bazową n To właśnie na nie i Guitar… klasy opierają sw j obie te oje działanie.

Mandolin getSpec(): MandolinSpec

Klasy Guitar implementu oraz Mandolin w klasie In ją operacje zdefiniow charakteryststrument, w sposób ane gitar oraz yczny odpowiednio dl mandolin. a

Klasę ,QVWUXPHQW zdefiniowaliśmy jako abstrakcyjną, gdyż ,QVWUXPHQW jest jedynie ogólnym wzorcem dla rzeczywistych instrumentów takich jak gitary i mandoliny, reprezentowanych odpowiednio przez klasy *XLWDU oraz 0DQGROLQ. Klasy abstrakcyjne definiują pewne podstawowe zachowania, jednak faktyczna implementacja tych zachowań jest określana dopiero w klasach pochodnych. ,QVWUXPHQW jest jedynie klasą ogólną, stanowiącą podstawę utworzenia klas implementujących niezbędne działania.

jesteś tutaj  225

Dodawanie klasy MandolinSpec

Będziemy także potrzebowali klasy MandolinSpec Mandoliny i gitary są do siebie podobne, jednak różnią się kilkoma szczegółami; wszystkie czynniki odróżniające mandoliny od gitar zawrzemy w klasie 0DQGROLQ6SHF:

GuitarSpec builder: Builder model: String type: Type backWood: Wood topWood: Wood numStrings: int getBuilder(): Builder getModel(): String getType(): Type getBackWood(): Wood getTopWood(): Wood getNumStrings(): int matches(GuitarSpec): boolean

MandolinSpec builder: Builder model: String type: Type Style: Style backWood: Wood topWood: Wood numStrings: int getBuilder(): Builder getModel(): String getType(): Type getStyle(): Style getBackWood(): Wood getTopWood(): Wood getNumStrings(): int matches(MandolinSpec): boolean

Każda mandolina może należeć do jednego ze stylów, na przykład są mandoliny stylu „A” lub stylu „F”.

olin Większość mand posiada cztery pary strun (czyli w sumie mają 8 strun), zatem w ich przypadku właściwość ę określająca liczb strun nie będzie konieczna.

Typu wyliczeniowego użyliśmy do stworzenia typu Wood oraz Builder, podobnie możemy go zastosować do określenia stylów mandolin.

Style toString(): String

nie wiesz Nic się nie stało, jeśli jeszcze nie jeśli lub ach dolin man o go wszystkie ści, zrozumiałeś wszystkich właściwo Spec; jakie dodaliśmy do klasy Mandolin ł, że najważniejsze jest to, byś zrozumia zebowali najprawdopodobniej będziemy potr dolinę oraz man cych tują ezen repr klas ch nowy osujesz jej specyfikacji. Jeśli jednak zast rakcyjną, abst ę klas lub ent rum interfejs Inst to tym lepiej!

Obie klasy specyfikacji są do siebie bardzo podobne. Czy także i w ich przypadku nie można by zastosować abstrakcyjnej klasy bazowej?

WYSIL

SZARE KOMÓRKI Co sądzisz o tym projekcie klas? Czy aplikacja będzie działać tak, jak chciał klient? Czy jest ona elastyczna? Czy sądzisz, że oprogramowanie napisane w taki sposób będzie można w łatwy sposób rozwijać i pielęgnować?

226

Rozdział 5. (część 1.)

Dobry projekt = elastyczne oprogramowanie Nie istnieją

głupie pytania

P

: Klasę ,QVWUXPHQW zdefiniowaliśmy jako abstrakcyjną, gdyż umieszczone w niej właściwości i metody zostały wyodrębnione z klas *XLWDU oraz 0DQGROLQ, prawda?

O: Nie. Zdefiniowaliśmy ją jako klasę abstrakcyjną, gdyż

aktualnie w systemie Ryśka nie występują żadne „instrumenty”. Jedynym celem, do jakiego służy ta klasa, jest stworzenie jednego miejsca, w którym można umieścić właściwości występujące jednocześnie w klasach *XLWDU oraz 0DQGROLQ. Jednak obecnie klasa ,QVWUXPHQW nie dysponuje żadnymi zachowaniami, które można by zdefiniować poza jej klasami pochodnymi. Tak naprawdę definiuje ona jedynie wspólne atrybuty i właściwości, które muszą być zaimplementowane we wszystkich instrumentach.

P: Czy takiego samego rozwiązania nie moglibyśmy

zastosować w klasach *XLWDU6SHF oraz 0DQGROLQ6SHF? Wygląda na to, że także i one mają wiele wspólnych atrybutów i operacji, podobnie jak klasy *XLWDU i 0DQGROLQ.

O: Bardzo dobry pomysł! Możemy zatem stworzyć

kolejną abstrakcyjną klasę bazową, na przykład o nazwie ,QVWUXPHQW6SHF, od której dziedziczyłyby klasy *XLWDU6SHF oraz 0DQGROLQ6SHF.

InstrumentSpec

Niemniej jednak, choć wyodrębniliśmy właściwości wspólne dla obu typów instrumentów, nie oznacza to wcale, że klasa ,QVWUXPHQW musi być klasą abstrakcyjną. Nic nie stoi na przeszkodzie, by klasa ,QVWUXPHQW była zwyczajną klasą, oczywiście o ile tylko projekt tworzonej aplikacji uzasadni takie rozwiązanie…

model: String getBuilder(): Builder getModel(): String getType(): Type getBackWood(): Wood getTopWood(): Wood matches(InstrumentSpec): boolean

GuitarSpec

MandolinSpec

numStrings: int

numStrings: int

getNumStrings(): int matches(GuitarSpec): boolean

getStyle(): Style matches(MandolineSpec): boolean

Poskładajmy to wszystko w jedną całość…

jesteś tutaj  227

Wyodrębnianie wspólnych zachowań

Spójrz: nowa aplikacja Ryśka Wygląda na to, że cała praca nad utworzeniem poprawnego projektu aplikacji, którą wykonaliśmy w rozdziale 1., teraz się opłaciła. Dodanie obsługi mandolin do aplikacji Ryśka zajęło nam niecałe 10 stron. Poniżej zamieściliśmy kompletny diagram klas nowej wersji aplikacji: Zmieniliśmy metodę addGuitar() na addInstrument().

Inventory addInstrument(String, double, InstrumentSpec) get(String): Instrument search(GuitarSpec): Guitar [*] search(MandolinSpec): Mandolin [*]

inventory

Instrument jest klasą abstrakcyjną… W języku UML informuje o tym nazwa klasy napisana kursywą.

Teraz będą nam potrzebne dwie metody search(): jedna do wyszukiwania gitar, a druga — do wyszukiwania mandolin.

*

Instrument serialNumber: String price: double getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

Guitar

228

Teraz metoda get() zwraca obiekt typu Instrument, a nie Guitar.

Rozdział 5. (część 1.)

To jest specjalny typ asocjacji, który przedstawimy dokładniej na nast ępnej stronie.

Klasy Guitar oraz Mandolin ograniczają się do dziedziczenia od klasy Instrument. Różnice pomiędzy obydwoma typami instrumentów zostały wydzielone z tych klas i umieszczone w klasach specyfikacji.

Mandolin

Dobry projekt = elastyczne oprogramowanie

Zawsze gdy w dwóch lub kilku miejscach projektu znajdziesz wspólne zachowania, poszukaj możliwości wyodrębnienia ich i umieszczenia w osobnej klasie; następnie zastosuj tę klasę i definiowane w nich zachowania. , że które sprawiła Oto zasada, dwie abstrakcyjne utworzyliśmy — Instrument klasy bazowe tSpec. en oraz Instrum

Klasa Instrument połączona ze ws Spec jest zy typami wyliczenio stkimi których wcześniej wymi, używaliśmy bezp w klasie GuitarSośrednio pec.

sa bazowa Teraz abstrakcyjna kla ona asocjacją ącz poł t jes Instrument InstrumentSpec. z abstrakcyjną klasą

builder 1

spec

InstrumentSpec

1

getBuilder(): Builder getModel(): String getType(): Type getBackWood(): Wood getTopWood(): Wood matches(InstrumentSpec): boolean

GuitarSpec

toString(): String

type

model: String Wszystkie właściwości występujące w specyfikacjach obu klas zostały przeniesione do innej abstrakcyjnej klasy bazowej.

Builder

1 topwood 1

Type toString(): String

Wood

backwood 1

toString(): String owy Style to typ wyliczeni klasie w e zni łąc wy ny wa uży t on MandolinSpec, gdyż jes znie łąc wy ny ycz yst charakter dla mandolin, a nie dla w. wszystkich instrumentó

MandolinSpec

numStrings: int getNumStrings(): int matches(GuitarSpec): boolean

getStyle(): Style matches(MandolinSpec): boolean

style 1

Style Będziemy musieli prz esłonić metodę matches() w klasach GuitarSpec oraz MandolinSpec.

toString(): String jesteś tutaj  229

Kolejna dawka UML-a

Diagramy klas bez tajemnic (ponownie) Teraz, po dodaniu do projektu klas abstrakcyjnych, klas pochodnych oraz kilku rodzajów asocjacji, nadszedł czas, by zaktualizować nasz diagram klas oraz umiejętności z nim związane. acja acza agregację. Agreg Ta linia z rombem ozn cjacji i oznacza, że aso jest specjalną formą (częściowo) z innego. jeden obiekt składa się ściowo składa się czę nt me A zatem Instru trumentSpec. z zawartości klasy Ins

Jeśli nazwa klasy jest zapisana kursywą, oznacza to, że jest to klasa abstrakcyjna. W tym przypadku nie chcemy, by ktokolwiek mógł tworzyć instancje klasy Instrument. Utworzyliśmy ją jedynie po to, by stanowiła wspólną bazę dla klas reprezentujących konkretne instrumenty: Guitar oraz Mandolin.

Instrument

InstrumentSpec

serialNumber: String price: double

model: String

getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

Guitar Mandolin

230

Rozdział 5. (część 1.)

I znowu kursyw a: także klasa InstrumentSpec klasą abstrakcyjnjest ą.

getBuilder(): Builder getModel(): String getType(): Type getBackWood(): Wood getTopWood(): Wood matches(InstrumentSpec): boolean

Trójkątna (w odróżnieniu od zwykłej) strzałka oznacza generalizację. Generalizacja służy do pokazania, że jedna klasa (taka jak Mandolin) dziedziczy zachowanie od klasy bardziej ogólnej (takiej jak Instrument).

Dobry projekt = elastyczne oprogramowanie

abyś Zagnij róg tej strony, z później, kiedy zapomnis UML, notacji i symboli języka źć. mógł szybko je odnale

¥FLÈJDZND]80/D Jak to nazywamy w Javie

Jak to nazywamy w UML-u

Klasa abstrakcyjna

Klasa abstrakcyjna

Związek

Asocjacja

Dziedziczenie

Generalizacja

Agregacja

Agregacja

Nie istnieją

głupie pytania

P: Czy jest jeszcze dużo więcej typów symboli i notacji używanych w języku UML, których będę się musiał nauczyć?

Jak to przedstawiamy w UML-u Nazwa klasy zapisana kursywą

O: Faktycznie w UML-u używanych jest wiele symboli i notacji,

jednak to wyłącznie od Ciebie zależy, ilu z nich będziesz używać oraz ile zapamiętasz. Wiele osób używa jedynie podstawowych elementów języka UML, które już poznałeś, i całkowicie im to do szczęścia wystarcza (podobnie jak ich klientom i szefom). Są jednak także osoby, które naprawdę lubią bawić się UML-em i stosują wszystkie udostępniane przez niego sztuczki. Naprawdę wszystko zależy od Ciebie: o ile tylko jesteś w stanie przedstawić swój projekt, to możesz używać UML-a w taki sposób, w jaki to zostało przewidziane.

jesteś tutaj  231

Klasy abstrakcyjne i instrumenty

Zacznijmy tworzyć kod nowego programu wyszukiwawczego Ryśka Możemy zacząć od utworzenia nowej klasy ,QVWUXPHQW oraz zdefiniowania jej jako klasy abstrakcyjnej. Następnie umieścimy w niej wszystkie właściwości wspólne dla obu rodzajów instrumentów — gitar i mandolin:

SXEOLFDEVWUDFWFODVV,QVWUXPHQW^  SULYDWH6WULQJVHULDO1XPEHU SULYDWHGRXEOHSULFH SULYDWH,QVWUXPHQW6SHFVSHF

kcyjny… Instrument jest abstracje klas Możesz tworzyć instan ich jak tak pochodnych tej klasy Guitar.

SXEOLF,QVWUXPHQW 6WULQJVHULDO1XPEHUGRXEOHSULFH ,QVWUXPHQW6SHFVSHF ^ WKLVVHULDO1XPEHU VHULDO1XPEHU Przeważająca większość tego kodu WKLVSULFH SULFH jest całkiem prosta i WKLVVSHF VSHF   stopniu prz  ypomina wcw znacznym ześniejszą wersję klasy Guitar ` PHWRG\RGF]\WXMÈFHL]DSLVXMÈFHQXPHUVHU\MQ\LFHQÚ

Zastosowaliśmy formę asocjacji nazywaną agregacją, gdyż każdy instrument składa się ze zmiennych serialNumber, price oraz obiektu InstrumentSpec.

Instrument serialNumber: String price: double getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec FODVVLQ VWUXPHQW ^ JHW 3ULFH

SXEOLF,QVWUXPHQW6SHFJHW6SHF ^ UHWXUQVSHF ` `

Instrument.java

.ROHMQHPRG\ILNDFMHPXVLP\ZSURZDG]LüZNODVLH*XLWDUMDYDLVWZRU]\üNODVĊUHSUH]HQWXMąFą PDQGROLQ\2ELHWHNODV\G]LHG]LF]ąRGNODV\,QVWUXPHQWG]LĊNLNWyUHMX]\VNXMąZáDĞFLZRĞFL ZVSyOQHGODZV]\VWNLFKLQVWUXPHQWyZ2SUyF]WHJRPXVLP\]GHILQLRZDüNRQVWUXNWRU\W\FKNODV L]DGEDüRWRE\E\á\GRQLFKSU]HND]\ZDQHRGSRZLHGQLHW\S\NODVVSHF\ILNDFML

Każda klasa reprezentująca instrument musi dziedziczyć od klasy Instrument i definiować konstruktor, pobierając argument będący obiektem specyfikacji odpowiedniego typu.

SXEOLFFODVV0DQGROLQH[WHQGV,QVWUXPHQW^  SXEOLFFODVV*XLWDUH[WHQGV,QVWUXPHQW^ SXEOLF0DQGROLQ 6WULQJVHULDO1XPEHUGRXEOHSULFH FODVV  0DQGROLQ6SHF0DQGROLQH6SHFVSHF ^ 0DQGROLQ ^ SXEOLF*XLWDU 6WULQJVHULDO1XPEHUGRXEOHSULFH VXSHU VHULDO1XPEHUSULFHVSHF  Mandolin 0DQGR OLQ ` *XLWDU6SHFVSHF  ` ^VXSHU VHULDO1XPEHUSULFHVSHF  ` Mandolin.java ` FODVV ` Klasa Mandolin jest niemal taka

Guitar

*XLWDU^ *XL WDU `

Guitar.java

232

Rozdział 5. (część 1.)

sama jak klasa Guitar; różni się od niej tylko tym, iż typu w konstruktorze pobiera obiekt MandolinSpec, a nie GuitarSpec.

Dobry projekt = elastyczne oprogramowanie

Utworzenie klasy abstrakcyjnej dla klas specyfikacji instrumentów Skoro zajęliśmy się już instrumentami, możemy przejść do klas definiujących specyfikacje instrumentów. Przede wszystkim musimy utworzyć nową klasę abstrakcyjną — ,QVWUXPHQW6SHF. Jest to oczywiste rozwiązanie, gdyż znaczne fragmenty specyfikacji poszczególnych instrumentów powtarzają się. Klas

spec 1

InstrumentSpec model: String getBuilder(): Builder getModel(): String getType(): Type getBackWood(): Wood getTopWood(): Wood matches(InstrumentSpec): boolean FODVVLQ VWUXPHQW 6SHF^ PDWFKHV `

InstrumentSpec.java

a InstrumentS SXEOLFDEVWUDFWFODVV,QVWUXPHQW6SHF^ jak kl pec, podobnie asa In      zdefiniowanastrument, został a a dla każdego jako abstrakcyjna, SULYDWH%XLOGHUEXLOGHU zostaną utwor typu instrumentu SULYDWH6WULQJPRGHO od niej klasy zone dziedziczące pochodne. SULYDWH7\SHW\SH SULYDWH:RRGEDFN:RRG SULYDWH:RRGWRS:RRG



SXEOLF,QVWUXPHQW6SHF %XLOGHUEXLOGHU6WULQJPRGHO7\SHW\SH :RRGEDFN:RRG:RRGWRS:RRG ^ WKLVEXLOGHU EXLOGHU Ten konstruktor jest bardzo WKLVPRGHO PRGHO podobny do naszego starego konstruktora klasy Guitar… WKLVW\SH W\SH WKLVEDFN:RRG EDFN:RRG y WKLVWRS:RRG WRS:RRG   …z tą różnicą, iż  usunęliśmści, e właściwo tki zys ws go nie z ` pują we wszystkich

które nie wystę jak numStrings instrumentach, takie bądź style.

ZV]\VWNLHPHWRG\GR]DSLVXLRGF]\WXZïDĂFLZRĂFL EXLOGHUPRGHOW\SHLWDNGDOHM SXEOLFERROHDQPDWFKHV ,QVWUXPHQW6SHFRWKHU6SHF ^ LI EXLOGHU RWKHU6SHFEXLOGHU UHWXUQIDOVH LI PRGHO QXOO   PRGHOHTXDOV Ĵĵ   PRGHOHTXDOV RWKHU6SHFPRGHO Ta wersja metody UHWXUQIDOVH matches() robi do LI W\SH RWKHU6SHFW\SH to, czego można kładnie oczekiwać: porównuje właściw UHWXUQIDOVH ości w tej klasie LI EDFN:RRG RWKHU6SHFEDFN:RRG     z wł aściwościami UHWXUQIDOVH dostępnymi w inn obiekcie reprezen ym LI WRS:RRG RWKHU6SHFWRS:RRG jakąś specyfikacjętującym UHWXUQIDOVH tę będziemy jedna . Metodę UHWXUQWUXH przesłonić w kla k musieli sach ` pochodnych… `

jesteś tutaj  233

Dokończenie klas specyfikacji

Zajmijmy się kodem klasy GuitarSpec… Po zakończeniu prac nad kodem klasy ,QVWUXPHQW6SHF zmodyfikowanie klasy *XLWDU6SHF nie powinno Ci przysporzyć najmniejszego problemu.

c rozszerza klasę Klasa GuitarSpe podobnie jak klasa , ec Sp . Instrument klasę Instrument Guitar rozszerza

SXEOLFFODVV*XLWDU6SHFH[WHQGV,QVWUXPHQW6SHF^ 















 tym że gitara mus   Z i zawierać właściwość num

Strings; nie jest ona dostępna w klasie bazowej Instrument.

SULYDWHLQWQXP6WULQJV

SXEOLF*XLWDU6SHF %XLOGHUEXLOGHU6WULQJPRGHO7\SHW\SH LQWQXP6WULQJV:RRGEDFN:RRG:RRGWRS:RRG ^ VXSHU EXLOGHUPRGHOW\SHEDFN:RRGWRS:RRG  WKLVQXP6WULQJV QXP6WULQJV `











SXEOLFLQWJHW1XP6WULQJV ^ UHWXUQQXP6WULQJV

Ten konstruktor do informacji  przechowywanych w klasie bazowej InstrumentSpec — dodaje właściwo — ści charakterystyczne dla gitar.

` 3U]HVïRQLÚFLHPHWRG\PDWFKHV ]NODV\ED]RZHM SXEOLFERROHDQPDWFKHV ,QVWUXPHQW6SHFRWKHU6SHF ^ LI VXSHUPDWFKHV RWKHU6SHF 





UHWXUQIDOVH LI  RWKHU6SHFLQVWDQFHRI*XLWDU6SHF

metody o tej matches() używa Ta wersja metodyefiniowanej w klasie bazowej, samej nazwie, zd nuje dodatkowe porównania, a następnie wykoczy przekazana specyfikacja  upewnić się, zić wartości by go typu, i sprawd jest odpowiednie akterystycznych dla gitar. właściwości char

InstrumentSpec

UHWXUQIDOVH *XLWDU6SHFVSHF  *XLWDU6SHF RWKHU6SHF

model: String

LI QXP6WULQJV VSHFQXP6WULQJV 

getBuilder(): Builder getModel(): String getType(): Type getBackWood(): Wood getTopWood(): Wood matches(InstrumentSpec): boolean

UHWXUQIDOVH UHWXUQWUXH ` `

GuitarSpec numStrings: int getNumStrings(): int matches(GuitarSpec): boolean

234

Rozdział 5. (część 1.)

FODVV *XLWDU^ 6SHF^ PDWFK HV

GuitarSpec.java

Dobry projekt = elastyczne oprogramowanie

…oraz MandolinSpec Po zmodyfikowaniu klasy *XLWDU6SHF stworzenie nowej klasy 0DQGROLQ6SHF będzie bardzo proste. Obie klasy są bowiem bardzo podobne, z tym że w klasie 0DQGROLQ6SHF musimy dodać jedną zmienną składową służącą do przechowywania referencji do stylu mandoliny (np.: „A” lub „F”) i nieco zmodyfikować metodę PDWFKHV : SXEOLFFODVV0DQGROLQ6SHFH[WHQGV,QVWUXPHQW6SHF^  SULYDWH6W\OHVW\OH    

właściwość style, Tylko mandoliny mają nie przenieśliśmy odu pow o teg z właśnie ntSpec. jej do klasy Instrume

SXEOLF0DQGROLQ6SHF %XLOGHUEXLOGHU6WULQJPRGHO7\SHW\SH 6W\OHVW\OH:RRGEDFN:RRG:RRGWRS:RRG ^ VXSHU EXLOGHUPRGHOW\SHEDFN:RRGWRS:RRG  WKLVVW\OH VW\OH ` SXEOLF6W\OHJHW6W\OH ^ UHWXUQVW\OH ` 3U]HVïRQLÚFLHPHWRG\PDWFKHV ]NODV\ED]RZHM SXEOLFERROHDQPDWFKHV ,QVWUXPHQW6SHFRWKHU6SHF ^ LI VXSHUPDWFKHV RWKHU6SHF UHWXUQIDOVH LI  RWKHU6SHFLQVWDQFHRI0DQGROLQ6SHF   UHWXUQIDOVH 0DQGROLQ6SHFVSHF  0DQGROLQ6SHF RWKHU6SHF LI VW\OHHTXDOV VSHFVW\OH UHWXUQIDOVH UHWXUQWUXH ` `



Klasa MandolinS pe i GuitarSpec, ko c, podobnie jak bazowej w celu rzysta z metody klasy wy porównania spec konania podstawowego yfikacji  potem rzutuje przekaza , a dopiero ny obiekt do typu MandolinSpec, by charakterystyczn wykonać porównania e tylko dla mand olin.

SXEOLFHQXP6W\OH^ $) `

MandolinSpec getStyle(): Style getNumStrings(): int matches(MandolinSpec): boolean FODVV *XLWDU^ 6SHF^ PDWFK HV

style 1

Style toString(): String

Będziesz potrzebował nowego typu wyliczeniowego — Style. Zdefiniuj w nim dwie wartości: A oraz F.

HQXP 6W\OH^ WR 6WULQJ `

Style.java

MandolinSpec.java

jesteś tutaj  235

Praca nad kodem aplikacji Ryśka

Kończenie prac nad aplikacją wyszukiwawczą Ryśka Jedyne, co nam jeszcze pozostało, to zaktualizowanie klasy ,QYHQWRU\ i przystosowanie jej do obsługi wielu różnych typów instrumentów, a nie jedynie gitar: SXEOLFFODVV,QYHQWRU\^

Teraz na liście inventory będą w, zapisywane obiekty różnych typó a nie jedynie gitary.

Inventory inventory: Instrument [*] addInstrument(String, double, InstrumentSpec) get(String): Instrument search(GuitarSpec): Guitar [*] FODVV ,QYHQ search(MandolinSpec): Mandolin [*]

 Dzięki zastosowaniu klas Instrument oraz InstrumentSpec możem addGuitar() i nadać jej y zmodyfikować metodę charakter, pozwalający bardziej ogólny na tworzenie dowolnych instrumentów. 

WRU\^ VHDUFK `

SULYDWH/LVWLQYHQWRU\   SXEOLF,QYHQWRU\ ^ Inventory.java LQYHQWRU\ QHZ/LQNHG/LVW  `      SXEOLFYRLGDGG,QVWUXPHQW 6WULQJVHULDO1XPEHUGRXEOHSULFH ,QVWUXPHQW6SHFVSHF ^ ,QVWUXPHQWLQVWUXPHQW QXOO LI VSHFLQVWDQFHRI*XLWDU6SHF ^ LQVWUXPHQW QHZ*XLWDU VHULDO1XPEHUSULFH *XLWDU6SHF VSHF  `HOVHLI VSHFLQVWDQFHRI0DQGROLQ6SHF ^ LQVWUXPHQW QHZ0DQGROLQ VHULDO1XPEHUSULFH 0DQGROLQ6SHF VSHF  `        LQYHQWRU\DGG LQVWUXPHQW  Hmm… to nie jes ` Ponieważ Instru t zbyt dobre rozwiązanie. me abstrakcyjną i nie nt jest klasą tej klasy w spos możemy tworzyć obiektów przed utworzenieób bezpośredni, zatem, będziemy musie m konkretnego obiektu, li wykonać pewn e dodatkowe czynności.

SXEOLF,QVWUXPHQWJHW 6WULQJVHULDO1XPEHU ^ IRU ,WHUDWRUL LQYHQWRU\LWHUDWRU LKDV1H[W  ^ ,QVWUXPHQWLQVWUXPHQW  ,QVWUXPHQW LQH[W  LI LQVWUXPHQWJHW6HULDO1XPEHU HTXDOV VHULDO1XPEHU ^ UHWXUQLQVWUXPHQW `      Oto kolejne  miejsce, w którym zastosowanie abstrakcyjnej klasy ` bazowej poprawia elastyczność UHWXUQQXOO naszego projektu. ` 0HWRGDVHDUFK *XLWDU6SHFVHDUFK6SHF QLHXOHJïD]PLDQLH ZSRUöZQDQLX]SRSU]HGQLÈZHUVMÈDSOLNDFML

Będziemy potrzebowali dodatkowej SXEOLF/LVWVHDUFK 0DQGROLQ6SHFVHDUFK6SHF ^  metody search() obsługującej /LVWPDWFKLQJ0DQGROLQV QHZ/LQNHG/LVW  wyszukiwanie mandolin. IRU ,WHUDWRUL LQYHQWRU\LWHUDWRU LKDV1H[W  ^ 0DQGROLQPDQGROLQ  0DQGROLQ LQH[W  LI PDQGROLQJHW6SHF PDWFKHV VHDUFK6SHF  PDWFKLQJ0DQGROLQVDGG PDQGROLQ  Teraz możesz już przetestować działanie ` poprawionej aplikacji Ryśka. Sprawdź, czy UHWXUQPDWFKLQJ0DQGROLQV będziesz w stanie samodzielnie poprawić ` kod klasy FindGuitarTester, oraz zobacz, ` jak ona działa po wprowadzeniu zmian

w projekcie aplikacji.

236

Rozdział 5. (część 1.)

Dobry projekt = elastyczne oprogramowanie Nie istnieją

głupie pytania

P: Klasy *XLWDU i 0DQGROLQ definiują

P: Jednak po zdefiniowaniu klasy

jedynie konstruktor. Czy to nie wydaje się nieco dziwaczne? Czy naprawdę potrzebujemy klas pochodnych dla każdego instrumentu tylko po to, by stworzyć w nich jedną metodę?

,QVWUXPHQW jako klasy abstrakcyjnej metoda DGG,QVWUXPHQW stała się naprawdę długa i złożona!

O: Owszem, potrzebujemy — choćby

przedstawioną na stronie 236, prawda? No cóż, owszem… po zdefiniowaniu ,QVWUXPHQW jako klasy abstrakcyjnej musimy skorzystać z dodatkowego kodu. Niemniej nie jest to wcale wygórowana cena za uzyskanie pewności, że nie będziemy mogli utworzyć obiektu typu ,QVWUXPHQW; takie obiekty nie istnieją bowiem w rzeczywistym świecie.

po to, by w każdej z nich zdefiniować odpowiedni konstruktor. Gdybyśmy tego nie zrobili, to w jaki sposób moglibyśmy odróżnić obiekt gitary od obiektu mandoliny? Nie ma innego sposobu określenia, na jakim instrumencie operujemy niż sprawdzenie typu klasy. Oprócz tego klasy te pozwalają nam na stworzenie konstruktorów, które zapewnią, że do każdego instrumentu zostanie przekazany odpowiedni obiekt specyfikacji. Dzięki temu nie możemy utworzyć obiektu *XLWDU i przekazać do niego obiektu 0DQGROLQ6SHF.

O: Chodzi Ci o metodę DGG,QVWUXPHQW

P: Czy to całe rozwiązanie nie jest jakieś dziwaczne? Chodzi mi o to, że chociaż w rzeczywistości może nie być takiej rzeczy jak „instrument”, który nie jest ani gitarą, ani mandoliną, ani niczym innym, to jednak i tak wydaje się, że z naszym projektem jest jakiś problem.

O: Cóż, może coś w tym jest. Faktycznie

wydaje się, że niektóre fragmenty naszego kodu można by uprościć lub skrócić, gdyby ,QVWUXPHQW nie był klasą abstrakcyjną. Z drugiej strony, inne fragmenty kodu na pewno by na tym straciły. Czasami oznacza to, iż musisz podjąć jakąś decyzję — zaakceptować jej skutki i zgodzić się na wiążące się z nią kompromisy. Jednak może za tym wszystkim kryje się coś więcej…

P: Dlaczego mamy dwie różne wersje metody VHDUFK ? Czy nie moglibyśmy scalić jej w jedną metodę, do której byłby przekazywany argument typu ,QVWUXPHQW6SHF?

O: Ponieważ ,QVWUXPHQW6SHF jest klasą ujące o tym, sygnały informaplikacji mogą ej To są subtelne w iązanie e naszej no że w projekci oblemy. Kiedy jakieś rozw mieć e pr ni ę ę si si ić e w aj ja yd po jrzeć mu w aplikacji w zastosowane z zdecydować się, by przy y zamiar am es m oż m to śnie sensu, adniej… i wła się nieco dokł zrobić.

abstrakcyjną, podobnie jak klasa ,QVWUXPHQW, zatem aplikacja Ryśka będzie musiała przekazywać w wywołaniu metody VHDUFK klasy ,QYHQWRU\ bądź to obiekt *XLWDU6SHF, bądź 0DQGROLQ6SHF. A ponieważ specyfikacja będzie pasować jedynie do innej specyfikacji tego samego typu, dlatego nie może się zdarzyć, by na liście odszukanych instrumentów znalazły się jednocześnie zarówno mandoliny, jak i gitary. A zatem, nawet gdybyś scalił obie wersje metody VHDUFK w jedną, to nie miałoby to żadnego wpływu na poprawę funkcjonalności kodu. Wprost przeciwnie, mogłoby się wydawać, że metoda zwraca zarówno mandoliny, jak i gitary (ponieważ zwracałaby wyniki typu ,QVWUXPHQW> @), podczas gdy w rzeczywistości taka sytuacji nigdy by się nie zdarzyła.

jesteś tutaj  237

Istotne usprawnienia

No… to w końcu zaczyna wyglądać naprawdę dobrze. Zastosowanie klas abstrakcyjnych pozwoliło nam uniknąć powielania kodu, a właściwości instrumentów zostały umieszczone i hermetyzowane w dwóch klasach specyfikacji.

Wprowadziłeś naprawdę bardzo ISTOTNE zmiany w aplikacji Ryśka Zmiany, jakie wprowadziłeś w aplikacji Ryśka, są znacznie poważniejsze niż wyposażenie jej w możliwości obsługi mandolin. Poprzez wyodrębnienie wspólnych właściwości i zachowań klas ,QVWUXPHQW oraz ,QVWUXPHQW6SHF sprawiłeś, że poszczególne klasy wchodzące w skład aplikacji Ryśka stały się bardziej niezależne. A to z kolei oznacza bardzo poważną poprawę samego projektu aplikacji.

Sama nie wiem… Wygląda na to, że cały czas mamy jakieś problemy; takie jak prawie puste klasy Guitar i Mandolin, paskudny kod tworzący instrumenty w metodzie addInstrument(). Czy mamy je po prostu zignorować?

Wspaniałego oprogramowania nie tworzy się w jeden dzień Oprócz znaczącej poprawy projektu aplikacji udało się nam także odkryć kilka występujących w niej problemów. No i dobrze… niemal zawsze wprowadzanie istotnych zmian w projekcie aplikacji będzie się wiązało z odkryciem jakichś problemów. Teraz Twoim zadaniem będzie sprawdzenie, czy tę nową wersję aplikacji dla Ryśka można jeszcze bardziej poprawić — przekształcić z oprogramowania dobrego na WSPANIAŁE.

238

Rozdział 5. (część 1.)

Dobry projekt = elastyczne oprogramowanie

Trzy kroki tworzenia wspaniałego oprogramowania (po raz kolejny) Czy aplikację wyszukiwawczą Ryśka można uznać za wspaniałe oprogramowanie? Pamiętasz o trzech krokach pozwalających na tworzenie wspaniałego oprogramowania, o których pisaliśmy we wcześniejszej części książki? Przyjrzyjmy się im jeszcze raz, by sprawdzić, na ile dobrze poradziliśmy sobie, wprowadzając ostatnie modyfikacje do aplikacji Ryśka. 1. Czy nowa wersja aplikacji robi to, co powinna?

2. Czy zastosowaliśmy dobre zasady projektowania obiektowego, takie jak hermetyzacja, unikanie powielania kodu oraz zapewnienie łatwości rozszerzania i modyfikowania kodu?

3. Jak łatwo będzie wielokrotnie stosować aplikację Ryśka? Czy zmiany wprowadzane w jednej części kodu wymuszają dokonywanie wielu zmian w innych częściach aplikacji? Czy poszczególne elementy aplikacji są ze sobą luźno powiązane?

Doskonałe oprogramowanie, i to za każdym razem? Trudno mi sobie wyobrazić, jak to mogłoby wyglądać. pytania, dz na te stronę ie w o p d o ą Koniecznie jrzyj na następn zi. za a potem j nasze odpowied i przeczyta

jesteś tutaj  239

Czy to jest wspaniałe oprogramowanie

Czy aplikację wyszukiwawczą Ryśka można uznać za wspaniałe oprogramowanie?

Rozwiązania ćwiczeń

Pamiętasz o trzech krokach pozwalających na tworzenie wspaniałego oprogramowania, o których pisaliśmy we wcześniejszej części książki? Przyjrzyjmy się im jeszcze raz, by sprawdzić, na ile dobrze poradziliśmy sobie, wprowadzając ostatnie modyfikacje do aplikacji Ryśka. 1. Czy nowa wersja aplikacji robi to, co powinna?

Oczywiście! Prawidłowo odnajduje gitary i mandoliny, choć nie może jednocześnie poszukiwać obu rodzajów instrumentów. A zatem może robi to, co powinna, nie w całości, lecz tylko w znacznej części… Lepiej zapytać Ryśka, co o tym sądzi. 2. Czy zastosowaliśmy dobre zasady projektowania obiektowego, takie jak hermetyzacja, unikanie powielania kodu oraz zapewnienie łatwości rozszerzania i modyfikowania kodu?

Tworząc klasy specyfikacji, wykorzystaliśmy hermetyzację; tworząc abstrakcyjne klasy bazowe Instrument oraz InstrumentSpec, zastosowaliśmy dziedziczenie. Niemniej jednak dodawanie nowych typów instrumentów wciąż wymaga całkiem sporego nakładu pracy… 3. Jak łatwo będzie wielokrotnie stosować aplikację Ryśka? Czy zmiany wprowadzane w jednej części kodu wymuszają dokonywanie wielu zmian w innych częściach aplikacji? Czy poszczególne elementy aplikacji są ze sobą luźno powiązane?

Zastosowanie pewnego fragmentu aplikacji Ryśka w innym programie byłoby trudne. Jej poszczególne elementy są ze sobą dosyć ściśle powiązane; a klasa IntrumentSpec jest nawet częścią klasy Instrument (przypominasz sobie, co pisaliśmy o agregacji?). Wygląda na to, że wciąż jest trochę rzeczy do zrobienia… ale mogę się założyć, że kiedy skończysz, rezultaty będą imponujące.

240

Rozdział 5. (część 1.)

Nie szkodzi, jeś li nieco inne odpowi podałeś pytania lub jeśli edzi na te Ci do głowy zupe przyszły pomysły. Najważ łnie inne jest to, żebyś soniejsze wszystko dokładn bie przemyślał i zro ie zu dlaczego udzielili miał, właśnie takich odśmy powiedzi.

Dobry projekt = elastyczne oprogramowanie Bardzo mi się podoba, jak pracujecie nad moją aplikacją! Jeśli zabawicie tu nieco dłużej, to zapewne zacznę sprzedawać gitary basowe, bandżo i dobro (wiecie, te śmieszne gitary, na których gra się, przesuwając palce po strunach). A może nawet skrzypce?

Jednym z najlepszych sposobów na sprawdzenie, czy oprogramowanie jest dobrze zaprojektowane, jest próba ZMODYFIKOWANIA go. Jeśli dokonywanie zmian w Twoim oprogramowaniu jest stosunkowo trudne, to najprawdopodobniej w jego projekcie będzie można wprowadzić jakieś poprawki i usprawnienia. Zobaczmy, na ile łatwo będzie dodać do aplikacji Ryśka obsługę kilku nowych instrumentów:

Inventory ring, double, InstrumentSpec) ment ): Guitar [*] pec): Mandolin [*] inventory

li zmienić Znowu będziemy musie do niej ać dod i ory ent Inv sę kla erech, obsługę wszystkich czt ntów. me tru ins ów typ nowych dczenie… To niezbyt miłe doświa Cztery nowe typy instrumentów oznaczają cztery nowe klasy — po jednej dla każdego typu instrumentu.

Banjo

*

Dobro Instrument

Guita

serialNumber: String price: double getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec spec

Bass Fiddle

Mando

1

BanjoSpec

GuitarSpec

InstrumentSpec

numStrings: DobroSpec int

model: String

numStrings: int

getBuilder(): Builder getModel(): String getType(): Type getBackWood(): Wood getTopWood(): Wood matches(InstrumentSpec): boolean

getNumStrings(): int matches(InstrumentSpec): boo

builder 1

Builder

type 1 bblackWood ackWo 1

Type

toString(): String i

: String

Potrzebujemy także czterech nowych obiektów specyfikacji, z których każdy będzie zawierał własny zbiór właściwości charakterystycznych dla instrumentów danego typu.

MandolinSpec

topWood

getStyle(): Style matches(InstrumentSpec): boole

1

Wood toStrinn

getNumStrings(): numStrings: intint BassSpec matches(GuitarSpec): boolean getNumStrings(): int FiddleSpec matches(GuitarSpec): boolean matches(GuitarSpec): boolean finish: String

: String

1

Style toString(): String

getFinish(): Style matches(FiddleSpec): boolean jesteś tutaj  241

Wprowadzanie zmian jest trudne

Ech… och… dodawanie nowych instrumentów nie jest łatwe! Jeśli łatwość dodawania jest czynnikiem określającym, czy nasze oprogramowanie zostało dobrze zaprojektowane, to w przypadku aplikacji Ryśka mamy poważny problem. Dodanie do aplikacji nowego typu instrumentu wymaga bowiem dodania kolejnej klasy dziedziczącej od klasy ,QVWUXPHQW:

Instrument

Banjo

serialNumber: String price: double spec: InstrumentSpec

typów z, ile nowych , Kiedy pomyślisRysiek może sprzedawać instrumentów a dodawania nowej to perspektyw ego z nich jest trochę klasy dla każd przerażająca.

getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

Następnie będziemy potrzebowali nowej klasy pochodnej klasy ,QVWUXPHQW6SHF:

BanjoSpec

InstrumentSpec model: String

numStrings: int

getBuilder(): Builder getModel(): String getType(): Type getBackWood(): Wood getTopWood(): Wood matches(InstrumentSpec): boolean

getNumStrings(): int matches(BanjoSpec): boolean

Jednak sprawy się naprawdę komplikują dopiero wtedy, gdy zaczniemy wprowadzać zmiany w klasie ,QYHQWRU\, próbując dostosować jej metody do obsługi wielu różnych typów instrumentów:

Inventory

asz sobie Czy przypominoperatory te wszystkie instrukcje instanceof i - else if e w ko un ar w w kodzie umieszczone trument()? ns dI ad y metod em każdego Wraz z dodaniinstrumentu pu nowego ty ie się sytuacja będz pogarszać.

Jak łatwo zauważyć, ten fragment kodu zaczął się powtarzać… Banjo, podobnie jak i gitary, mają właściwo numStrings; właściwo ść ść ta nie występuje jednak we wszystkich instrumentach, zatem nie możemy jej umieś cić w klasie bazowej Instrument.

Jeśli już nie pamiętasz , na czym polegał problem z metodą addInstrument(), to zajrzyj na stronę 236 .

Inventory: Instrument [*] addInstrument(String, double, InstrumentSpec) get(String): Instrument search(GuitarSpec): Guitar [*] search(MandolinSpec): Mandolin [*] search(BanjoSpec): Banjo [*]

242

Rozdział 5. (część 1.)

Wraz z dodawaniem każdego nowego typu instrumentu sytuacja z metodą search() staje się coraz bardziej irytująca. Teraz na przykład potrzebujemy nowej wersji tej metody, przystosowanej do wyszukiwania banjo.

Dobry projekt = elastyczne oprogramowanie

Co zatem możemy zrobić? Wygląda na to, że wciąż mamy jeszcze sporo roboty nad tym, by przekształcić aplikację Ryśka we wspaniałe oprogramowanie, którego modyfikowanie i rozszerzanie nie będzie przysparzać większych problemów. Bynajmniej nie oznacza to jednak, że cała praca wykonana do tej pory jest nieistotna — przeważnie projekt aplikacji trzeba poprawiać wiele razy, rozwiązując występujące w nim problemy, które wcześniej nie były widoczne. Po zastosowaniu zasad projektowania obiektowego w aplikacji Ryśka mogliśmy wskazać kilka problemów, które będziemy musieli rozwiązać, jeśli zależy nam na tym, byśmy nie stracili następnego roku na pisanie klas %DQMR i )LGGOH (i kto wie jakich jeszcze…).

-HGQDN]DQLPEĊG]LHV]GREU]HSU]\JRWRZDQ\GR UR]SRF]ĊFLDNROHMQHJRHWDSXSUDFQDGDSOLNDFMą5\ĞND SRZLQLHQHĞGRZLHG]LHüVLĊRNLONXVSUDZDFK$]DWHPEH] ]EĊGQHJR]DPLHV]DQLD]UyEP\VRELHNUyWNLRGSRF]\QHNRG SURJUDPRZDQLDLQDVWDZP\RGELRUQLNLWHOHZL]\MQHJG\Ī ZáDĞQLH]DF]\QDVLĊ«

2ELHNWRZD. DWDVWURID Najbardziej popularny quiz w Obiektowie

jesteś tutaj  243

2ELHNWRZD. DWDVWURID Najbardziej popularny quiz w Obiektowie Unikanie ryzyka

Sławni projektanci

Konstrukcje używane w kodzie

Utrzymanie i wielokrotne użycie

Nerwica oprogramowania

$100

$100

$100

$100

$100

$200

$300

$400

Dobry wieczór, witam wszystkich w programie OBIEKTOWA KATASTROFA, $200 $200 najbardziej $200 popularnym quizie w Obiektowie. Na dzisiejszy wieczór przygotowaliśmy całkiem nową, dużą grupę odpowiedzi związanych, jak zwykle, z zagadnieniami projektowania obiektowego. Mam nadzieję, że jesteście przygotowani na zadawanie prawidłowych $300 $300 $300 pytań.

$400

$400

$400

$200

$300

$400

Do dzisiejszego programu wybraliśmy kilka niezwykle interesujących kategorii tematycznych, a zatem… zaczynajmy. Pamiętajcie — ja czytam odpowiedź, a Waszym zadaniem jest zadanie odpowiedniego pytania. Życzę powodzenia!

Nie istnieją

głupie pytania

O: Być może na pierwszy rzut oka tego nie widać, ale

w rzeczywistości wciąż pracujemy nad aplikacją Ryśka. Aby jednak zapewnić jej odpowiednią elastyczność i dostatecznie duże możliwości wielokrotnego stosowania kodu, będziemy potrzebowali naprawdę zaawansowanych technik obiektowych. Dlatego też chcieliśmy dać Ci możliwość dobrego poznania i zapamiętania wszystkich niezbędnych zasad, zanim spróbujesz je wykorzystać do rozwiązania całkiem złożonego problemu.

P: Dlaczego bawimy się w jakieś quizy? Czy nie powinniśmy poprawiać aplikacji wyszukiwawczej Ryśka?

246

Obiektowa katastrofa!

O: Pytania, jakie należy dopasować do odpowiedzi występujących

w tym rozdziale, wcale nie są proste, niemiej jednak powinieneś być w stanie je podać. Nie spiesz się, ważne, abyś to Ty sam wymyślił pytania, oczywiście o ile będzie to możliwe. I pamiętaj, że wolno Ci zajrzeć na następną stronę dopiero po sformułowaniu pytania. Po podaniu pytania na kolejnej stronie znajdziesz nieco więcej informacji dotyczących odpowiedzi oraz związanych z nią zasad projektowania obiektowego. Poza tym głęboko wierzymy, że będziesz bardzo dobrym programistą, więc ufamy w Twoje możliwości.

P

: Skoro są jakieś nowe zasady projektowania obiektowego, to niby skąd mam wiedzieć, jakie pytanie zadać? Naprawdę jesteście bardzo wymagający, nieprawdaż?

2ELHNWRZD. DWDVWURID Najbardziej popularny quiz w Obiektowie Unikanie ryzyka

Sławni projektanci

Konstrukcje używane w kodzie

Utrzymanie i wielokrotne użycie

Nerwica oprogramowania

$100

$100

$100

$100

$100

$200

$200

$200

$200

$300

$300

$300

$300

$400

$400

$300

Ta konstrukcja stosowana w kodzie odgrywa podwójną rolę. Z jednej strony jest używana do definiowania $400 $400 występować $400 zachowań, które mogą w wielu typach, a z drugiej jest niezwykle ważna dla klas, które używają tych typów.

Tutaj zapisz pyta które odpowiedź nie, na przedstawiliśmy właśnie .

„Czym jest _________________?” pytania i odpowiedzi  247

„Czym jest INTERFEJS ?”

Czy taką od Właśnie ta powiedź podałeś? ki zadać do od e pytanie powiniene na stronie powiedzi przedstawioś 247. nej

Załóżmy, że dysponujesz aplikacją, w której został zdefiniowany poniższy interfejs oraz wiele klas pochodnych dziedziczących od niego to samo wspólne zachowanie: uje Interfejs Athlete defini metodę play(), którą następnie, w charakterystyczny dla siebie sposób, e implementują wszystki poniższe klasy.

Athlete

Oto w jaki sposób przedstawiamy interfejs w języku UML: podaje y my słowo i naz wę klasy, a wszystko to kursywą.

getSport(): String play()

BaseballPlayer FootballPlayer HockeyPlayer getSport(): String getSport(): String BasketballPlayer play() play() getSport(): String TennisPlayer getSport(): String play() getSport(): String play()

play()

Pisząc kod korzystający z takich klas, zawsze będziesz mieć dwie możliwości. Pierwszą z nich jest napisanie kodu, który operuje bezpośrednio na odpowiedniej klasie pochodnej, takiej jak )RRWEDOO3OD\HU; drugą — napisanie kodu operującego na interfejsie, w naszym przypadku nosi on nazwę $WKOHWH. Jeśli znajdziesz się w takiej sytuacji, zawsze powinieneś tworzyć kod używający interfejsu, a nie implementacji.

Team

Athlete

wnia Zape ność! ycz elast

getSport(): String play()

addPlayer(???)

BaseballPlayer Jakiego typu powinien być parametr?

Stawia ograniczenia!

Tworzenie kodu wykorzystującego interfejsy, a nie klasy, które je implementują, zapewnia możliwość łatwego rozszerzania oprogramowania. Tworząc kod używający interfejsu, zapewniasz mu możliwość operowania na wszystkich klasach dziedziczących od tego interfejsu — nawet na klasach, które jeszcze nie zostały zdefiniowane.

play()

Dlaczego rozwiązanie to ma tak duże znaczenie? Gdyż zapewnia Twojej aplikacji bardzo dużą elastyczność. Otóż zamiast operowania tylko i wyłącznie na jednej, ściśle określonej klasie pochodnej — takiej jak %DVHEDOO3OD\HU — uzyskujemy możliwość operowania na znacznie bardziej ogólnym typie $WKOHWH. Oznacza to, że kod aplikacji będzie w stanie operować na dowolnej klasie pochodnej interfejsu $WKOHWH, takiej jak 7HQQLV3OD\HU lub +RFNH\3OD\HU, a nawet na klasach pochodnych, które jeszcze nawet nie zostały zaprojektowane (może na klasie &ULFNHW3OD\HU?).

248

Obiektowa katastrofa!

Unikanie ryzyka

Sławni projektanci

Konstrukcje używane w kodzie

Utrzymanie i wielokrotne użycie

Nerwica oprogramowania

$100

$100

$100

$100

$100

$200

$200

$200

$200

$300

$300

$300

$300

Spośród wszystkich zasad projektowania obiektowego to właśnie ona może się poszczycić uchronieniem programistów $400 $400 liczby problemów $400 $400 od największej pojawiających się podczas utrzymania i pielęgnacji kodu. A to dzięki zgrupowaniu w jednym miejscu wszystkich zmian w zachowaniu obiektu.

$400

„Czym jest _________________?” pytania i odpowiedzi  249

„Czym jest HERMETYZACJA?” O hermetyzacji pisaliśmy już całkiem sporo, przede wszystkim w kontekście zapobiegania powielaniu kodu. Jednak hermetyzacja chroni nas nie tylko przed zwyczajnym powielaniem fragmentów kodu metodą „skopiuj-i-wklej”. Hermetyzacja pomaga nam także zabezpieczać klasy przed niepotrzebnymi zmianami. Za każdym razem kiedy w tworzonej aplikacji pojawi się jakieś zachowanie, które, jak podejrzewasz, w przyszłości może ulec modyfikacji, to zapewne będziesz chciał oddzielić je od tych fragmentów aplikacji, które według wszelkiego prawdopodobieństwa nie będą zmieniać się zbyt często. Innymi słowy, zawsze powinieneś próbować poddawać hermetyzacji te fragmenty kodu, które ulegają zmianom.

Oto przykład bardzo prostej klasy, reprezentującej artystę malarza, który może wykonywać trzy operacje: przygotować nowe sztalugi, wyczyścić pędzle i rozpocząć tworzenie nowego dzieła.

Operacje przygo to sztalug i czyszc wywania zenia pędzli zapewne nie się zmieniać zbyt będą często.

Painter prepareEasel() cleanBrushes() paint()

lowaniem? Sposób Jednak co z samym ma dzo różny… Zmieniać bar być że mo malowania używania samych może się także sposób ść malowania obrazów. bko szy pędzli… a nawet tym miejscu klasy A zatem to właśnie w zmiany. Painter mogą zachodzić

 Wygląda na to, że klasa 3DLQWHU posiada dwie metody, które są raczej „stałe”, oraz metodę SDLQW , która na pewno będzie się znacznie zmieniać w poszczególnych implementacjach. A zatem spróbujmy hermetyzować te fragmenty klasy, które mogą się zmieniać — w tym celu usuńmy implementację sposobu malowania poza klasę 3DLQWHU.

Hermetyzowaliśmy kod, który może ulegać zmianom — usunęliśmy go z klasy Painter.

Painter

PaintStyle

prepareEasel() cleanBrushes() setPaintStyle(PaintStyle)

getStyle(): String paint()

Zauważ, iż w ty korzystamy z int m miejscu z jego implementerfejsu, a nie acji.

ModernPaintStyle paint()

Teraz wszystkie odmienności zost umieszczone poza klasą Painter, ały w różnych klasach implementującyc h interfejs PaintStyle.

250

Obiektowa katastrofa!

Interfejs reprezen PaintStyle który czętuje styl malowa zmianom sto może ulega nia, ć .

SurrealPaintStyle paint()

ImpressionistPaintStyle paint()

CubistPaintStyle paint()

Unikanie ryzyka

Sławni projektanci

Konstrukcje używane w kodzie

Utrzymanie i wielokrotne użycie

Nerwica oprogramowania

$100

$100

$100

$100

$100

$200

$200

$200

$200

$300

$300

$400

$300

$300

Jest powodem katastrofy $400 $400i śmierci $400 wielu źle zaprojektowanych fragmentów oprogramowania, dlatego każda klasa powinna starać się, by mieć do niej tylko jeden powód.

„Czym jest _________________?” pytania i odpowiedzi  251

„Czym jest ZMIANA ?” Już wiesz, że jedynym pewnikiem w programowaniu są ZMIANY. Oprogramowanie, które nie jest dobrze zaprojektowane, przestaje działać nawet po wprowadzeniu nieznacznych zmian, jednak wspaniałe oprogramowanie można modyfikować bez większych problemów. Najprostszym sposobem upewnienia się, że nasze oprogramowanie będzie odporne na błędy, jest zadbanie o to, by każda klasa miała tylko jeden powód do zmiany. Innymi słowy, powinieneś dążyć do zminimalizowania prawdopodobieństwa, że dana klasa będzie musiała się zmienić, poprzez zmniejszenie liczby czynników, które mogą Cię zmusić do wprowadzenia zmian. Przyjrzyj się metodom zdefiniowanym w tej klasie. Ich działanie jest związane z ruszaniem i zatrzymywaniem się, sposobem wymiany opon, sposobem prowadzenia samochodu przez kierowcę, myciem samochodu, a nawet ze sprawdzaniem i wymianą oleju.

Automobile Jak można sobie wyobr BARDZO WIELE przyczazić, istnieje doprowadzić do koniec yn, które mogłyby klasy. Jeśli mechanik zności zmiany tej sprawdzania stanu olezmieni sposób ju albo kierowca zmieni sposób prowadze bądź też jeśli zostanie nia samochodu, lokalna myjnia samoch unowocześniona odowa, to będziemy musieli wprowadzić sto sowne zmiany w kodzie tej klasy.

start() stop() changeTires(Tire [*]) drive() wash() checkOil() getOil(): int

Jeśli przyjrzysz się jakiejś klasie i dojdziesz do wniosku, iż może istnieć więcej niż jeden powód do wprowadzenia w niej modyfikacji, to najprawdopodobniej będzie to sygnałem, że ta klasa próbuje realizować zbyt wiele zadań. W takim przypadku zastanów się, czy można podzielić funkcjonalność i rozbić klasę na kilka mniejszych, z których każda będzie realizować tylko jedno zadanie — a zatem będzie istnieć tylko jeden powód, który mógłby zmusić nas do jej modyfikacji. Każda z klas Drive CarWash wykonu r oraz operację, a zate je tylko JEDNĄ m ich zmiany będz konieczność ie się pojawiać jeszcze rzadziej.

Automobile start() stop() getOil(): int

CarWash wash(Automobile)

Driver Klasa Automobil e drive(Automobile) prostsza. Obsługu stała się ZNACZNIE ruszania, zatrzym je ona jedynie operacje wartości atrybutu ywania się oraz zwracania Teraz jest znaczn określającego stan oleju. ie bardziej odpo rna na zmiany!

252

Obiektowa katastrofa!

Mechanic checkOil(Automobile) changeTires(Automobile, Tire [*])

Gdybyś chciał, to prawdopodobnie mógłbyś rozdzielić nawet te dwie funkcje i rozdzielić funkcjonalność klasy Mechanic na bardziej szczegółowe klasy pochodne.

Do tej pory radziłeś sobie całkiem dobrze; jednak teraz nadszedł czas na OSTATECZNĄ KATASTROFĘ. Poniżej przedstawiony został diagram klas aplikacji, która, delikatnie mówiąc, nie jest zbyt elastyczna. Aby udowodnić, że naprawdę jesteś w stanie uniknąć obiektowej katastrofy, zapisz poniżej, jak zmieniłbyś projekt tej aplikacji. Będziesz musiał wykorzystać wszystkie przedstawione wcześniej zasady projektowania obiektowego, a zatem nie spiesz się… i powodzenia!

2VWDWHF]QD

. DWDVWURID DessertCounter

Dessert

orderCone(IceCream[*], Topping[*]): Cone orderSundae(IceCream[*], Topping[*], Syrup[*]): Sundae addTopping(Cone, Topping): Cone addTopping(Sundae, Topping): Sundae

serve()

Sundae iceCream: IceCream [*] syrups: Syrup [*] toppings: Topping [*] addIceCream(IceCream) addSyrup(Syrup) addTopping(Topping) serve()

Topping

Nuts

Cherries

iceCream: IceCream [*] toppings: Topping [*] addIceCream(IceCream) addTopping(Topping) serve()

IceCream

description: String

taste: String

getDescription(): String

getTaste(): String serve()

WhippedCream serve()Nuts

Cone

Syrup

Vanilla

ingredients: String [*] getIngredients:String [*] serve()

serve()

Chocolate Peppermint MinChocolateChip

HotFudge Caramel

pytania i odpowiedzi  253

2VWDWHF]QD

. DWDVWURID W klasie DessertCoun ter używane są implem interfejsu Dessert. Te entacje jedną — orderDessert dwie metody możemy zastąpić () — która będzie zw rac typu Dessert, a zatem będzie używać interfejsać obiekt u.

Odpowiedzi

DessertCounter orderCone(IceCream[*], Topping[*]): Cone orderSundae(IceCream[*], Topping[*], Syrup[*]): Sundae addTopping(Cone, Topping): Cone addTopping(Sundae, Topping): Sundae

Można wskazać więcej niż jeden powód, który mógłby nas zmusić do zmiany klasy DessertCounter: jeśli zmieni się proces zamawiania oraz jeśli zmieni się sposób, w jaki do klas Cone oraz Sundae dodawane są obiekty Topping.

Klasa Syrup jest implementacją interfejsu Topping… Tak naprawdę nie potrzebujemy metody służącej specjalnie do dodawania właśnie tego typu. Jest to bowiem przykład kodowania pod kątem implementacji.

Nie istnieją

Dessert serve()

Sundae iceCream: IceCream [*] syrups: Syrup [*] toppings: Topping [*] addIceCream(IceCream) addSyrup(Syrup) addTopping(Topping) serve()

Cone iceCream: IceCream [*] toppings: Topping [*] addIceCream(IceCream) addTopping(Topping) serve()

naprawdę SPORO W naszym przykładzie występuje inniśmy spróbować implementacji metody serve(). Powmenty kodu i umieścić hermetyzować zmieniające się frag deserów w jednym cały kod związany z serwowaniem serwowania ulegnie es miejscu. Dzięki temu, jeśli proc niać WSZYSTKICH zmianie, nie będziemy musieli zmieda serve(). meto jest klas, w których używana

głupie pytania

O: We wcześniejszej części książki już kilka razy miałeś okazję

przekonać się, że jeśli tyko zauważysz możliwość powielania kodu, to powinieneś poszukać szansy zastosowania hermetyzacji. W tym przykładzie z dużą dozą prawdopodobieństwa możemy założyć, że serwowanie obiektów 6XQGDH nie będzie się szczególnie różnić od serwowania obiektów &RQH.

A zatem mógłbyś stworzyć nową klasę, przykładowo: 'HVVHUW6HUYLFH, i w niej umieścić metodę VHUYH . Następnie wszystkie klasy wchodzące w skład aplikacji, czyli implementujące interfejsy 'HVVHUW, ,FH&UHDP oraz 7RSSLQJ, powinny korzystać z metody 'HVVHUW6HUYLFHVHUYH . Jeśli metoda VHUYLFH ulegnie zmianie, to będziesz musiał zaktualizować kod tylko w jednym miejscu aplikacji: klasie 'HVVHUW6HUYLFH.

254

Obiektowa katastrofa!

Właśnie w ten sposób udało Ci się hermetyzować fragmenty kodu, które mogą ulec zmianie — w naszym przypadku jest to kod metody VHUYH — oraz zapewnić, że dla każdej klasy będzie istnieć tylko jeden powód do wprowadzania zmian.

P: Jak się domyśliliście, że należy hermetyzować metodę serve() i usunąć ją z tych wszystkich klas? Ja jakoś to przegapiłem.

Zarówno interfejs Topping, jak i IceCreame udostępniają metodę serve() i wyglądają dosyć podobnie… Może moglibyśmy wyodrębnić z nich wspólne właściwości i przenieść je do jakiejś nowej klasy bazowej?

dków przypa szości spólnych k ię w W zi do lanie w wydzie ości prowad iw c ś wła yzacji. hermet

IceCream taste: String getTaste(): String serve()

Topping Vanilla

description: String getDescription(): String

Chocolate Peppermint

WhippedCream serve()Nuts Nuts

Cherries

MinChocolateChip

Syrup ingredients: String [*] getIngredients:String [*] serve()

Nuts

HotFudge Caramel

pytania i odpowiedzi  255

Wróćmy do aplikacji wyszukiwawczej Ryśka

Wspaniale było gościć w programie takiego zawodnika jak Ty i naprawdę bardzo byśmy chcieli zaprosić Cię w przyszłym tygodniu, jednak właśnie dostaliśmy bardzo pilną wiadomość od „Ryśka". Czy wiesz może coś o kontynuacji prac nad jego aplikacją do wyszukiwania instrumentów?

Teraz jesteś już gotów ponownie zmierzyć się z problemem nieelastycznego kodu aplikacji Ryśka Obecnie, gdy Twój arsenał wzbogacił się o nowe narzędzia i techniki projektowania obiektowego, bez wątpienia jesteś już świetnie przygotowany do tego, by ponownie zająć się aplikacją Ryśka i zapewnić jej większą elastyczność. Kiedy skończysz, okaże się, że zastosowałeś wszystko, czego dowiedziałeś się w tej części rozdziału, a modyfikacje w aplikacji Ryśka nie będą już sprawiać większych problemów.

Zasady projektowania obiektowego Poddawaj hermetyzacji to, co się zmienia. Stosuj interfejsy, a nie implementacje. Każda klasa w aplikacji powinna mieć tylko jeden powód do zmian.

Te trzy zasady są OG RO Zapamiętaj je, gdyż w MNIE ważne! rozdziałach będziemy kolejnych z nich bardzo często korzystać.

256

Obiektowa katastrofa!

 F]ÚĂÊ 'REU\SURMHNW HODVW\F]QHRSURJUDPRZDQLH

Zabierz swoje oprogramowanie na 30-minutowy trening I wymach… 2… 3… 4…

Czy kiedykolwiek marzyłeś o tym, by być nieco bardziej elastycznym w działaniu? Jeśli kiedykolwiek wpadłeś w problemy podczas prób wprowadzania zmian w aplikacji, to zazwyczaj oznacza to, że Twoje oprogramowanie powinno być nieco bardziej elastyczne i odporne. Aby pomóc swojej aplikacji, będziesz musiał przeprowadzić odpowiednią analizę, zastanowić się nad niezbędnymi zmianami w projekcie i dowiedzieć się, w jaki sposób rozluźnić zależności pomiędzy jej elementami. I w końcu, w wielkim finale, przekonasz się, że większa spójność może pomóc w rozwiązaniu problemu powiązań. Brzmi interesująco? A zatem przewróć kartkę — przystępujemy do poprawiania nieelastycznej aplikacji.

to jest nowy rozdział  257

Problemy występujące w aplikacji Ryśka

Wracamy do aplikacji wyszukiwawczej Ryśka Posiadając już wiedzę na temat nowych zasad projektowania obiektowego, możemy przystąpić do pracy nad uczynieniem aplikacji Ryśka bardziej elastyczną i dobrze zaprojektowaną. Oto miejsce, w którym poprzednio się zatrzymaliśmy, oraz problemy, na które się natknęliśmy. ) Metoda addInstrument( yczny yst zawiera kod charakter ów dla poszczególnych typ , za każdym instrumentów; a zatem ą klasę razem gdy dodamy nowbędziemy pochodną Instrument, . musieli zmienić ten kod

Inventory addInstrument(String, double, InstrumentSpec) get(String): Instrument search(GuitarSpec): Guitar [*] search(MandolinSpec): Mandolin [*] inventory

*

Instrument serialNumber: String price: double getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

Guitar

Mandolin

ch Te klasy nie definiują żadnych inny są więc , tora truk kons cz metod opró za, prawdziwym utrapieniem… Co gors rumentu dla każdego dodawanego typu inst musimy stworzyć taką klasę.

258

Rozdział 5. (część 2.)

W naszej aplikacji dla każdej klasy implementującej interfejs Instrument została zdefiniowana odrębna metoda search()… To nie jest dobre rozwiązanie.

Dobry projekt = elastyczne oprogramowanie

ądku… Te klasy wydają się być w porz ość eczn koni jest m leme prob Jedynym u zmiany kodu tej klasy w przypadk dodawania nowych instrumentów.

Builder builder

type

InstrumentSpec

Type

1

model: String getBuilder(): Builder getModel(): String getType(): Type getBackWood(): Wood getTopWood(): Wood matches(InstrumentSpec): boolean

GuitarSpec

toString(): String

1

toString(): String

topWood 1

Wood backWood

toString(): String

MandolinSpec

numStrings: int getNumStrings(): int matches(GuitarSpec): boolean

getStyl(): Style matches(MandolinSpec): boolean

Podobnie jak było w przypadku klas Instrument, także i tutaj dodanie jakiegokolwiek nowego instrumentu powoduje konieczność utworzenia nowej klasy implementującej interfejs InstrumentSpec.

style 1

Style toString(): String jesteś tutaj  259

Sprawdzanie metody search()

Panowie, przeglądałem diagram klas aplikacji dla tego Ryśka i myślę, że na pewno musi się dać utworzyć te metody search() w jakiś lepszy sposób.

)UDQHN Tak… to mi nie daje żyć, ale nie widzę żadnego sposobu rozwiązania tego problemu. Przecież aplikacja musi w jakiś sposób poszukiwać każdego z typów instrumentów. .XED Wciąż nie rozumiem, dlaczego nie możemy zdefiniować tylko jednej metody VHDUFK , do której przekazywalibyśmy argument typu ,QVWUXPHQW6SHF. Czy to nie pozwoliłoby nam stworzyć tylko jednej wersji metody VHDUFK ?

Kuba

Franek

-HU]\ Cóż, to by pomogło… jednak nawet takie rozwiązanie nie pozwoliłoby nam na jednoczesne poszukiwanie kilku różnych typów instrumentów. Jeśli klient dostarczy obiekt %DQMR6SHF, to nigdy nie będzie on pasować do obiektu *XLWDU6SHF lub 0DQGROLQ6SHF. A zatem wyniki zwrócone przez metodę VHDUFK zawsze będą zawierać wyłącznie obiekty tego samego typu, jakiego była przekazana specyfikacja. Jerzy

.XED A to dlatego, że nie możemy utworzyć obiektów typu ,QVWUXPHQW6SHF? ,QVWUXPHQW6SHF jest klasą abstrakcyjną i dlatego musimy tworzyć jej konkretne implementacje, takie jak 0DQGROLQ6SHF lub %DQMR6SHF. )UDQHN To może właśnie w tym tkwi problem… Poza tym czy nie powinniśmy raczej korzystać z interfejsów, takich jak ,QVWUXPHQW6SHF, a nie z jego implementacji, takich jak *XLWDU6SHF czy też %DQMR6SHF? -HU]\ Hm. Nie pomyślałem o tym, ale macie rację. Naprawdę powinniśmy się skoncentrować na interfejsie, a nie na tych wszystkich klasach, które go implementują.

260

Rozdział 5. (część 2.)

Dobry projekt = elastyczne oprogramowanie

Dokładniejsza analiza metody search() Chyba nie ma najmniejszych wątpliwości, że coś jest nie w porządku w sposobie wyszukiwania instrumentów w aplikacji Ryśka. Moglibyśmy zmodyfikować klasę ,QVWUXPHQW6SHF, tak by nie była abstrakcyjna, jednak czy to by rozwiązało nasze problemy?

FODVV VHDUFK *XLWDU6SHF ,QYHQ VHDUFK 0DQGROLQ6SHF WRU\^ VHDUFK VHDUFK %DQMR6SHF VHDUFK )LGGOH6SHF `

Inventory.java W przypadku zastosowania tej wersji klasy Inventory, dodanie do aplikacji Ryśka jakiegokolwiek nowego instrumentu wymaga całkiem sporo pracy…

…jednak w razie uży cia klasy, definiującej tylk tej wersji metodę search(), wprowo jedną modyfikacji jest znaczn adzanie ie prostsze.

FODVV ,QYHQ WRU\^ VHDUFK ,QVWUXPHQW6SHF VHDUFK `

Obecnie dysponujemy metodami search() dla każdego ze sprzedawanych typów instrumentów. Jednak możemy zmienić klasę InstrumentSpec tak, by nie była klasą abstrakcyjną…

Jak widać, nazwa klasy nie jest już wydrukowana kursywą, a to oznacza, że nie jest to już klasa abstrakcyjna… a pomimo to nie musimy niczego zmieniać w jej klasach pochodnych.

InstrumentSpec model: String getBuilder(): Builder getModel(): String getType(): Type getBackWood(): Wood getTopWood(): Wood matches(InstrumentSpec): boolean

Teraz w wywołaniu metody search() możemy już przekazać obiekt typu InstrumentSpec.

Inventory.java

jesteś tutaj  261

Utworzenie konkretnej klasy InstrumentSpec

Korzyści, jakie dała nam analiza

Inventory

Wykorzystajmy zatem wszystkie wnioski związane ze zmianą klasy ,QVWUXPHQW6SHF z abstrakcyjnej na konkretną i przekonajmy się, czy rezygnacja z klasy abstrakcyjnej pozwoli nam poprawić projekt klasy ,QYHQWRU\.

inventory: Instrument [*] addInstrument(String, double, InstrumentSpec) get(String): Instrument search(InstrumentSpec): Instrument [*] FODVV ,QYHQ

WRU\^ 6HDUFK `

SXEOLFFODVV,QYHQWRU\^ SULYDWH/LVWLQYHQWRU\

Oto kluczowa zmiana, którą prezentujemy na tej stronie.

SXEOLF,QYHQWRU\ ^ LQYHQWRU\ QHZ/LQNHG/LVW  `

Wciąż mamy tu niewielki problem… Ta metoda staje się coraz dłuższa i bardziej skomplikowana wraz z dodawaniem każdego nowego typu instrumentu…

SXEOLFYRLGDGG,QVWUXPHQW 6WULQJVHULDO1XPEHUGRXEOHSULFH ,QVWUXPHQW6SHFVSHF ^   ,QVWUXPHQWLQVWUXPHQW QXOO LI VSHFLQVWDQFHRI*XLWDU6SHF ^ LQVWUXPHQW QHZ*XLWDU VHULDO1XPEHUSULFH *XLWDU6SHF VSHF  `HOVHLI VSHFLQVWDQFHRI0DQGROLQ6SHF ^ LQVWUXPHQW QHZ0DQGROLQ VHULDO1XPEHUSULFH 0DQGROLQ6SHF VSHF  ` LQYHQWRU\DGG LQVWUXPHQW  …a poza tym cały czas używamy klas ` implementacji, a Instrument.

nie bazowej klasy

SXEOLF,QVWUXPHQWJHW 6WULQJVHULDO1XPEHU ^ IRU ,WHUDWRUL LQYHQWRU\LWHUDWRU LKDV1H[W  ^ ,QVWUXPHQWLQVWUXPHQW  ,QVWUXPHQW LQH[W  LI LQVWUXPHQWJHW6HULDO1XPEHU HTXDOV VHULDO1XPEHU ^ UHWXUQLQVWUXPHQW ` znacznie Metoda search() wygląda teraz ` jej a jedn nam zy tarc Wys j! lepie UHWXUQQXOO typu wersja, która pobiera argument ` InstrumentSpec.            metody search() SXEOLF/LVWVHDUFK ,QVWUXPHQW6SHFVHDUFK6SHF ^ Jak widać, w kodzie u Instrument, typ ego ow baz używamy /LVWPDWFKLQJ,QVWUXPHQWV QHZ/LQNHG/LVW  lementują, a nie klas, które go impMandolin. ź bąd r IRU ,WHUDWRUL LQYHQWRU\LWHUDWRU LKDV1H[W  ^ takich jak Guita cznie lepsze. To rozwiązanie jest zna ,QVWUXPHQWLQVWUXPHQW  ,QVWUXPHQWLQH[W  LI LQVWUXPHQWJHW6SHF PDWFKHV VHDUFK6SHF PDWFKLQJ,QVWUXPHQWVDGG LQVWUXPHQW  Oprócz tego, że metoda search() w obecnej postaci ` zapewnia lepszy projekt klas, może zwracać UHWXUQPDWFKLQJ,QVWUXPHQWV   wszystkie instrum  enty, które pasują do podanej jeśli na liście będą się nawet acji, specyfik ` znajdowały różne typy instrumentów, na przykład ` dwie gitary i jedna mandolina.

262

Rozdział 5. (część 2.)

Inventory.java

Dobry projekt = elastyczne oprogramowanie

Zaostrz ołówek

Jedna z tych rzeczy nie jest podobna do innej… a może jest?

Metoda search() nie jest jedynym czynnikiem, który sprawia, że dodawanie nowych instrumentów do aplikacji Ryśka jest trudne. Kolejnym jest konieczność dodawania nowej klasy pochodnej klasy Instrument dla każdego nowego typu instrumentu. Ale dlaczego? Spróbujmy to nieco dokładniej przeanalizować. Dlaczego w aplikacji Ryśka potrzebna jest klasa Instrument?

Jakie są wspólne cechy wszystkich instrumentów?

Jakimi elementami różnią się poszczególne elementy?

Jeśli masz jakiś pomysł, który pozwoli nam zmienić aplikację Ryśka w taki sposób, by tworzenie kolejnych klas dla kolejnych dodawanych instrumentów nie było konieczne, to zaznacz je na poniższym diagramie. W razie potrzeby możesz dowolnie usuwać i dodawać klasy oraz ich właściwości; tylko od Ciebie zależy, jak poprawisz aplikację Ryśka. Banjo Instrument

Guitar

serialNumber: String price: double spec: InstrumentSpec getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

Dobro

Mandolin Fiddle Bass

jesteś tutaj  263

Odporność na zmiany

Zaostrz ołówek

Jedna z tych rzeczy nie jest podobna do innej…

Rozwiązanie

a może jest?

Metoda search() nie jest jedynym czynnikiem, który sprawia, że dodawanie nowych instrumentów do aplikacji Ryśka jest trudne. Kolejnym jest konieczność dodawania nowej klasy pochodnej klasy Instrument dla każdego nowego typu instrumentu. Ale dlaczego? Spróbujmy to nieco dokładniej przeanalizować. Dlaczego w aplikacji Ryśka potrzebna jest klasa Instrument?

Nie musiałeś podawać dokładnie takich samych odpowiedzi, jakie tu zamieściliśmy, niemniej jednak powinieneś przynajmniej mieć te same pomysły.

Większość instrumentów ma przynajmniej kilka wspólnych właściwości, takich jak numer seryjny oraz cena. Można je przechowywać w klasie bazowej Instrument, od której następnie będą dziedziczyć klasy reprezentujące poszczególne typy instrumentów. Jakie są wspólne cechy wszystkich instrumentów?

Numer seryjny, cena oraz pewien zbiór danych określających specyfikację (chociaż poszczególne klasy specyfikacji mogą się od siebie różnić szczegółami). Jakimi elementami różnią się poszczególne elementy?

Specyfikacją — każdy z typów instrumentów posiada odmienny zbiór właściwości. A ponieważ każdy instrument ma odmienną klasę specyfikacji, zatem klasy te będą się od siebie różnić postacią konstruktora. Jeśli masz jakiś pomysł, który pozwoli nam zmienić aplikację Ryśka w taki sposób, by tworzenie kolejnych klas dla kolejnych dodawanych instrumentów nie było konieczne, to zaznacz je na poniższym diagramie. W razie potrzeby możesz dowolnie usuwać i dodawać klasy oraz ich właściwości; tylko od Ciebie zależy, jak poprawisz aplikację Ryśka. Banjo Instrument

Guitar

serialNumber: String price: double spec: InstrumentSpec getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

Dobro

Mandolin Fiddle Bass

264

Rozdział 5. (część 2.)

Czy wymyśliłeś jakikolwiek sposób poprawienia aplikacji Ryśka?

Dobry projekt = elastyczne oprogramowanie

Dokładniejsza analiza klas instrumentów Chociaż metoda VHDUFK wygląda już znacznie lepiej, to wciąż mamy poważne problemy ze wszystkimi klasami reprezentującymi instrumenty oraz z metodą DGG,QVWUXPHQW klasy ,QYHQWRU\. Pamiętasz zapewne, że początkowo zdefiniowaliśmy klasę ,QVWUXPHQW jako klasę abstrakcyjną, gdyż każdy typ instrumentu był reprezentowany przez swoją własną klasę pochodną. Klasa Instrument dba o wszystkie wspólne właściwości, występujące we wszystkich instrumentach.

Instrument serialNumber: String price: double

Poszczególne klasy ins różnią się od siebie jedtrumentów postacią konstruktora ynie .

getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

Banjo Guitar

SXEOLFFODVV0DQGROLQH[WHQGV,QVWUXPHQW^ SXEOLF0DQGROLQ 6WULQJVHULDO1XPEHUGRXEOHSULFH 0DQGROLQ6SHFVSHF ^ VXSHU VHULDO1XPEHUSULFHVSHF  FODVV SXEOLFFODVV*XLWDUH[WHQGV,QVWUXPHQW^ ` 0DQGROLQ SXEOLF0DQGROLQ 6WULQJVHULDO1XPEHUGRXEOHSULFH ^ 0DQGR *XLWDU6SHFVSHF ^ OLQ ` VXSHU VHULDO1XPEHUSULFHVSHF  FODVV Dobro Mandolin.java ` *XLWDU^ *XL WDU `

Bass Mandolin

Fiddle

Guitar.java

Ale przecież, tak naprawdę, klasy są związane z zachowaniami! Ale przecież zazwyczaj klasy pochodne tworzymy właśnie dlatego, że różnią się one zachowaniem od swej klasy bazowej. A czy w aplikacji Ryśka zachowanie klasy *XLWDU różni się od zachowania klasy ,QVWUXPHQW? Czy działa ona inaczej niż klasy 0DQGROLQ lub %DQMR?

jesteś tutaj  265

Zachowanie czy właściwości? Guitar, Mandolin oraz inne klasy instrumentów nie mają różnych zachowań. Mają jednak różne właściwości… a zatem będą nam potrzebne różne klasy pochodne dla poszczególnych typów instrumentów, prawda?

Wszystkie instrumenty — przynajmniej z punktu widzenia Ryśka — zachowują się tak samo. A zatem można podać jedynie dwa powody przemawiające za tworzeniem odrębnych klas dla każdego z typów instrumentów: 1. Klasa ,QVWUXPHQW reprezentuje ideę, a nie pewien konkretny obiekt, dlatego też faktycznie powinna być klasą abstrakcyjną. To z kolei sprawia, że klasy pochodne reprezentujące poszczególne typy instrumentów są niezbędne. To jest dobra zasada projektowania obiektowego, jednak bez wątpienia te wszystkie klasy pochodne przysparzają problemów. W dalszej części wrócimy do tego zagadnienia.

2. Każdy typ instrumentu posiada inne właściwości i używa innej klasy pochodnej klasy ,QVWUXPHQW6SHF, dlatego też każdy typ instrumentu wymaga innego konstruktora. Wydaje się, że są to całkiem uzasadnione powody (hm… a przynajmniej pierwszy z nich), jednak takie rozwiązanie sprawia, że w aplikacji Ryśka pojawia się bardzo dużo dodatkowych klas, które nie robią zbyt wiele… i sprawiają, że nasze oprogramowanie jest mało elastyczne i trudne do modyfikacji. Cóż zatem możemy z tym zrobić?

Gdybyśmy tworzyli system reprezentujący sposób grania na tych instrumentach, to zapewne potrzebowalibyśmy różnych klas pochodnych, by udostępnić wszelkie możliwe zachowania, takie jak: uderzanie, szarpanie, brzdąkanie, bębnienie…

To wygląda jak kolejny przykład, w którym używamy implementacji, a nie interfejsu. Zatem nie można powiedzieć, że jest to dobry argument przemawiający za zdefiniowaniem klasy Instrument jako abstrakcyjnej.

Czy pamiętasz drugi krok na drodze do tworzenia wspaniałego oprogramowania, który podaliśmy w rozdziale 1.: ja Ryśka Ponieważ aplikac owo (tym idł aw już działa pr ramach zajęliśmy się w 1.), m kie kro d na ac pr gotowi zatem jesteśmy ąpić st zy pr by , go do te ia do prób poprawien ego sz elastyczności na oprogramowania.

Zastosuj proste zasady projektowania obiektowego, by poprawić elastyczność oprogramowania.

266

Rozdział 5. (część 2.)

W jaki sposób możemy zast os to zalecenie, ować rozwiązać probby jakie występu lemy, w aplikacji Ryją śka?

Dobry projekt = elastyczne oprogramowanie

Zaostrz ołówek Zasady projektowania obiektowego spieszą na ratunek! Nie ma żadnych wątpliwości co do tego, że w aplikacji Ryśka jest jakiś problem, jednak nie do końca wiemy, na czym on polega. Kiedy nie wiesz, co należy zrobić, by rozwiązać problem projektowy, powinieneś przeanalizować wszystkie zasady projektowania obiektowego i sprawdzić, czy któraś z nich nie mogłaby Ci pomóc. Dla każdej z wymienionych poniżej zasad zaznacz prostokącik, jeśli uważasz, że dana zasada może nam pomóc. Następnie, jeśli zaznaczyłeś zasadę, to zapisz obok niej, w jaki sposób według Ciebie można ją zastosować do poprawienia aplikacji Ryśka.

Dziedziczenie

Polimorfizm

Wyodrębnianie

Hermetyzacja

2GSRZLHG]L]QDMG]LHV]QDVWURQLH

jesteś tutaj  267

Rozwiązanie ćwiczenia

Zaostrz ołówek

Rozwiązanie Zasady projektowania obiektowego spieszą na ratunek! Nie ma żadnych wątpliwości co do tego, że w aplikacji Ryśka jest jakiś problem, jednak nie do końca wiemy, na czym on polega. Kiedy nie wiesz, co należy zrobić, by rozwiązać problem projektowy, powinieneś przeanalizować wszystkie zasady projektowania obiektowego i sprawdzić, czy któraś z nich nie mogłaby Ci pomóc.

Dziedziczenie

268

Już korzystamy z dziedziczenia w klasach Instrument, InstrumentSpec oraz wszystkich ich klasach pochodnych. Jednak nie wydaje się, by klasy pochodne klasy Instrument działały inaczej od swojej klasy bazowej… Różnią się od niej i pomiędzy sobą jedynie postacią konstruktora.

Polimorfizm

Z polimorfizmu korzystamy w metodzie search(); dzięki niemu wszystkie instrumenty są traktowane jako instancje klasy Instrument, a my nie musimy się przejmować, czy w rzeczywistości są to obiekty Guitar czy Mandolin. Dzięki polimorfizmowi wyszukiwanie instrumentów stało się znacznie prostsze… niemniej jednak byłoby bardzo miło, gdybyśmy mogli wykorzystać go także w metodzie addInstrument() i zredukować ilość powtarzającego się kodu.

Wyodrębnianie

Klasa InstrumentSpec wyodrębnia szczegółowe informacje dotyczące specyfikacji poszczególnych instrumentów i pozwala usunąć je z klasy Instrument. Dzięki temu możemy dodawać nowe właściwości instrumentów bez konieczności modyfikowania samej klasy Instrument.

Hermetyzacja

W naszej aplikacji bardzo często korzystamy z hermetyzacji, jednak może będziemy w stanie znaleźć dla niej jeszcze jakieś zastosowania? Pamiętaj, by hermetyzować kod, który może się zmieniać! W klasach instrumentów takimi elementami, które mogą się zmieniać, są właściwości. Dlatego musimy się zastanowić, czy nie moglibyśmy w jakiś sposób ich hermetyzować — czyli całkowicie usunąć z klas Instrument oraz InstrumentSpec.

Rozdział 5. (część 2.)

Dobry projekt = elastyczne oprogramowanie Panowie, w naszym projekcie używaliśmy dziedziczenia, polimorfizmu i wyodrębniania. Jednak zaczynam sądzić, że najważniejsza w naszym przypadku jest hermetyzacja. Czy pamiętacie, czego nauczyliśmy się o oddzielaniu tego, co się zmienia, od tego, co pozostaje niezmienne? -HU]\ Hm… pewnie mówisz o hermetyzowaniu tego, co jest zmienne, prawda? )UDQHN Dokładnie! A doskonale wiemy, że w naszej aplikacji tymi zmiennymi elementami są właściwości poszczególnych instrumentów. .XED A ja myślałem, że ten temat mamy już za sobą. Przecież właśnie z tego powodu mamy te wszystkie klasy pochodne klasy Instrument, takie jak Guitar czy Mandoline — żebyśmy za ich pomocą mogli przedstawić różnice pomiędzy poszczególnymi instrumentami. )UDQHN No ale, jak widać, te klasy niewiele nam pomogły… a poza tym zachowanie poszczególnych typów instrumentów jest zawsze takie samo, a zatem czy naprawdę potrzebujemy tych wszystkich klas? -HU]\ Czyli sugerujesz, że powinniśmy zrezygnować z abstrakcyjnej klasy Instrument? I pozbyć się tych wszystkich klas pochodnych reprezentujących poszczególne typy instrumentów? .XED Ale… jestem całkowicie zaskoczony. A co z właściwościami, które przecież są różne w różnych instrumentach? )UDQHN No, co z nimi? Klasa Instrument przechowuje referencję do obiektu InstrumentSpec, zatem wszystkie różnice pomiędzy instrumentami można obsługiwać przy wykorzystaniu klas specyfikacji. Spójrz: już Nazwa klasy Instrument nie jest cza, ozna to a , ywą kurs a wan ruko wyd yjna. że nie jest to już klasa abstrakc

Także klasa InstrumentSpec nie jest już klasą abstrakcyjną.

Instrument

InstrumentSpec

serialNumber: String price: double getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

spec 1

Tak naprawdę nie ma używać klas pochodnycżadnego powodu, by poszczególne typy ins h reprezentujących tru te klasy jedynie niepot mentów! Wszystkie rzebnie zwiększały złożoność aplikacji.

builder: Builder model: String type: Type backWood: Wood topWood: Wood getBuilder(): Builder getModel(): Model getType(): Type getBackWood(): Wood getTopWood(): Wood matches(InstrumentSpec): boolean

W rzeczywistości już hermetyzowaliśmy właściwości i oddzieliliśmy je od pozostałych fragmentów aplikacji! Jednak nie wykorzystywaliśmy naszego dobrego projektu.

GuitarSpec numStrings: int getNumStrings(): int matches(GuitarSpec): boolean

MandolinSpec getStyle(): Style matches(MandolinSpec): boolean

jesteś tutaj  269

Pozwólmy umrzeć złemu projektowi

Śmierć projektu (decyzja) Najtrudniejsza rzecz, jakiej będziesz musiał stawić czoła, to pozwolić odejść błędom popełnionym podczas samodzielnego tworzenia projektu oprogramowania. W przypadku aplikacji Ryśka nie ma większego sensu korzystanie z odrębnych klas pochodnych klasy ,QVWUXPHQW, których do tej pory używaliśmy do reprezentowania poszczególnych typów instrumentów. Niemniej jednak dojście do tego wniosku zajęło nam niemal 30 stron (i dwie części rozdziału 5.). Dlaczego?

Ponieważ wcześniej takie rozwiązanie wydawało się mieć sens, a bardzo TRUDNO jest zmieniać coś, co wydaje się działać prawidłowo!

Koduj raz, analizuj dwa razy Kiedy napotykasz jakieś problemy, przyglądaj się i analizuj projekt. Przyczyną obecnych problemów mogą być decyzje, które podjąłeś wcześniej.

Klasy pochodne instrumentu %ÚG]LHP\]D:DPLWÚVNQLÊ QRPRĝHQLHDĝWDNEDUG]R

Stosunkowo łatwo jest przeglądać i analizować kod napisany przez kogoś innego, jednak musisz nauczyć się analizować własny kod i odnajdywać w nim błędy. W takich sytuacjach wprost nieoceniona może być pomoc jakiegoś zaprzyjaźnionego programisty, który poświęci czas na przeanalizowanie naszego kodu. Nie przejmuj się, jeśli będziesz musiał wprowadzać zmiany; w przyszłości lepszy projekt aplikacji zapewni Ci ogromne oszczędności czasu i wysiłku.

Projektowanie jest procesem cyklicznym… i musisz wykazywać chęć poprawiania swoich projektów, a nie tylko tych, które zostały stworzone przez innych.

Duma jest wrogiem dobrych projektów

8ZDJD

270

Nigdy nie powinieneś bać się analizowania swoich własnych decyzji projektowych i poprawiania ich, nawet jeśli to oznacza zmianę wcześniej podjętych decyzji.

Rozdział 5. (część 2.)

zję błędną i złą decy Porzućmy naszą prowadziła do utworzenia ra projektową, któ reprezentujących różne klas pochodnych . Zapomnijmy o niej ów typy instrument szną drogę pisania i powróćmy na słu gramowania. wspaniałego opro

Dobry projekt = elastyczne oprogramowanie

Zmieńmy złe decyzje projektowe na dobre Właśnie usun sześć klas i ęliśmy z projektu od już potrzebow tej pory nie będziemy klas reprezen ali żadnych nowych instrumentów tujących kolejne typy chciał sprzed , które Rysiek będzie awać!

A zatem pozbądźmy się tych wszystkich klas pochodnych dziedziczących od klasy Instrument: Banjo Klasa Instrument nie jest już klasą . abstrakcyjną

Instrument

G iit

serialNumber: String price: double spec: InstrumentSpec

Dobro

getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

Mandolin Fiddle Bass

Jednak w każdym instrumencie będziemy potrzebowali nowej właściwości, która pozwoli nam określić, jakiego typu jest dany instrument:

Instrument serialNumber: String price: double spec: InstrumentSpec getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

instrumentType 1

instrumentType toString(): String

W tym typie możemy umieścić takie wartości jak: GUITAR, BANJO, MANDOLIN, i tak dalej. To jest naprawdę znacznie lepsze rozwiązanie niż grupa klas pochodnych.

iowy, To może być kolejny typ wyliczen der. Buil lub d Woo do bny podo A zatem obecnie dodanie nowego do typu instrumentu sprowadza się typu dodania nowej wartości do tego wyliczeniowego.

To jest ogromne usprawnienie projektu. Jednak wciąż dla każdego nowego typu instrumentu należy dodawać nową klasę specyfikacji, a to dalej jest nieelastyczne.

jesteś tutaj  271

Hermetyzuj to, co się zmienia

Kolejna ostra wymiana poglądów (i niewielka pomoc ze strony Julki) Nie cierpię się wtrącać, ale zastanawiałam się nad tym, o czym wspominałeś wcześniej, Jurku — by hermetyzować to, co się zmienia.

-HU]\ Ale przecież właśnie to zrobiliśmy… zmieniliśmy klasę ,QVWUXPHQW z abstrakcyjnej na konkretną i pozbyliśmy się wszystkich jej klas pochodnych. -XOND Ale ja uważam, że to dopiero początek — pierwszy krok. Powiedzcie, co tak naprawdę jest zmienne w aplikacji Ryśka?

Jakie elementy aplikacji Ryśka są zmienne? ____________________________________ ____________________________________ ____________________________________

W tych pustych liniach zapisz, co według Ciebie jest zmienne w aplikacji Ryśka.

)UDQHN Już to przerabialiśmy: w przypadku aplikacji Ryśka takimi zmiennymi elementami są właściwości instrumentów. -XOND A zatem czy nie możemy ich w jakiś sposób hermetyzować? -HU]\ Ale przecież już to zrobiliśmy: w tym celu stworzyliśmy klasę ,QVWUXPHQW6SHF. Julka uważnie , obserwowała wszystko co działo się w tym rozdziale, i ma pewne pomysły dotyczące sposobów poprawienia aplikacji Ryśka.

)UDQHN Zaczekaj, Jurek. Stworzyliśmy klasę ,QVWUXPHQW6SHF, gdyż te właściwości były używane zarówno przez klientów, jak i przez obiekty instrumentów. A zatem przyjęte przez nas rozwiązanie miało raczej na celu uniknięcie powielania kodu… -XOND Właśnie… o to mi chodzi. Ale właściwości umieszczone w klasie ,QVWUXPHQW6SHF także mogą się zmieniać. Może zatem potrzebna jest nam kolejna warstwa hermetyzacji? -HU]\ Hm… czyli ze względu na możliwość zmieniania się właściwości w klasie ,QVWUXPHQW6SHF powinniśmy je wyodrębnić i usunąć z niej? To jakby hermetyzacja w hermetyzacji… albo jakoś podobnie. -XOND Fakt… coś w tym stylu. Wyodrębniamy specyfikacje używane przez klientów przy wyszukiwaniu instrumentów oraz w samych instrumentach i usuwamy je z klasy ,QVWUXPHQW; następnie hermetyzujemy właściwości umieszczone do tej pory w klasie ,QVWUXPHQW6SHF, gdyż także one mogą się zmieniać.

272

Rozdział 5. (część 2.)

Dobry projekt = elastyczne oprogramowanie

Zastosowanie „podwójnej hermetyzacji” w aplikacji Ryśka Przyjrzyjmy się warstwie hermetyzacji, jaką już mamy, a następnie przekonajmy się, w jaki sposób możemy ponownie zastosować hermetyzację, by usunąć z klasy ,QVWUXPHQW6SHF te jej elementy, które mogą się zmieniać.

Instrument serialNumber: String price: double spec: InstrumentSpec getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

yzacja” nie „podwójna hermet Tak naprawdę ta logii analizy i projektowania należy do termino m nie dziw się, jeśli Twój obiektowego, zatenie się na Ciebie popatrzy, wykładowca dziw terminu. jeśli użyjesz tego

InstrumentSpec builder: Builder model: String spec type: Type 1 backWood: Wood topWood: Wood getBuilder(): Builder getModel(): Model getType(): Type getBackWood(): Wood getTopWood(): Wood matches(InstrumentSpec): boolean

Ponieważ niektóre spośród tych właściwości mogą się zmieniać, chcemy usunąć je z klasy ,QVWUXPHQW6SHF. Musimy dysponować jakimś sposobem odwoływania się do właściwości oraz ich wartości, a jednocześnie uniknąć podawania samych właściwości na stałe w klasie ,QVWUXPHQW6SHF. Czy masz jakieś pomysły, jak by to można zrobić?

Jak sądzisz, jaki typ (lub typy) pozwoliłyby nam reprezentować te właściwości oraz ich wartości, a jednocześnie uchroniły od konieczności modyfikowania klasy InstrumentSpec w przypadku dodawania do aplikacji Ryśka nowych typów instrumentów?

W rozdziale 1. zdaliśm z tego, iż zarówno kliey sobie sprawę i instrumenty muszą nci Ryśka, jak używać tych właściwości. Dlatego też stworzyliśmy klasę InstrumentSpec , te właściwości i usunąćby wyodrębnić je z klasy Instrument.

na tym, że Jednak problem polega w różnych ne róż są te ści wo właści o powodu, instrumentach i z teg mentu, tru ins u typ dla każdego pochodną musimy tworzyć klasę . klasy InstrumentSpec

Poprzez hermetyzację elementów zmiennych poprawiasz elastyczność swoich aplikacji oraz sprawiasz, że będą łatwiejsze do modyfikacji. jesteś tutaj  273

Proste rozwiązania są najfajniejsze

Dynamizujemy właściwości instrumentów Jaką odpowiedź podałeś na pytanie zadane na poprzedniej stronie, dotyczące sposobu przechowywania właściwości? My uważamy, że zastosowanie klasy 0DS będzie wspaniałym rozwiązaniem, pozwalającym na obsługę właściwości różnych typów, a jednocześnie zapewniającym możliwość dodawania nowych właściwości w dowolnej chwili.

InstrumentSpec properties: Map builder: Builder model: String type: Type backWood: Wood topWood: Wood getProperty(String): Object getProperties(): Map getBuilder(): Builder getModel(): Model getType(): Type getBackWood(): Wood getTopWood(): Wood matches(InstrumentSpec): boolean

Teraz używamy tylko wej jednej zmiennej składo typu Map, w której możemy przechowywać. wszystkie właściwości yć Możemy się pozb wszystkich tych metod, właściwości (oraz ują) er które na nich op używać go kie st zy ws do i py naszej nowej ma właściwości. Do pobierania właściwości wartości m metody getPro ożemy używać Odczyta ona perty()… z mapy warto właściwości ść o nazwie. Nazw podanej przekazywali ę tę będziemy metody w formw wywołaniu ie łańcucha znaków.

Ta właściwość będzie nam potrzebna, by określić typ instrumentu, z jakim mamy do czynienia.

Właściwości instrumentType

instrumentType. GUITAR

builder

Builder.MARTIN

model

„OM-18”

type backWood

Wood.MAHOGANY

topWood

Wood.SITKA

numStrings Ale to nie wszystko! Możemy raz na zawsze pozbyć się tych wszystkich klas pochodnych dziedziczących od InstrumentSpec!

GuitarSpec numStrings: int getNumStrings(): int matches(GuitarSpec): boolean

MandolinSpec getStyle(): Style matches(MandolinSpec): boolean

274

Rozdział 5. (część 2.)

Jedynym powodem, dla którego stworzyliśmy klasy pochodne klasy InstrumentSpec, była konieczność obsługi różnych zbiorów właściwości, które posiadały instrumenty różnych typów. Teraz wszystkie właściwości, które wcześniej były zdefiniowane w klasac h pochodnych klasy InstrumentSpec, możem przenieść do naszej ma y py.

Type.ACOUSTIC

style

6 Style.F

Dobry projekt = elastyczne oprogramowanie

Co zrobiliśmy — dokładniejsza analiza Za każdym razem gdy w aplikacji zauważymy elementy, które mogą się zmieniać, powinniśmy poszukać możliwości zastosowania hermetyzacji. W przypadku klasy ,QVWUXPHQW6SHF zdaliśmy sobie sprawę, że takimi zmiennymi elementami są właściwości instrumentów. informacje Z klasy Instrument usunęliśmy ściliśmy umie i entu rum inst o specyfikacji pec; je w osobnej klasie InstrumentS Ryśka zrobiliśmy tak, ponieważ klienci w, określają specyfikację instrumentó metodzie która jest następnie używana w ch(). sear

Wszystkie właściwości, które zmieniają się w różnych instrumentach i typach instrumentów, umieściliśmy w klasie InstrumentSpec.

:\RGUÚEQLP\ ]PLHQQHHOHPHQW\

Teraz wszystkie właściwości są zapisywane w obiekcie Map w postaci par nazwa-wartość.

model

„OM-18” .ODVD,QVWUXPHQW6SHF

1D]Z\ ZïDĂFLZRĂFL

:DUWRĂFLZïDĂFLZRĂFL

:ïDĂFLZRĂFLLQVWUXPHQWX

Kiedy zauważysz, że w używanych obiektach występuje pewien zbiór zmieniających się właściwości, zastosuj jakąś kolekcję, na przykład klasy 0DS, by zapisywać je w sposób dynamiczny. Dzięki temu usuniesz ze swoich klas bardzo dużo metod i unikniesz konieczności modyfikowania kodu w przypadku dodawania nowych właściwości. jesteś tutaj  275

Zastosowanie klas Instrument i InstrumentSpec

Zastosowanie nowych wersji klas Instrument oraz InstrumentSpec Przyjrzyjmy się po raz ostatni, w jaki sposób możemy w praktyce korzystać z naszych nowych klas ,QVWUXPHQW oraz ,QVWUXPHQW6SHF. Poniżej pokazaliśmy, jak aktualnie wygląda projekt aplikacji Ryśka:

Instrument

Wartości tych typów wyliczeniowy zapisywane w mapie przechowującch są ej właściwości instrumentów

InstrumentSpec

serialNumber: String price: double

spec 1

getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

InstrumentType Builder toString(): String Type toString(): String

properties: Map getProperty(String): Object getProperties(): Map matches(InstrumentSpec): boolean

Wood toString(): String Style toString(): String toString(): String

Ani klasa Instrument, ani InstrumentSpec nie są już klasami abstrakcyjnymi.

Załóżmy, że operując na obiekcie JLWDU\ chciałbyś sprawdzić, kto ją wyprodukował. Poniżej pokazaliśmy, jak byś mógł odczytać tę informację:

instrument

spec

Obecnie nie korzystamy już z klas pochodnych klasy Instrument, a zatem gitara jest reprezentowana przez obiekt klasy Instrument.

instrumentSpec

właściwości

Obiekt Instrument zaw obiekt klasy Instrume iera ntS w którym są przechow pec, właściwości konkretne ywane go instrumentu.

builder 1D]Z\ ZïDĂFLZRĂFL

Builder. MARTIN :DUWRĂFLZïDĂFLZRĂFL

:ïDĂFLZRĂFLLQVWUXPHQWX A obiekt InstrumentSpec zawiera sane obiekt typu Map, w którym zapi tujące są pary nazwa-wartość reprezen właściwości instrumentu.

LQVWUXPHQW

JHW6SHF

JHW3URSHUW\ ķEXLOGHUĵ

LQVWUXPHQWJHW6SHF JHW3URSHUW\ ĵEXLOGHUĵ To wyrażenie zwraca producenta gitary.

276

Rozdział 5. (część 2.)

Dobry projekt = elastyczne oprogramowanie

Magnesiki z kodem =DVWRVRZDQLHRELHNWXW\SX0DSGRSU]HFKRZ\ZDQLDZâDĤFLZRĤFLZ\GDMHVLĐGREU\P UR]ZLċ]DQLHPSU]HNRQDMP\VLĐMHGQDNMDNZSUDNW\FHEĐG]LHZ\JOċGDþQDV]NRG SRQDSLVDQLXQRZHMZHUVMLNODV\,QVWUXPHQW6SHF7ZRLP]DGDQLHPMHVWX]XSHâQLHQLH SRQLİV]HJRIUDJPHQWXNRGXSU]\Xİ\FLXPDJQHVLNyZSU]HGVWDZLRQ\FKXGRâXVWURQ\ LPSRUWMDYDXWLOBBBBBBBBBBB LPSRUWMDYDXWLOBBBBBBBBBBB LPSRUWMDYDXWLOBBBBBBBBBBB SXEOLFFODVV,QVWUXPHQW6SHF^ SULYDWHBBBBBBBSURSHUWLHV SXEOLF,QVWUXPHQW6SHF BBBBBBBBBBBBBBBBBBB ^ LI SURSHUWLHV BBBBBBB ^ WKLVSURSHUWLHV QHZBBBBBBBBB  `HOVH^ WKLVSURSHUWLHV QHZBBBBBBBB BBBBBBBBBBB  ` ` SXEOLF2EMHFWBBBBBBBBBB 6WULQJBBBBBBBBBBB ^ UHWXUQSURSHUWLHVJHW BBBBBBBBBBBB  ` SXEOLFBBBBBBBJHW3URSHUWLHV ^ UHWXUQBBBBBBBBBB ` SXEOLFERROHDQPDWFKHV BBBBBBBBBBBBRWKHU6SHF ^ IRU BBBBBBBBBL RWKHU6SHFBBBBBBBBBBB NH\6HW BBBBBBBB  LBBBBBBBBB  ^ 6WULQJBBBBBBBBB  6WULQJ LBBBBBBBB  LI SURSHUWLHVJHW BBBBBBBBB HTXDOV RWKHU6SHFJHW3URSHUW\ BBBBBBBBBBBB ^ UHWXUQBBBBBBBBB ` UHWXUQBBBBBBBBBB ` `

JHW3URSHUWLHV 0DS SURSHUW\1 ,QVWUXPHQW6SHF +DVK1H[W DPH WUXH +DVK0DS QXOO 0DS SURSHUWLHV SURSHUW\1DPH SURSHUW\1DPH /LVW WUXH SURSHUWLHV IDOVH IDOVH ,WHUDWRU 0DS ,WHUDWRU SURSHUWLHV 2EMHFW QXOO +DVK0DS QH[WSURSHUW\1DPH +DV K0DS JHW3URSHUWLHV ,QVWUXPHQW6SHF SURSHUW\1DPH

,WHUDWRU

0DS

jesteś tutaj  277

Nowa klasa InstrumentSpec

Magnesiki z kodem. Rozwiązanie =DVWRVRZDQLHRELHNWXW\SX0DSGRSU]HFKRZ\ZDQLDZâDĤFLZRĤFLZ\GDMHVLĐGREU\P UR]ZLċ]DQLHPSU]HNRQDMP\VLĐMHGQDNMDNZSUDNW\FHEĐG]LHZ\JOċGDþQDV]NRG SRQDSLVDQLXQRZHMZHUVMLNODV\,QVWUXPHQW6SHF7ZRLP]DGDQLHPMHVWX]XSHâQLHQLH SRQLİV]HJRIUDJPHQWXNRGXSU]\Xİ\FLXPDJQHVLNyZSU]HGVWDZLRQ\FKXGRâXVWURQ\

,WHUDWRU LPSRUWMDYDXWLO,WHUDWRU +DVK0DS LPSRUWMDYDXWLO+DVK0DS LPSRUWMDYDXWLO0DS 0DS SXEOLFFODVV,QVWUXPHQW6SHF^ SULYDWH0DSSURSHUWLHV 0DS

0DS SURSHUWLHV Prawdę rzekłszy, w tym miejscu SXEOLF,QVWUXPHQW6SHF 0DSSURSHUWLHV ^ mógłbyś użyć dowolnej klasy LI SURSHUWLHV QXOO ^ QXOO implementującej interfejs Map. +DVK0DS WKLVSURSHUWLHV QHZ+DVK0DS      `HOVH^ +DVK0DS WKLVSURSHUWLHV QHZ+DVK0DS SURSHUWLHV  SURSHUWLHV ` `

SURSHUW\1DPH SXEOLF2EMHFWJHW3URSHUW\ 6WULQJSURSHUW\1DPH ^ 2EMHFW UHWXUQSURSHUWLHVJHW SURSHUW\1DPH  SURSHUW\1DPH ` SXEOLF0DSJHW3URSHUWLHV ^ 0DS UHWXUQSURSHUWLHV SURSHUWLHV `

,QVWUXPHQW6SHF SXEOLFERROHDQPDWFKHV ,QVWUXPHQW6SHFRWKHU6SHF ^ RU DW HU ,W ,WHUDWRU JHW3URSHUWLHV IRU ,WHUDWRUL RWKHU6SHFJHW3URSHUWLHV NH\6HW LWHUDWRU  LKDV1H[W  ^ +DVK1H[W QH[W 6WULQJSURSHUW\1DPH SURSHUW\1DPH  6WULQJ LQH[W  LI SURSHUWLHVJHW SURSHUW\1DPH HTXDOV SURSHUW\1DPH RWKHU6SHFJHW3URSHUW\ SURSHUW\1DPH ^ SURSHUW\1DPH UHWXUQIDOVH IDOVH ` Nie pomyl wartości zwracanych w tych JHW3URSHUWLHV ` dwóch wierszach, gdyż w przeciwnym IDOVH razie metoda matches() zawsze będzie UHWXUQWUXH WUXH zwracała błędny wynik. QXOO WUXH ` ,QVWUXPHQW6SHF /LVW `

278

Rozdział 5. (część 2.)

Dobry projekt = elastyczne oprogramowanie Nie istnieją

głupie pytania

P: A zatem teraz zarówno

,QVWUXPHQW, jak i ,QVWUXPHQ6SHF są klasami konkretnymi, a nie abstrakcyjnymi?

O: Tak. Klasa ,QVWUXPHQW nie odgrywa

już roli idei; reprezentuje konkretne instrumenty znajdujące się w magazynie sklepu Ryśka. Natomiast obiekty klasy ,QVWUXPHQW6SHF są używane przez klientów do określania i przekazywania specyfikacji poszukiwanych instrumentów oraz w klasie ,QVWUXPHQW, do przechowywania specyfikacji konkretnego instrumentu.

P

: A zatem mogę się pozbyć klas *XLWDU i 0DQGROLQ?

O: A i owszem. Podobnie jak klas %DQMR,

P: Rozumiem, dlaczego pozbyliśmy

się klas *XLWDU i 0DQGROLQ, jednak nie do końca pojmuję, dlaczego nie są nam potrzebne różne klasy pochodne klasy ,QVWUXPHQW6SHF.

O: Nie przejmuj się. To jeden z najtrud-

niejszych aspektów projektu aplikacji Ryśka. Pamiętaj, że jedną z najważniejszych zasad projektowania obiektowego jest hermetyzacja tych elementów, które mogą się zmieniać. W przypadku aplikacji Ryśka tymi zmiennymi elementami są właściwości poszczególnych typów instrumentów. Właśnie dlatego wyodrębniliśmy je z klasy ,QVWUXPHQW6SHF i zapisaliśmy w obiekcie typu 0DS. Teraz, jeśli zdecydujesz się dodać do aplikacji nowy instrument posiadający nowe właściwości, wystarczy, że zapiszesz je w formie par nazwa-wartość we właściwości SURSHUWLHV obiektu 0DS.

'REUR oraz wszystkich pozostałych klas pochodnych dziedziczących od klasy ,QVWUXPHQW, które wcześniej tak pracowicie tworzyłeś.

P: A dzięki zmniejszeniu liczby klas

P: A to wszystko dlatego, że

O: W tym przypadku — tak. Można

obecnie używamy bezpośrednio klasy ,QVWUXPHQW, tak?

O: I znowu masz rację! Pamiętaj, że

najczęściej klasy pochodne są tworzone ze względu na różnice w zachowaniu. W przypadku klas pochodnych klasy ,QVWUXPHQW ich zachowanie się jednak nie zmieniało; jedyne, czym te klasy różniły się od siebie, była postać wywołania konstruktora. A zatem cała ta masa klas jedynie ograniczała elastyczność aplikacji, a nie zapewniała nam żadnej przydatnej funkcjonalności.

poprawiła się elastyczność naszej aplikacji?

sobie jednak wyobrazić sytuację, kiedy to właśnie dodawanie klas poprawi elastyczność tworzonego oprogramowania. Pamiętasz przecież, że dodanie klasy ,QVWUXPHQW6SHF ułatwiło nam odseparowanie instrumentów od ich właściwości, zatem było to dobre rozwiązanie. Jednak faktycznie w tym rozdziale usuwaliśmy zbędne klasy, i to w efekcie ułatwiło dodawanie nowych instrumentów do aplikacji Ryśka.

P: Nigdy bym nie wymyślił, że nie potrzebujemy klas pochodnych reprezentujących instrumenty lub ich specyfikacje. Zastanawiam się, jak w ogóle mogę to zrozumieć i stać się w tym dobrym?

O: Najlepszym sposobem nauczenia

się projektowania oprogramowania jest jego pisanie! W przypadku aplikacji Ryśka musieliśmy odrzucić przyjęte wcześniej błędne koncepcje — takie jak dodanie do projektu klasy *XLWDU lub 0DQGROLQ — by domyślić się, jakie jest właściwe rozwiązanie problemu. Większość dobrych projektów powstaje na drodze modyfikacji złych projektów; niemal nikomu nie udaje się stworzyć dobrego projektu już za pierwszym razem. A zatem po prostu rób to, co wydaje się sensowne, a następnie wdrażaj zasady projektowania obiektowego i wzorce projektowe, by przekonać się, czy możesz poprawić utworzony projekt.

Większość dobrych projektów powstaje poprzez analizę i modyfikację złych projektów. Nigdy nie obawiaj się popełniać błędów, a następnie modyfikować zaproponowanych rozwiązań. jesteś tutaj  279

Aktualizacja aplikacji Ryśka

Końcowe prace nad aplikacją Ryśka — typ wyliczeniowy InstrumentType Już niemal udało nam się stworzyć wspaniałą aplikację. Przyjrzyjmy się naszym nowym pomysłom projektowym, rozpoczynając od nowego typu wyliczeniowego określającego poszczególne typy instrumentów:

Jak na razie to są ws zystkie typy instrumentów, jakie spr zedaje Rysiek.

SXEOLFHQXP,QVWUXPHQW7\SH^

InstrumentType

*8,7$5%$1-2'2%52),''/(%$660$1'2/,1 SXEOLF6WULQJWR6WULQJ ^ VZLWFK WKLV ^ FDVH*8,7$5UHWXUQĵ*LWDUDĵ FDVH%$1-2UHWXUQĵ%DQMRĵ FDVH'2%52UHWXUQĵ'REURĵ FDVH),''/(UHWXUQĵ6NU]\SFHĵ FDVH%$66UHWXUQĵ*LWDUDEDVRZDĵ FDVH0$1'2/,1UHWXUQĵ0DQGROLQDĵ GHIDXOWUHWXUQĵ1LHRNUHĂORQ\ĵ ` ` `

toString(): String HQXP,Q VWUXPQW 7\SH^ WR 6WULQJ InstrumentType.java

Zaktualizujmy także klasę Inventory

Metoda toString() jedynie ułatwia wyświetlanie informacji o typie instrumentu.

Inventory inventory: Instrument [*]

']LĊNL]PLDQRPMDNLHZSURZDG]LOLĞP\ZNODVDFK,QVWUXPHQWRUD] ,QVWUXPHQW6SHFPRĪHP\]QDF]QLHXSURĞFLüNRGNODV\,QYHQWRU\

addInstrument(String, double, InstrumentSpec) get(String): Instrument search(InstrumentSpec): Instrument [*]

SXEOLFFODVV,QYHQWRU\^ SXEOLFYRLGDGG,QVWUXPHQW 6WULQJVHULDO1XPEHUGRXEOHSULFH ,QVWUXPHQW6SHFVSHF ^ ,QVWUXPHQWLQVWUXPHQW QXOO LI VSHFLQVWDQFHRI*XLWDU6SHF ^ LQVWUXPHQW QHZ*XLWDU VHULDO1XPEHUSULFH *XLWDU6SHF VSHF  `HOVHLI VSHFLQVWDQFHRI0DQGROLQ6SHF ^ LQVWUXPHQW QHZ0DQGROLQ VHULDO1XPEHUSULFH 0DQGROLQ6SHF VSHF  ` ,QVWUXPHQWLQVWUXPHQW QHZ,QVWUXPHQW VHULDO1XPEHUSULFHVSHF  LQYHQWRU\DGG LQVWUXPHQW  Teraz możemy już tworzyć ` obiekty klasy Instrument, gdyż SR]RVWDï\NRGNODV\ nie jest to klasa abstrakcyjna. `

280

Rozdział 5. (część 2.)

Dodawanie instrumentów ie stało się znaczn prostsze.

Dobry projekt = elastyczne oprogramowanie

Zaostrz ołówek

Zobaczmy, co tak naprawdę udało Ci się zrobić Wprowadziliśmy naprawdę sporo zmian w aplikacji przygotowywanej dla sklepu Ryśka, a wszystko to w imię „poprawienia elastyczności”. Zobaczmy, jak się mają obecnie sprawy. Zajrzyj na stronę 258 i zobacz, jak wyglądał projekt aplikacji, kiedy zaczynaliśmy go modyfikować. Następnie, na tej stronie, narysuj diagram klas obecnej wersji aplikacji Ryśka.

2GSRZLHG]L]QDMG]LHV]QDQDVWÚSQHMVWURQLH

jesteś tutaj  281

Większa elastyczność

Elastyczna aplikacja Ryśka W aplikacji Ryśka wprowadziliśmy naprawdę dużo zmian… i łatwo można zapomnieć wszystkie etapy, jakie przeszliśmy na drodze do jej ostatecznej postaci. Spójrz jednak na przedstawiony poniżej diagram klas i przekonaj się, o ile prostsza jest obecnie aplikacja Ryśka:

Inventory addInstrument(String, double, InstrumentSpec) get(String): Instrument search(InstrumentSpec): Instrument [*]

*

Inventory

Instrument serialNumber: String price: double getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

Klasa InstrumentSpec nie jest już klasą abstrakcyjną.

spec

1

InstrumentSpec properties: Map

Do przechowywania właściwości używamy obiektu typu Map, dzięki czemu nie potrzebujemy już klas pochodnych reprezentujących specyfikacje poszczególnych typów instrumentów.

282

getProperty(String): Object getProperties(): Map matches(InstrumentSpec): boolean

Rozdział 5. (część 2.)

Obecnie klasa Inventory definiuje tylko jedną metodę search(); co więcej, metoda ta może zwracać instrumenty różnych typów, oczywiście, o ile będą one pasować do podanej specyfikacji.

jest już Klasa Instrument nie ócz tego opr ; jną kcy tra klasą abs zystkich pozbyliśmy się tych wsentujących klas pochodnych reprez mentów. tru poszczególne typy ins

Dodaliśmy nowy typ wyliczeniowy, reprezentujący różne typy instrumentów dostępnych w sklepie Ryśka.

InstrumentType Builder toString(): String Type toString(): String Wood toString(): String Style toString(): String toString(): String Wszystkie te typy wyliczeniowe są używane do określania właściwo ści instrumentów, zapisywanych w klas InstrumentSpec, we właściwości ie properties typu Map. Jak widać, przypadku stopień powiązań pom w tym iędzy poszczególnymi klasami naszej aplik acji jest bardzo niewielki.

Dobry projekt = elastyczne oprogramowanie

Ale czy nasza aplikacja w ogóle działa? FODVV )LQG,Q VWUX PHQW^ PDLQ `

Obecnie aplikacja Ryśka wygląda znacznie lepiej niż na początku tego rozdziału, gdy zaczynaliśmy ją modyfikować — i bez wątpienia wygląda lepiej, niż gdy dodaliśmy do niej te wszystkie klasy pochodne reprezentujące banjo i mandoliny. Jednak wciąż musimy się upewnić, że nasze narzędzie wyszukiwawcze faktycznie działa, i to działa prawidłowo! A zatem zaktualizujmy naszą klasę testową i przekonajmy się, jak funkcjonuje wyszukiwanie wykorzystujące najnowszą wersję aplikacji Ryśka: LPSRUWMDYDXWLO+DVK0DS LPSRUWMDYDXWLO,WHUDWRU LPSRUWMDYDXWLO/LVW LPSRUWMDYDXWLO0DS SXEOLFFODVV)LQG,QVWUXPHQW^ SXEOLFVWDWLFYRLGPDLQ 6WULQJ>@DUJV ^ XWZRU]HQLHLLQLFMDOL]DMFDPDJD]\QX ,QYHQWRU\LQYHQWRU\ QHZ,QYHQWRU\  LQLWLDOL]H,QYHQWRU\ LQYHQWRU\ 

FindInstrument.java

Teraz klienc InstrumentSi wypełniają danymi typu instru pec. Ponieważ ten klobiekt mentu, zate ie może zwraca m aplikac nt nie określił gitary, man ć instrumenty dowol ja wyszukiwawcza dostępne w doliny oraz wszystkie nych typów — inne instru sklepie Ryś menty ka.

0DSSURSHUWLHV QHZ+DVK0DS  SURSHUWLHVSXW ĵEXLOGHUĵ%XLOGHU*,%621    SURSHUWLHVSXW ĴEDFN:RRGĵ:RRG0$3/(  ,QVWUXPHQW6SHFFOLHQW6SHF QHZ,QVWUXPHQW6SHF SURSHUWLHV 

co Obecnie musimy w nie ni red poś bez ej bardzi sposób posługiwać się mapą właściwości przechowywaną w obiekcie InstrumentSpec, jednak ej przy użyciu zwyczajn pętli można bardzo prosto przejrzeć e i wyświetlić wszystki . ntu właściwości instrume

/LVWPDWFKLQJ,QVWUXPHQWV LQYHQWRU\VHDUFK FOLHQW6SHF  LI PDWFKLQJ,QVWUXPHQWVLV(PSW\ ^ 6\VWHPRXWSULQWOQ Ĵ0RĝH]DLQWHUHVXMÈFLÚQDVWÚSXMÈFHLQVWUXPHQW\ĵ  IRU ,WHUDWRUL PDWFKLQJ,QVWUXPHQWVLWHUDWRU LKDV1H[W  ^ ,QVWUXPHQWLQVWUXPHQW  ,QVWUXPHQW LQH[W  ,QVWUXPHQW6SHFVSHF LQVWUXPHQWJHW6SHF    6\VWHPRXWSULQWOQ ĵ1DSU]\NïDGĵVSHFJHW3URSHUW\ ĵLQVWUXPHQW7\SHĵ  ĵRQDVWÚSXMÈF\FKZïDĂFLZRĂFLDFKĵ  IRU ,WHUDWRUM VSHFJHW3URSHUWLHV NH\6HW LWHUDWRU  MKDV1H[W  ^ 6WULQJSURSHUW\1DPH  6WULQJ MQH[W  Chcemy pominąć właści LI SURSHUW\1DPHHTXDOV ĵLQVWUXPHQW7\SHĵ instrumentType, gdyż wość FRQWLQXH zajęliśmy się nią jeszcz e 6\VWHPRXWSULQWOQ ĵĵSURSHUW\1DPHĵĵ przed rozpoczęciem VSHFJHW3URSHUW\ SURSHUW\1DPH  wykonywania pętli. `         6\VWHPRXWSULQWOQ ĵ7HQLQVWUXPHQWĵVSHFJHW3URSHUW\ ĵLQVWUXPHQW7\SHĵ  ĵMHVWZQDV]\PVNOHSLHGRVWÚSQ\]Dĵ LQVWUXPHQWJHW3ULFH ĵ3/1?Qĵ  ` `HOVH^ 6\VWHPRXWSULQWOQ ĵ3U]\NURQDPDOHQLHPDP\QLF]HJRFRE\VSHïQLDïR7ZRMHRF]HNLZDQLDĵ  ` `  PHWRGDLQLWLDOL]H,QYHQWRU\ LQLFMDOL]XMÈFDPDJD]\Q5\ĂND `

nty do dodać jakieś instrume Oprócz tego musimy byśmy mogli wyszukiwać tak ka, y się magazynu Ryś tylko gitary… Zajmiem w nim coś więcej niż e. oni tym na następnej str

jesteś tutaj  283

Inicjalizacja magazynu Ryśka

Polowanie na zawartość magazynu

Mandoliny

Gitary

Gibson F5-G, mandolina akustyczna, klon boki i spód, góra klon, numer seryjny #9019920, cena 5495,95 zł

Colings CJ, 6 strun, akustyczna, palisander indyjski spód i boki, góra świerk sitkajski, numer seryjny #11277, cena 3999,95 zł Martin D-18, 6 strun, akustyczna, mahoń spód i boki, góra dąb, numer seryjny #122784, cena 5495,95 zł Fender Stratocastor, 6 strun, elektryczna, olcha spód i boki, góra olcha, numer seryjny #V95693, cena 1499,95 zł Fender Stratocastor, 6 strun, elektryczna, olcha spód i boki, góra olcha, numer seryjny #V9512, cena 1549,95 zł Gibson SG ’61 Reissue, 6 strun, elektryczna, mahoń spód i boki, góra mahoń, numer seryjny #82765501, cena 1890,95 zł Gibson Les Paul, 6 strun, elektryczna, klon spód i boki, góra klon, numer seryjny #70108276, cena 2295,95 zł Oto sam początek metody initializeInvento ry(), który dodaje do magazynu Ryśka pierwszą z powyższych git ar.

dku Pamiętaj, że w przypa Strings num mandolin właściwość nie jest używana.

Banjo Gibson RB-3, 5 strun, banjo akustyczne, klon boki i spód, numer seryjny #8900231, cena 2945,95 zł

W przypadku banjo gat drewna używany w gór unek instrumentu nie jest nej części określany.

SULYDWHVWDWLFYRLGLQLWLDOL]H,QYHQWRU\ ,QYHQWRU\LQYHQWRU\ ^ 0DSSURSHUWLHV QHZ+DVK0DS  SURSHUWLHVSXW ĵLQVWUXPHQW7\SHĵ,QVWUXPHQW7\SH*8,7$5  SURSHUWLHVSXW ĵEXLOGHUĵ%XLOGHU&2//,1*6  SURSHUWLHVSXW ĵPRGHOĵĵ&-ĵ  SURSHUWLHVSXW ĵW\SHĵ7\SH$&2867,&  SURSHUWLHVSXW ĵQXP6WULQJVĵ  SURSHUWLHVSXW ĵWRS:RRGĵ:RRG,1',$1B526(:22'  SURSHUWLHVSXW ĵEDFN:RRGĵ:RRG6,7.$  LQYHQWRU\DGG,QVWUXPHQW ĵĵQHZ,QVWUXPHQW6SHF SURSHUWLHV  WXWDMXPLHĂÊVZöMNRG Tutaj powinieneś zapi sasać j d, ć swój kod, powinieneś zapi swTuta ` ój ko który do FODVV da do u y nu doda do mag maktór wszystkie gazy wszystkie azyn pozoestinst pozo stał e prze ałe rumenty. dsta prze dstawione instruwion menty.

284

2GSRZLHG]L]QDMG]LHV]QDVWURQLH

Aby przekonać się, czy nowa wersja oprogramowania Ryśka działa prawidłowo, musimy spróbować przeszukać magazyn, w którym będzie coś więcej niż jedynie gitary. Twoim zadaniem jest napisanie kodu metody LQLWLDOL]H,QYHQWRU\ umieszczonej w pliku )LQG,QVWUXPHQWMDYD, która doda do magazynu Ryśka kilka gitar, mandolin oraz banjo. Poniżej wymieniliśmy instrumenty, które obecnie znajdują się w magazynie Ryśka, a nawet, by ułatwić Ci wykonanie zadania, napisaliśmy kod, który dodaje pierwszą gitarę.

Rozdział 5. (część 2.)

)LQG,Q VWUX PHQW^ PDLQ `

FindInstrument.java

Dobry projekt = elastyczne oprogramowanie

Testowanie dobrze zaprojektowanej aplikacji Ryśka Upewnij się, że do metody LQLWLDOL]H,QYHQWRU\ w pliku )LQG,QVWUXPHQWMDYD dodałeś wszystkie instrumenty przedstawione na poprzedniej stronie, a następnie skompiluj wszystkie klasy nowej wersji aplikacji. Teraz jesteś już gotów, by przetestować aplikację Ryśka… …a przynajmniej — prawie gotów. Przede wszystkim musisz określić, co powinno zwracać wyszukiwanie bazujące na aktualnej wersji klasy )LQG,QVWUXPHQW. Oto zbiór wymagań, jakie podał nowy klient Ryśka:

0DSSURSHUWLHV QHZ+DVK0DS  SURSHUWLHVSXW ĵEXLOGHUĵ%XLOGHU*,%621  SURSHUWLHVSXW ĵEDFN:RRGĵ:RRG0$3/(  ,QVWUXPHQW6SHFFOLHQW6SHF  QHZ,QVWUXPHQW6SHF SURSHUWLHV 

Klient Ryśka nie instrumentu, jed określił typu go jakiś instrumenak interesuje z bokami i spod nt firmy Gibson em z drzewa klonowe wykonanymi go.

FODVV )LQG,Q VWUX PHQW^ PDLQ `

FindInstrument.java

Bazując na tej specyfikacji, przejrzyj instrumenty przedstawione na poprzedniej stronie i zapisz, jakie gitary, mandoliny oraz banjo powinna zwrócić aplikacja Ryśka:

które instrumenty, Tutaj zapisz powinien zwrócić ie eb według Ci Instrument po program Find magazynu Ryśka. przeszukaniu

NAGRODA SPECJALN A. Spróbuj zapisać instru które odnajdzie aplika menty, cja Ryśka, i zapisz je dok w takiej postaci, w jakładnie zostaną one wyświetloniej e.

jesteś tutaj  285

Dodawanie instrumentów do magazynu Ryśka

Wyniki polowania na zawartość magazynu Aby przekonać się, czy nowa wersja oprogramowania Ryśka działa prawidłowo, musimy spróbować przeszukać magazyn, w którym będzie coś więcej niż jedynie gitary. Twoim zadaniem jest napisanie kodu metody LQLWLDOL]H,QYHQWRU\ umieszczonej w pliku )LQG,QVWUXPHQWMDYD, która doda do magazynu Ryśka kilka gitar, mandolin oraz banjo.

SULYDWHVWDWLFYRLGLQLWLDOL]H,QYHQWRU\ ,QYHQWRU\LQYHQWRU\ ^ 0DSSURSHUWLHV QHZ+DVK0DS  SURSHUWLHVSXW ĵLQVWUXPHQW7\SHĵ,QVWUXPHQW7\SH*8,7$5  SURSHUWLHVSXW ĵEXLOGHUĵ%XLOGHU&2//,1*6  Colings CJ, 6 strun, akustyczna, palisande SURSHUWLHVSXW ĵPRGHOĵĵ&-ĵ  r indyjski spód i boki, góra świerk sitkajski, SURSHUWLHVSXW ĵW\SHĵ7\SH$&2867,&  numer seryjny #11277, cena 3999,95 zł SURSHUWLHVSXW ĵQXP6WULQJVĵ     

Tu wykorzystaliśmy rozwiązanie skrótowe — cały czas używamy tego samego obiektu Map.

SURSHUWLHVSXW ĵWRS:RRGĵ:RRG,1',$1B526(:22'  SURSHUWLHVSXW ĵEDFN:RRGĵ:RRG6,7.$  LQYHQWRU\DGG,QVWUXPHQW ĵĵ Martin D-18, 6 strun, akustyczna, mahoń QHZ,QVWUXPHQW6SHF SURSHUWLHV  spód i boki, góra dąb, numer seryjny #122784, cena 5495,95 zł SURSHUWLHVSXW ĵEXLOGHUĵ%XLOGHU0$57,1  SURSHUWLHVSXW ĵPRGHOĵĵ'ĵ      SURSHUWLHVSXW ĵWRS:RRGĵ:RRG0$+2*$1<  SURSHUWLHVSXW ĵEDFN:RRGĵ:RRG$',521'$&.  zna, LQYHQWRU\DGG,QVWUXPHQW ĵĵ Fender Stratocastor, 6 strun, elektryc seryjny r me nu , ha olc ra QHZ,QVWUXPHQW6SHF SURSHUWLHV  olcha spód i boki, gó ,95 zł #V95693, cena 1499 SURSHUWLHVSXW ĵEXLOGHUĵ%XLOGHU)(1'(5  SURSHUWLHVSXW ĵPRGHOĵĵ6WUDWRFDVWRUĵ  SURSHUWLHVSXW ĵW\SHĵ7\SH(/(&75,&   Fender Stratocastor,   6 strun, elektryczna, SURSHUWLHVSXW ĵWRS:RRGĵ:RRG$/'(5   olcha spód  i boki, góra olcha, numer seryjny #V9512, cena 1549,95 zł SURSHUWLHVSXW ĵEDFN:RRGĵ:RRG$/'(5  LQYHQWRU\DGG,QVWUXPHQW ĵ9ĵ QHZ,QVWUXPHQW6SHF SURSHUWLHV  LQYHQWRU\DGG,QVWUXPHQW ĵ9ĵ QHZ,QVWUXPHQW6SHF SURSHUWLHV 

óch gitar Specyfikacje tych dw się jedynie nią róż e, czn są identy trumentów. właściwości samych ins

286

Rozdział 5. (część 2.)

Dobry projekt = elastyczne oprogramowanie

SURSHUWLHVSXW ĵEXLOGHUĵ%XLOGHU*,%621  SURSHUWLHVSXW ĵPRGHOĵĵ/HV3DXOĵ  strun, elektryczna, SURSHUWLHVSXW ĵWRS:RRGĵ:RRG0$3/(   Gibson Les Paul, 6  klon spód i boki, góra klon, numer seryjny SURSHUWLHVSXW ĵEDFN:RRGĵ:RRG0$3/(  #70108276, cena 2295,95 zł LQYHQWRU\DGG,QVWUXPHQW ĵĵ QHZ,QVWUXPHQW6SHF SURSHUWLHV  SURSHUWLHVSXW ĵPRGHOĵĵ6*IJ5HLVVXHĵ  SURSHUWLHVSXW ĵWRS:RRGĵ:RRG0$+2*$1<  SURSHUWLHVSXW ĵEDFN:RRGĵ:RRG0$+2*$1<   LQYHQWRU\DGG,QVWUXPHQW ĵĵ QHZ,QVWUXPHQW6SHF SURSHUWLHV  Jeśli dodając kolejne instrumenty, używasz tej samej mapy właściwości, to dodając mandolinę, nie zapomnij usunąć wartości właściwości numStrings.

n, elektryczna, Gibson SG ’61 Reissue, 6 stru  mahoń spód i boki, góra mahoń, numer 0,95 zł seryjny #82765501, cena 189

SURSHUWLHVSXW ĵLQVWUXPHQW7\SHĵ,QVWUXPHQW7\SH0$1'2/,1  SURSHUWLHVSXW ĵW\SHĵ7\SH$&2867,&  SURSHUWLHVSXW ĵPRGHOĵĵ)*ĵ  Gibson F5-G, mandolina akustyczna, klon SURSHUWLHVSXW ĵEDFN:RRGĵ:RRG0$3/(  boki i spód,  góra klon,  numer seryjny SURSHUWLHVSXW ĵWRS:RRGĵ:RRG0$3/(  #9019920, cena 5495,95 zł. SURSHUWLHVUHPRYH ĵQXP6WULQJVĵ  LQYHQWRU\DGG,QVWUXPHQW ĵĵ Banjo nie mają QHZ,QVWUXPHQW6SHF SURSHUWLHV  wierzchniej warstwy drewna, zatem usunęliśmy

właściwość SURSHUWLHVSXW ĵLQVWUXPHQW7\SHĵ,QVWUXPHQW7\SH%$1-2  topWood. SURSHUWLHVSXW ĵPRGHOĵĵ5%:UHDWKĵ  SURSHUWLHVUHPRYH ĵWRS:RRGĵ     SURSHUWLHVSXW ĵQXP6WULQJVĵ  Gibson RB-3, 5 strun, banjo akustyczne, klon LQYHQWRU\DGG,QVWUXPHQW ĵĵ boki i spód, numer seryjny #8900231, cena QHZ,QVWUXPHQW6SHF SURSHUWLHV  2945,95 zł. FODVV ` )LQG,Q ` VWUX PHQW^ PDLQ `

FindInstrument.java

jesteś tutaj  287

Aplikacja Ryśka działa tak, jak powinna

Rysiek dostał działającą aplikację, a jego klient ma trzy instrumenty do wyboru:

Ta gitara spełnia wymagania okre w specyfikacji Bartka, gdyż jej ślone spód i boki są wykonane z drewna klon i została wyprodukowana w firm owego ie Gibson.

Klient Ryśka ma możliwość wyboru jednego z trzech instrumentów: gitary, mandoliny lub banjo.

y A to z kolei jest mandolina firm drewna z m nany wyko em spod ze on Gibs pasuje ent rum inst ten e takż go… klonowe ka. Bart cji do specyfika

Oto kolejny instrument firmy Gibson zrobiony z drewna klonowego… tym razem jest to banjo. W przypadku banjo nie jest określane drewno, z jakiego wykonany jest wierzch instrumentu, jednak nie ma to dla nas większego znaczenia.

Nie istnieją

głupie pytania

P: Wyniki wygenerowane przez moją wersję programu

P: Czy to jest dobry test; chodzi mi o to, że dysponujemy

są inne niż przedstawione w książce. Co zrobiłem źle?

tylko jednym banjo i jedną mandoliną?

O: Jeśli Twoja wersja aplikacji Ryśka zwróciła inne instrumenty

O: To bardzo dobre pytanie. I owszem, masz rację, byłoby

lub te same instrumenty, lecz posiadające inne cechy, to powinieneś sprawdzić, czy zdefiniowałeś te same instrumenty. Sprawdź ćwiczenie na stronie 284, a następnie odpowiedzi zamieszczone na stronach 285 – 286. Upewnij się, że instrument, jaki Twoja aplikacja zapisuje w magazynie, jest taki sam jak nasz.

288

Rozdział 5. (część 2.)

znacznie lepiej, gdybyśmy dysponowali kilkoma mandolinami i banjo więcej, tak by aplikacja Ryśka mogła wybierać z nich jedynie te, które spełniają narzucone kryteria. Nie ociągaj się, dodaj do systemu kilka nowych mandolin i banjo, które nie spełniają zadanych kryteriów, a następnie sprawdź, jakie wyniki zwróci aplikacja Ryśka.

Dobry projekt = elastyczne oprogramowanie

To świetnie, że udało Ci się zapewnić poprawne działanie swojego oprogramowania, jednak nie powinieneś jeszcze popadać w samozachwyt ze względu na ten doskonały projekt. Ja oraz moi koledzy z „Komisji do spraw zmian" czuwamy, by sprawdzić, jak w rzeczywistości wygląda spójność Twojego oprogramowania.

Koordy n

ator ds . zmian

;

Jak łatwo można wprowadzać zmiany w aplikacji Ryśka?

;

Czy aplikacja Ryśka naprawdę jest dobrze zaprojektowana?

;

I co w ogóle oznacza termin „spójność oprogramowania”?

jesteś tutaj  289

Konkurs łatwości modyfikacji

Wielki konkurs łatwości modyfikacji Jak łatwo można wprowadzać zmiany w aplikacji Ryśka? Spróbujmy ponownie dodać do aplikacji Ryśka możliwości obsługi skrzypiec i gitar Dobro. W rozdziale 5. próbowaliśmy ułatwić całą zabawę, lecz wyszedł z tego jeden wielki bałagan. Teraz jednak wszystko powinno wyglądać znacznie prościej, prawda? Poniżej przedstawiliśmy diagram klas aktualnej wersji aplikacji Ryśka.

Inventory

Oto jak obecnie ojekt wygląda nasz pr a. śk Ry ji ac lik ap

addInstrument(String, double, InstrumentSpec) get(String): Instrument search(InstrumentSpec): Instrument [*]

*

Inventory

Instrument serialNumber: String price: double getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

spec

1

InstrumentSpec properties: Map getProperty(String): Object getProperties(): Map matches(InstrumentSpec): boolean

290

Rozdział 5. (część 2.)

InstrumentType Builder toString(): String Type toString(): String Wood toString(): String Style toString(): String toString(): String

Dobry projekt = elastyczne oprogramowanie

Sprawdźmy naszą aplikację przy wykorzystaniu testu łatwości modyfikacji Sprawdzenie łat wo wprowadzania zm ści w oprogramowan ian iu jednym z najleps jest sposobów przeko zych się, czy projekt nania oprogramowania tego jest dobry.

1

Ile nowych typów trzeba było dodać do aplikacji Ryśka, by obsłużyć nowy typ instrumentu?

2

Ile klas trzeba było zmienić, by obsłużyć nowy typ instrumentu?

3

Załóżmy, że Rysiek chciałby zacząć przechowywać w swojej aplikacji informacje o roku produkcji instrumentów. Ile klas musiałbyś zmienić, by umożliwić gromadzenie i przechowywanie tych informacji?

4

Załóżmy, że Rysiek chciałby także dodać nową właściwość — neckWood — w której planuje zapisywać informacje o drewnie, z jakiego jest wykonany gryf instrumentu. Ile klas musiałbyś zmienić w celu dodania tej właściwości do aplikacji?

2GSRZLHG]L]QDMG]LHV]QDVWURQLH

jesteś tutaj  291

Łatwa do modyfikacji?

Wielki konkurs łatwości modyfikacji Jak łatwo można wprowadzać zmiany w aplikacji Ryśka? Spróbujmy ponownie dodać do aplikacji Ryśka możliwości obsługi skrzypiec i gitar Dobro. W rozdziale 5. próbowaliśmy ułatwić całą zabawę, lecz wyszedł z tego jeden wielki bałagan. Teraz jednak wszystko powinno wyglądać znacznie prościej, prawda?

Sprawdźmy naszą aplikację przy wykorzystaniu testu łatwości modyfikacji 1

Ile nowych typów trzeba było dodać do aplikacji Ryśka, by obsłużyć nowy typ instrumentu? Żadnego! Pozbyliśmy się tych wszystkich klas pochodnych reprezentujących poszczególne typy instrumentów i dziedziczących od klas Instrument i InstrumentSpec.

2

Ile klas trzeba było zmienić, by obsłużyć nowy typ instrumentu? Jedną: musimy dodać nowy typ instrumentu do typu wyliczeniowego InstrumentType.

3

Załóżmy, że Rysiek chciałby zacząć przechowywać w swojej aplikacji informacje o roku produkcji instrumentów. Ile klas musiałbyś zmienić, by umożliwić gromadzenie i przechowywanie tych informacji? Żadnej! Rok produkcji instrumentu możemy zapisać w obiekcie Map dostępnym w obiekcie specyfikacji — InstrumentSpec.

4

Załóżmy, że Rysiek chciałby także dodać nową właściwość — neckWood — w której planuje zapisywać informacje o drewnie, z jakiego jest wykonany gryf instrumentu. Ile klas musiałbyś zmienić w celu dodania tej właściwości do aplikacji? Może żadnej, ale w najgorszym przypadku jedną. neckWood to po prostu kolejna właściwość, którą możemy przechowywać w mapie w obiekcie InstrumentSpec… jednak być może będziemy musieli dodać nowe gatunki drewna do typu wyliczeniowego Wood.

292

Rozdział 5. (część 2.)

Dobry projekt = elastyczne oprogramowanie

Jak miło! Nasze oprogramowanie można łatwo modyfikować… ...ale co z tą „spójnością”?

Spójna klasa realizuje e są Im bardziej spójn jest za klasy, tym wyżs owania. spójność oprogram

jedną operację

Spójne klasy koncentrują się na konkretnych zadaniach. Nasza klasa Inventory dba jedynie o magazyn sklepu Ryśka, a nie o to, jakie gatunki drewna mogą być zastosowane do produkcji gitar lub jakie dwa instrumenty porównać ze sobą.

naprawdę dobrze Przyjrzyj się metodom swoich klas — czy odpowiadają one nazwom klas, w jakich zostały zdefiniowane? Jeśli znajdziesz jakąś metodę, która wydaje się nie na miejscu, to być może powinna ona należeć do jakiejś innej klasy.

i nie udaje, że

robi coś innego lub

jest czymś innym.

nie próbuje Klasa Instrumentukiwania sz obsługiwać wy omadzić ani nie usiłuje grnie, z jakiego ew dr o cji ma or inf instrumenty. są wykonywane jedynie na się je tru Koncen ntu — me tru ins opisaniu na niczym więcej.

Kącik naukowy spójność — określa stopień powiązań pomiędzy elementami jednego modułu, klasy lub obiektu. Im wyższa jest spójność oprogramowania, tym tworzące je klasy są lepiej zdefiniowane, a ich obowiązki — lepiej ze sobą związane. Każda klasa dysponuje precyzyjnie określonym zbiorem związanych ze sobą akcji, które może wykonywać. jesteś tutaj  293

Spójne klasy koncentrują się na jednej rzeczy

Spójność oraz jeden powód do modyfikacji klasy Być może nie zdajesz sobie z tego sprawy, jednak w tej książce pisaliśmy już o spójności. Pamiętasz może to stwierdzenie:

Jest powodem katastrofy i śmierci wielu źle zaprojektowanych fragmentów oprogramowania, dlatego każda klasa powinna starać się, by mieć do niej tylko jeden powód.

To jedna z odpowi w poprzedniej cz edzi podanych — Obiektowa Ka ęści rozdziału ta Czy pamiętasz py strofa. pasujące do tej tanie odpowiedzi?

W rzeczywistości spójność pozwala określić, jak bardzo powiązane są ze sobą funkcjonalności klas tworzących aplikację. Jeśli wszystkie możliwości funkcjonalne pewnej klasy są ze sobą ściśle powiązane, to będzie istnieć tylko jeden powód do modyfikowania takiej klasy… a o tym pisaliśmy już w poprzedniej części tego rozdziału, pt. Obiektowa Katastrofa! Oto klasy, jakie analizowaliśmy, starając się zapewnić, by dla każdej klasy istniał tylko jeden powód do wprowadzania modyfikacji. klas jest bardzo Funkcja każdej z tych Każda z tych klas . ona eśl okr ie yjn cyz pre jnością, cechuje się wysoką spónich można łatwo z dą każ u tem a dzięki konieczności modyfikować i to bez s. kla ych zmieniania inn

Automobile start() stop() getOil(): int

CarWash wash(Automobile)

Driver drive(Automobile)

Czy możesz wymy sposób, by klasa ślić jakiś jeszcze bardziej Mechanic była spójna?

294

Rozdział 5. (część 2.)

Mechanic checkOil(Automobile) changeTires(Automobile, Tire [*])

Dobry projekt = elastyczne oprogramowanie Nie istnieją

głupie pytania

P: Czyli spójność to jedynie

P: Jednak to wszystko oznacza,

P: Czy oprogramowanie cechujące

wymyślne słowo określające, jak łatwo można modyfikować moje aplikacje?

że modyfikowanie oprogramowania będzie łatwiejsze, prawda?

O: Niezupełnie. Zagadnienie spójności

O: W większości przypadków — tak. Czy

się wysoką spójnością jest nie tylko łatwe do modyfikacji, lecz także do wielokrotnego stosowania?

koncentruje się na sposobie, w jaki zostały stworzone poszczególne klasy, obiekty lub pakiety wchodzące w skład oprogramowania. Jeśli każda z klas realizuje kilka zadań, które są ze sobą powiązane, to prawdopodobnie będzie to fragment oprogramowania o wysokiej spójności. Jeśli jednak jakaś klasa wykonuje wiele czynności, które nie są ze sobą powiązane, to najprawdopodobniej oprogramowanie będzie miało niską spójność.

P: A zatem oprogramowanie o wysokiej spójności jest jednocześnie oprogramowaniem o luźnych powiązaniach?

O: Dokładnie! Niemal zawsze, im

bardziej spójne jest oprogramowanie, tym luźniejsze powiązania pomiędzy poszczególnymi klasami. W przypadku aplikacji Ryśka klasa ,QYHQWRU\ faktycznie dba jedynie o zarządzanie magazynem, nie interesuje się natomiast porównywaniem instrumentów ani sposobem przechowywania właściwości określających ich specyfikacje. Oznacza to, że klasa ,QYHQWRU\ ma wysoką spójność. Jednocześnie oznacza to także, iż jest ona luźno powiązana z pozostałymi klasami w aplikacji — na przykład zmiany wprowadzane w klasie ,QVWUXPHQW nie będą miały dużego wpływu na klasę ,QYHQWRU\.

pamiętasz jednak wersję aplikacji Ryśka, od której zaczynaliśmy pracę w tym rozdziale? Obsługiwała ona jedynie gitary i nawet nie dysponowaliśmy klasami ,QVWUXPHQW oraz ,QVWUXPHQW6SHF. Jednak ta wersja aplikacji była naprawdę spójna, powiązanie pomiędzy klasą *XLWDU oraz ,QYHQWRU\ było bardzo luźne. Niemniej jednak wyposażenie aplikacji w możliwość obsługi mandolin wymagało dużego nakładu pracy i wielu zmian w projekcie. Jeśli w znaczący sposób zmieniasz sposób działania aplikacji — jak w przypadku przystosowania aplikacji przeznaczonej do sprzedawania instrumentów jednego typu, do sprzedawania wielu różnych typów instrumentów — to być może będziesz musiał wprowadzić wiele zmian w projekcie, który jest spójny i cechuje się luźnymi powiązaniami. A zatem spójność nie zawsze jest testem określającym łatwość modyfikowania aplikacji. Niemniej jednak, w przypadkach gdy nie zmieniasz drastycznie sposobu działania aplikacji, wysoka spójność oprogramowania będzie zazwyczaj oznaczać także dużą łatwość wprowadzania zmian.

P: A wysoka spójność jest lepsza od niskiej?

O: Trafiłeś w sedno. Wysoka spójność

oraz luźne powiązania wspólnie sprawiają, że oprogramowanie można łatwo rozszerzać, a nawet dzielić na fragmenty, które będą wielokrotnie stosowane. A wszystko to dlatego, iż poszczególne obiekty w oprogramowaniu nie są wzajemnie od siebie zależne. Możesz to sobie wyobrazić w następujący sposób: im wyższa jest spójność aplikacji, tym precyzyjniej są określone zadania i funkcje poszczególnych obiektów. A im precyzyjniej są zdefiniowane obiekty (ich zadania i funkcje), tym łatwiej można wyodrębnić taki obiekt z kontekstu i zastosować w zupełnie innym kontekście. Obiekt po prostu realizuje swoje precyzyjnie określone zadanie, niezależnie od tego, gdzie jest używany.

P: A wszystkie zmiany, jakie wprowadzaliśmy w tym rozdziale w aplikacji Ryśka, zwiększały jej spójność, prawda?

O: W większości przypadków — tak.

Jednak przyjrzyjmy się temu pytaniu nieco dokładniej…

O: Owszem. Projekt obiektowy jest

dobry, kiedy wszystkie klasy i moduły tworzące aplikację realizują jedno proste zadanie oraz jeśli realizują je naprawdę dobrze. Jeśli jednak jedna klasa zaczyna wykonywać dwa lub kilka różnych zadań, to najprawdopodobniej będzie to oznaczać, że odchodzisz od wysokiej spójności i oddalasz się od dobrego projektu.

jesteś tutaj  295

Zwiększanie spójności

Przegląd zmian wprowadzonych w oprogramowaniu dla Ryśka Czy zatem zmiany, jakie wprowadzaliśmy w aplikacji Ryśka, spowodowały zwiększenie jej spójności? Czy nasze obiekty są ze sobą luźno powiązane? Przekonajmy się…

wysoka

sji do spraw Członkowie Komi erzą w potęgę wi ko bo głę ian Zm mowania projektów progra ości. ójn sp j kie o wyso

iek handlował W tym momencie Rys aplikacja jedynie gitarami. Nasza dzo spójna, bar sie cza tym w a był dodanie niezależnie od tego, żemandolin aży zed spr i możliwośc a bardzo wymagało wprowadzeni jekcie. poważnych zmian w pro

Inventory addGuitar(String, double, GuitarSpec) getGuitar(String): Guitar search(GuitarSpec): Guitar [*]

inventory

* spec

Guitar serialNumber: String price: double

GuitarSpec

spójność

1

type

1

toString(): String

niska 296

topWood

1 Type

toString(): String

inventory

*

blackWood

Builder

getSerialNumber():String getPrice(): double setPrice(float) getBuilder():String getModel():String getType():String getBackWood():String getTopWood():String

addInstrument(String, double, InstrumentSpec) get(String): Instrument search(GuitarSpec): Guitar [*] search(MandolinSpec): Mandolin [*]

getBuilder(): Builder getModel(): String getType(): Type getBackWood(): Wood getTopWood(): Wood getNumStrings(): int

builder

Guitar

Inventory

model: String numStrings: int

1

getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): GuitarSpec

serialNumber: String price: double builder: String model: String type: String backWood: String topWood: String

Oto nasza pierwsza próba dodania możliwości sprzedaży instrumentów wielu różnych typów… Ta wersja aplikacji na pewno miała niższą spójność niż poprzednia.

Instrument

1

Guitar

serialNumber: String price: double

Wood

getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

toString(): String

Inventory

Mandolin

1

spec

guitars: List

Rozdział 5. (część 2.)

numStrings: int

model: String getBuilder(): Builder getModel(): String getType(): Type getBackWood(): Wood getTopWood(): Wood matches(InstrumentSpec): boolean builder

Czy pamiętasz ten prosty diagram klas, który narysowaliśmy jeszcze w rozdziale 1.? Wtedy nasza aplikacja składała się jedynie z dwóch klas i nie były one wcale ani dobrze zaprojektowane, ani wyjątkowo spójne.

GuitarSpec

InstrumentSpec

addGuitar(String, double, String, String, String, String, String); getGuitar(String): Guitar search(Guitar): Guitar

1

type

blackWood topWood

1

Builder toString(): String

1

Type toString(): String

1 Wood

toString(): String

getNumStrings(): int matches(InstrumentSpec): boolean

MandolineSpec getStyl(): Style matches(InstrumentSpec): boolean style

1

Style toString(): String

Dobry projekt = elastyczne oprogramowanie Za każdym razem gdy wprowadzasz zmiany w oprogramowaniu, staraj się, by zwiększały one jego spójność.

A to już obecna postać projektu uje Ryśka. Ta wersja aplikacji cech ymi luźn ią, nośc spój oką się wys powiązaniami i bardzo dużą łatwością rozszerzania i wielokrotnego użycia.

Inventory addInstrument(String, double, InstrumentSpec) get(String): Instrument search(InstrumentSpec): Instrument [*]

inventory

*

Instrument

To był chyba najgorszy moment naszego projektu… Próby dodawania nowych instrumentów kończyły się totalną katastrofą.

serialNumber: String price: double getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

spec

Inventory

1

InstrumentType Builder toString(): String

InstrumentSpec

addInstrument(String, double, InstrumentSpec) get(String): Instrument search(GuitarSpec): Guitar [*] search(MandolinSpec): Mandolin [*]

Type toString(): String

properties: Map getProperty(String): Object getProperties(): Map matches(InstrumentSpec): boolean

Wood toString(): String Style toString(): String toString(): String

*

inventory

Instrument serialNumber: String price: double

spec

Dobro Mandolin ndo

Fiddle Bass

1

InstrumentSpec

numStrings: num mSt m S int

getBuilder(): Builder getModel(): String getType(): Type getBackWood(): Wood getTopWood(): Wood matches(InstrumentSpec): boolean

getNumStrings(): int matches(InstrumentSpec): boo

1

type

1

Type String

MandolinSpec

blackW ckW kWood topWood

1

Builder

BanjoSpec numStrings: int

DobroSpec getNumStrings(): int matches(GuitarSpec): numStrings: int BassSpecboolean

getStyle(): Style matches(InstrumentSpec): boole

1 Wood

toStringg

Właśnie TO jest naszym celem… oprogramowanie, które poprzez sekwencję zmian projektowych staje się bardziej spójne.

GuitarSpec

model: String

build

toString(): String

Banjo

Guit

getSerialNumber(): String getPrice(): double setPrice(float) getSpec(): InstrumentSpec

1

String

Cykl życia projektu

numStrings: int getNumStrings(): int FiddleSpec matches(GuitarSpec): boolean getNumStrings(): int numStrings: int matches(GuitarSpec): boolean getNumStrings(): int toString(): String matches(GuitarSpec): boolean

jesteś tutaj  297

Doskonałe oprogramowanie wystarczy

To wszystko wygląda naprawdę super. Ale skąd wiesz, kiedy zakończyć zmiany i uznać, że projekt jest gotowy? Chodzi mi o to, czy jest coś w stylu skali spójności i kiedy dotrę do wartości „10" (lub innej), to będzie to oznaczać, że można skończyć?

Doskonałe oprogramowanie to zazwyczaj takie, które jest wystarczająco dobre. Bardzo trudno jest określić, kiedy należy zakończyć projektowanie oprogramowania. Oczywiście, można się upewnić, że oprogramowanie robi to, co powinno, i wtedy przystąpić do prac nad poprawą jego elastyczności i spójności. Ale co potem? Czasami trzeba skończyć projektowanie, bo wyczerpał się limit czasu… albo funduszy… a czasami po prostu zdajesz sobie sprawę, że wykonałeś swoją pracę na tyle dobrze, że można zająć się czymś innym. Jeśli stworzone oprogramowanie działa, jeśli klient jest zadowolony i jeśli zrobiłeś wszystko, co w Twojej mocy, by upewnić się, że oprogramowanie jest dobrze zaprojektowane, to być może będzie to właściwy moment, by zająć się kolejnym projektem. Spędzanie nie wiadomo ilu godzin na próbach napisania „oprogramowania doskonałego” jest zwyczajną stratą czasu; natomiast poświęcenie wielu godzin pracy na stworzenie doskonałego oprogramowania, a następnie zajęcie się kolejnym projektem nie tylko zapewni Ci więcej pracy, szybki awans, lecz także masę kasy i wiele zaszczytów.

298

Rozdział 5. (część 2.)

Dobry projekt = elastyczne oprogramowanie

Trzeba wiedzieć, kiedy powiedzieć: „Jest wystarczająco dobrze!” Ubóstwiam to! W końcu mogę sprzedawać wszystkie typy instrumentów, jakie tylko zechcę, i pomagać moim klientom w wyborze tych instrumentów, których poszukują.

Upewnij się, że projekt jest elastyczny

Zanim uznasz projekt za zakończony, zawsze że musisz się upewnić, to, oprogramowanie robi co powinno.

Zrobiliśmy tu naprawdę dobry projekt. Cechuje się wysoką spójnością, luźnymi powiązaniami pomiędzy klasami… Założę się, że kiedy następnym razem Rysiek będzie chciał coś zmienić, nie będziemy z tym mieli najmniejszych problemów.

Kiedy już zadbas o odpowiednią z funkcjonalność, up się, że podejmow ewnij trafne decyzje pr ałeś i wykorzystaj do ojektowe, br projektowania ob e zasady by poprawić ela iektowego, st swojej aplikacji. yczność

Upewnij się, że klient jest zadowolony

Jeśli zrobiłeś obie z powyższych rzeczy, to może nadszedł czas, by zająć się czymś innym… następnym projektem, następną aplikacją albo nawet następnym rozdziałem. jesteś tutaj  299

Przybornik projektanta

Narzędzia do naszego projektanckiego przybornika O rany! Naprawdę przebyłeś bardzo długą drogę od czasu, kiedy w rozdziale 1. zaczęliśmy pracować nad aplikacją dla Ryśka. Dowiedziałeś się w tym czasie bardzo wiele na temat projektowania, warto zatem krótko podsumować wszystko, co możesz dodać do swojego projektanckiego przybornika. W tym rozdziale zrobiliśmy kilka projektów, poświęć więc chwilę, by przeanalizować wszystko, czego się nauczyłeś.

Wymagania Dobre wymagania gwarantują, że system będzie działał zgodnie z wymaganiami klienta.

Analiza i projekt

zaprojektowane oprogramowanie Upewnij się, że wymagania obejmują Dobrze łatwo zmieniać i rozszerzać. można użycia ku przypad kroki ie wszystk opracowanego dla tworzonego systemu. Stosuj podstawowe zasady projektowania obiektowego, takie jak hermetyzacja by Wykorzystaj przypadki użycia, zenie, by poprawić elastyczność dowiedzieć się o wszystkich rzeczach,i odziedzicoprogramowania. swojego których klient zapomniał Ci powiedzieć. projekt nie jest elastyczny, to Przypadki użycia ujawnią wszystkie Jeśli Nigdy nie zostawiaj złego ZMIEŃ! GO nia, wymaga e brakując lub niekompletne jeśli jest to Twój nawet , projektu do dodać musiał które zapewne będziesz projekt. tworzonego systemu. się, że każda z klas aplikacji nia Wraz z upływem czasu Twoje wymagaUpewnij jest spójna: każda z nich powinna zawsze będą się zmieniać (i powiększać). się koncentrować na realizacji TYLKO JEDNEGO ZADANIA, przy czym powinna je wykonywać bardzo dobrze. Modyfikując oprogramowanie, zawsze i ze wszystkich sił staraj się poprawiać jego spójność.

Celem dobrego projektu jest uzyskanie oprogramowania o wysokim stopniu spójności i luźnych powiązaniach.

o Zasady projektowania obiektoweg zmienia. Poddawaj hermetyzacji to, co się ntacje. Stosuj interfejsy, a nie impleme mieć tylko jeden powód Każda klasa w aplikacji powinna do zmian. cjonalności. Klasy dotyczą zachowania i funk

300

Rozdział 5. (część 2.)

częścią Pomiędzy poprzednią a Katastrofa! tow iek Ob — ału dzi roz do naszego my aliś dod tą z — ora ro zasad spo m kie cał ka przyborni . ego ow ekt obi a projektowani

5R]ZLÈ]\ZDQLHQDSUDZGÚGXĝ\FKSUREOHPöZ

„Nazywam się Art Vandelay… jestem Architektem” Tak sobie myślałem… nie pamiętasz może, czy dobrze skręciliśmy śruby w rusztowaniu na dolnych piętrach?

Nadszedł czas, by zbudować coś NAPRAWDĘ DUŻEGO. Czy jesteś gotów? Zdobyłeś już bardzo dużo narzędzi do swojego projektanckiego przybornika, jednak w jaki sposób z nich skorzystasz, kiedy będziesz musiał napisać coś naprawdę dużego? Cóż, może jeszcze nie zdajesz sobie z tego sprawy, ale dysponujesz wszystkimi narzędziami, jakie mogą być potrzebne do skutecznego rozwiązywania poważnych problemów. Niebawem poznasz kilka nowych narzędzi, takich jak analiza dziedziny oraz diagramy przypadków użycia, jednak nawet te nowe narzędzia opierają się na wiadomościach, które już zdobyłeś, takich jak: uważne słuchanie klienta oraz dokładne zrozumienie, co trzeba napisać, zanim jeszcze przystąpimy do faktycznego pisania kodu. Przygotuj się… nadszedł czas, byś sprawdził, jak sobie radzisz w roli architekta.

to jest nowy rozdział  301

A co z dużymi aplikacjami?

Słuchajcie… wszystkie te gadki o pisaniu wspaniałego oprogramowania są super… ale prawdziwe aplikacje zawierają znacznie więcej niż pięć klas. Niby w jaki sposób mam przekształcić taką dużą aplikację we wspaniałe oprogramowanie?

Duże problemy rozwiązujemy tak samo jak małe. Do tej pory pracowaliśmy nad stosunkowo małymi aplikacjami… Aplikacja obsługująca sklep Ryśka, w swojej najgorszej postaci, liczyła jedynie kilkanaście klas, a system sterujący drzwiczkami dla psa nigdy nie miał więcej niż kilku. Jednak wszystko, czego się do tej pory nauczyłeś, można wykorzystać także podczas pisania dużych aplikacji.

1. Upewnij się, że oprogramowanie robi to, czego oczekuje klient.

Czy pamięta prowadzące sz te trzy kroki doskonałeg do powstania o W równym oprogramowania? one do ogrostopniu odnoszą się składającychmnych aplikacji, klas, jak ró się z ponad 1000 wymagając wnież do projektów yc jedynie kilk h utworzenia u klas.

2. Zastosuj proste zasady projektowania obiektowego, by poprawić elastyczność oprogramowania. 3. Staraj się, by projekt oprogramowania zapewniał łatwość jego utrzymania i pozwalał na wielokrotne stosowanie. 302

Rozdział 6.

Rozwiązywanie naprawdę dużych problemów

Wszystko zależy od sposobu spojrzenia na duży problem Zastanów się nad sposobem rozwiązywania poważnych problemów podczas prac nad dużymi aplikacjami. Zazwyczaj analizujemy aplikację na wyższym poziomie abstrakcji, a następnie przystępujemy do pracy nad niewielkim fragmentem jej funkcjonalności.

Najlepszym sposobem analizowania dużego problemu jest postrzeganie go jako wielu małych elementów funkcjonalności. Każdy z tych elementów można następnie potraktować jako pojedynczy problem, który należy rozwiązać, wykorzystując przy tym poznane już zasady. Kiedy już jakiś fragment aplikacji zacznie działać zgodnie z oczekiwaniami, można przejść do następnego fragmentu. Jednak podczas prac nad każdym z takich etapów należy stosować te same podstawowe zasady projektowania obiektowego, które prezentowaliśmy na poprzednich 250 stronach tej książki.

ści DUŻY W rzeczywisto jedynie st je PROBLEM szych zbiorem mniejnkcjonalności, fu w tó en em el y z których każdmniejszy reprezentuje problem.

Duże problemy możesz rozwiązywać, dzieląc je na wiele małych fragmentów funkcjonalności, a następnie pracując nad każdym z nich z osobna.

Niewielki problem

Niewielki problem

Niewielki problem

Niewielki problem

Niewielki problem

Duży problem jesteś tutaj  303

Rozwiązywanie dużych problemów

To, o czym już wiesz… Zdobyłeś już sporo wiadomości, które pomogą Ci w rozwiązywaniu dużych problemów programistycznych… choć być może jeszcze sobie tego nie uświadomiłeś. Przyjrzyjmy się szybko niektórym informacjom dotyczącym pisania wspaniałych (i dużych) aplikacji, które już posiadasz:

Zastosowan także podc ie hermetyzacji pomag za problemów. s rozwiązywania du a hermetyzac Im wyższy będzie st żych podzielić apji, tym łatwiej Ci będzopień funkcjonalno likację na różne frag ie menty ści.

Poprzez hermetyzację tych fragmentów aplikacji, które mogą się zmieniać, poprawiasz jej elastyczność i ułatwiasz wprowadzanie zmian. Dzięki stosowaniu interfejsów, a nie Najlepszym sposobem

implementacji,

uzyskania dobrych wymagań jest zrozumienie, co system powinien robić.

rozbudowa Twojego Jeśli będziesz wi jakie zadania po edział, wi realizować każdy nien fragment funkcjo niewielki na aplikacji, to nie lności mieć problemów powinieneś z połączeniem ich w dużą aplikację, która bę wykonywać to, cz dzie ego oczekujemy.

oprogramowania będzie łatwiejsza.

a jeszcze większego To stwierdzenie nabier tworzenia dku ypa prz w znaczenia rzez wykorzystanie dużych aplikacji. Pop powiązania asz jsz nie interfejsów zm fragmentami mi lny egó pomiędzy poszcz pamiętasz, ne ew aplikacji… a, jak zap sze są dobre. zaw a” ani iąz pow źne „lu

304

Rozdział 6.

Rozwiązywanie naprawdę dużych problemów

Analiza pomaga Ci

ierdzenie nie Bez wątpienia to stw wielkości od ie eżn zal zmienia się tości im wyższy aplikacji. W rzeczywis aplikacji, tym ści jno jest stopień spó poszczególne bardziej niezależne są lności i tym ona kcj fun jej y ent gm fra każdym z nich łatwiej pracować nad ch. ały ost poz od ie niezależn

Doskonałe oprogramowanie można łatwo zmieniać

upewnić się, że system Analiza nabiera jeszcze większego znaczenia podczas prac nad dużymi aplikacjami… a zazwyczaj będziesz zaczynać od analizowania poszczególnych fragmentów funkcjonalności, by następnie przejść do analizy interakcji pomiędzy tymi fragmentami.

będzie działać w rzeczywistym kontekście.

i rozbudowywać, i zawsze robi ono to, czego chce klient.

Masz duży problem? Użyj kilku z tych małych zasad i zadzwoń do mnie jutro rano. Założę się, że błyskawicznie nad wszystkim zapanujesz.

A zatem spróbujmy rozwiązać DUŻY problem! Dosyć już tych rozważań o tym, czego się już nauczyłeś; zobaczmy, jak zdołasz wykorzystać tę wiedzę do zaprojektowania całkowicie nowego i naprawdę dużego oprogramowania. Przewróć kartkę, by dowiedzieć się czegoś na temat Gerarda, jego nowej firmy tworzącej gry komputerowe oraz nowego dużego projektu.

jesteś tutaj  305

Prezentacja gry Gerarda

*U \*HUDUGD 3UH]HQWDFMDSRP\VïX Oto poważny problem, którym będziemy się zajmowali w tym i kilku następnych rozdziałach.

do Firma Gry Gerarda tworzy szkielety, używane przez projektantów gier etapami. opracowywania gier strategicznych, w których rozgrywka jest prowadzona graczy W odróżnieniu od arkadowych strzelanek oraz gier, które mają przyciągnąć się dzięki atrakcyjnej szacie graficznej i efektom dźwiękowym, nasze gry będą pni opierać na technicznych szczegółach strategii i taktyki. Nasz szkielet udostę uchroni wszelkie narzędzia niezbędne do stworzenia konkretnej gry i jednocześnie programistę od kodowania powtarzających się czynności. framework, Szkielet ten, nazwany przez nas GSF (od angielskich słów: game system Gry szkielet systemu gier), będzie stanowić podstawę wszystkich prac firmy ym Gerarda. Przybierze on postać biblioteki klas o precyzyjnie zdefiniowan kich interfejsie programowania (API), który powinien być przydatny dla wszyst zespołów zajmujących się tworzeniem gier planszowych. Szkielet będzie udostępniać standardowe możliwości funkcjonalne związane z: X

definiowaniem i reprezentowaniem konfiguracji planszy do gry; jednostek wojskowych, konfigurowaniem armii oraz wszelkich innych jednostek biorących udział w rozgrywce;

X definiowaniem

X przesuwaniem X określaniem

jednostek na planszy;

poprawności ruchów;

X prowadzeniem

bitew;

X dostarczaniem

informacji na temat jednostek.

owych Nasz szkielet powinien uprościć proces tworzenia strategicznych gier plansz cić swój rozgrywanych etapami, tak by korzystające z niego osoby mogły poświę czas na implementację samej gry.

306

Rozdział 6.

Rozwiązywanie naprawdę dużych problemów

Nie interesują mnie żadne wymyślne, błyskające, wybuchające gry w stylu Gwiezdnych Wojen… Chcę czegoś z prawdziwą strategią, czegoś, co zmusi Cię do myślenia! Wciągająca gra wojenna rozgrywana etapami — o to mi chodzi.

da Oto Gerard. Wygląie, jednak naprawdę poważnm pasjonatem jest niepoprawny h. gier strategicznyc

Zaostrz ołówek Co powinniśmy zrobić w pierwszej kolejności? Poniżej wymieniliśmy kilka czynności, od których mógłbyś rozpocząć wstępne prace dla firmy Gerarda. Zaznacz prostokąciki przy tych czynnościach, od których uważasz, że należałoby zacząć.

Pogadać z Gerardem.

Zgromadzić wymagania.

Rozpocząć rysowanie diagramu klas.

Pogadać z osobami, które najprawdopodobniej będą korzystać ze szkieletu.

Napisać przypadek użycia.

Rozpocząć tworzenie diagramu pakietu.

jesteś tutaj  307

Co powinieneś zrobić w pierwszej kolejności?

Hej, to jest naprawdę proste pytanie. My zaczynamy od zapisania wymagań i przypadków użycia, podobnie jak zrobiliśmy pisząc system do obsługi drzwiczek dla firmy PsieOdrzwia.

Wymagania i przypadki użycia to dobry punkt wyjściowy… Rozpoczęcie prac nad systemem od stworzenia listy wymagań oraz zapisania przypadków użycia to naprawdę rewelacyjny pomysł. Dzięki temu możesz określić, co system powinien robić, a następnie, krok po kroku, dodawać kolejne funkcjonalności zapisane na liście… rozwiązując wiele małych problemów, by rozwikłać jeden naprawdę duży problem.

Oto jeden z programistów należących do naszego zespołu.

Jednak nie jestem do końca przekonany, czy mamy wystarczająco dużo informacji, by napisać listę wymagań lub stworzyć przypadki użycia… Wszystko, co na razie mamy, to ta śmieszna prezentacja pomysłu. Jednak ona nie mówi nam wiele o tym, co tak naprawdę system powinien robić.

…ale co tak naprawdę wiemy o systemie? Prezentacja pomysłu zawiera całkiem sporo informacji o tym, czego chce Gerard, zostawia jednak otwarte pole do interpretacji. O jakiej planszy Gerard myślał? Kim tak naprawdę są osoby, które będą z niego korzystać? Gracze czy projektanci gier? W końcu, czy gry będą miały raczej charakter historyczny, czy musimy się przygotować na lasery i statki kosmiczne? Wygląda na to, że musimy się jeszcze sporo dowiedzieć, nim będziemy mogli przystąpić do tworzenia naprawdę dobrej listy wymagań.

308

Rozdział 6.

Rozwiązywanie naprawdę dużych problemów

Potrzebujemy znacznie więcej informacji Wszystkie informacje, jakimi dysponujemy, rozpoczynając prace nad szkieletem, pochodzą z prezentacji pomysłu… jednak niewiele nam one dały. A zatem musimy w jakiś sposób określić, co system powinien robić. Ale jak możemy zdobyć te informacje? stwa… ane podobień To są tak zw e do tworzonego bn do po Co jest systemu?

Co przypomina system? Jednym ze sposobów pozwalających na zdobycie dodatkowych informacji o systemie jest określenie, co system przywodzi na myśl. Innymi słowy, musisz odpowiedzieć sobie na pytanie, czy znasz coś, co by przypominało funkcjonowanie lub zachowanie systemu?

To nazywamy różnicą… rzeczy są różne od sie Jakie bie?

Czego system nie przypomina ? Innym doskonałym sposobem określenia, co system powinien robić, jest przeanalizowanie, czego on nie przypomina. Takie porównanie pomaga określić, czym nie musimy się przejmować w tworzonym systemie.

A zatem posłuchajmy, o czym mówiono na jednym ze spotkań w firmie Gerarda, i przekonajmy się, jakie informacje możemy zdobyć… jesteś tutaj  309

Słuchanie klienta

Rozmowa klientów Zanim zaczniemy prace nad szkieletem systemu gier, który mamy napisać, musimy zapoznać się, przynajmniej pobieżnie, z planami co do niego, jakie ma Gerard oraz jego zespół.

Czy pamiętacie tę starą grę komputerową — Zork? Wszyscy ją uwielbiali, chociaż działała w trybie tekstowym. Już znaleźliś m podobieństwa! y pewne ma interfejs System gr przypominając aficzny y grę Zork.

Robert z marketingu.

Beata z działu projektowego.

Zuzanna i Tomasz z działu sprzedaży.

7RPDV] No tak… Gerard uwielbia gry z interfejsem tekstowym. A ludzie powoli zaczynają być znużeni tymi grami z wymyślną grafiką, takimi jak Gwiezdne Wojny Epizod 206 (lub jakiś inny — nawet nie wiem, jaki numer jest aktualnie w sprzedaży). Jeśli mamy obsługiwać te wszystkie wersje gier, to kluczową cechą naszego szkieletu GSF będzie musiała być elastyczność.

%HDWD Dodatkowo będziemy potrzebowali mnóstwo różnych okresów. Moglibyśmy wypuścić wersję z czasów wojny secesyjnej z bitwami pod Antietam i Vicksburgiem, wersję z czasów I wojny światowej w Europie… Założę się, że graczom bardzo się spodoba to całe historyczne uzbrojenie. =X]DQQD Dobry pomysł, Beato! Założę się, że będziemy także mogli zlecić projektantom gry wykonanie dodatkowych pakietów, tak by gracz mógł kupić grę symulującą czasy II wojny światowej, w której dowodzi siłami sprzymierzonych, a następnie by mógł dokupić dodatek pozwalający mu grać siłami przeciwnika. 5REHUW To jest także doskonały chwyt marketingowy… Jeśli nasz system będzie obsługiwać różne okresy historyczne, różne rodzaje jednostek i uzbrojenia, różne ofensywy i bitwy, to będziemy go mogli sprzedawać prawie wszystkim firmom piszącym gry. %HDWD Czy uważacie, że powinniśmy zajmować się czymś innym niż jedynie bitwy historyczne? Chodzi mi o to, że moglibyśmy sprzedawać nasz system także gościom piszącym te śmieszne gry ze statkami kosmicznymi, a oni mogliby go używać do bitew science-fiction.

310

Rozdział 6.

A oto i różnice. System nie jest grą o bogatym i zaawansowanym interfejsie graficznym.

Rozwiązywanie naprawdę dużych problemów 7RPDV] Hmmm… Założę się, że Gerardowi spodoba się ten pomysł. Oczywiście zakładając, że tacy goście jeszcze w ogóle piszą gry, w których rozgrywka jest prowadzona etapami. Ale dlaczego nie moglibyśmy zaistnieć także na tym rynku, a nie tylko na rynku gier historycznych? 5REHUW Czy sądzisz, że moglibyśmy sprzedawać to jako system do pisania wszelkich możliwych gier, zaczynając do internetowej wersji gry Risk, a na nowoczesnej wersji Stratego kończąc? Obie te lkie gry zrobiły kiedyś furorę… byłoby super sprzedawać nasz system ludziom, którzy piszą takie gry. wie nie e Kolejn podobieństwo… a zatem naprawdę chodzi nam o gry wojenne rozgrywane etapami.

%HDWD A zatem omówmy szczegóły. Wiemy, że musimy sprzedać nasz system jak największej liczbie projektantów gier, a zatem musi on być naprawdę elastyczny. Sądzę, że możemy zacząć od zwyczajnej prostokątnej planszy, na której będziemy ustawiali kwadratowe pionki. No dobrze, w końcu

7RPDV] Możemy pozwolić projektantom określać, ile pionków znajdzie się na planszy, prawda? zaczynamy poznawać Będą mogli wybierać wysokość i szerokość albo jakieś inne parametry? ysły %HDWD Tak… a oprócz tego musimy obsługiwać wszelkie możliwe rodzaje terenu: góry, rzeki, równiny, pastwiska… =X]DQQD …może jeszcze przestrzeń kosmiczną, kratery lub asteroidy, albo coś podobnego — z myślą o twórcach gier kosmicznych…

pom dotyczące faktycznych cech i możliwości systemu.

5REHUW A może nawet podwodny świat, na przykład wodorosty lub muł. Co wy na to? %HDWD Doskonałe pomysły! A zatem będziemy potrzebować podstawowego pola planszy, które można dowolnie dostosowywać do naszych potrzeb i rozszerzać; z kolei plansza będzie mogła składać się z pól dowolnego rodzaju. =X]DQQD Czy musimy przejmować się wszystkimi sprawami związanymi z regułami poprawności ruchów pionków na planszy oraz innymi podobnymi zasadami, które zazwyczaj w takich grach obowiązują?

Znowu wzmianka o grach strategicznych… zdecydowanie musimy tu uważać na podobieństwa z grami tego typu.

Czy zanotowaliście i zrozumieliście wszystko, o czym była mowa? Czy możecie już zacząć pracę nad moim nowym systemem do tworzenia gier?

7RPDV] Sądzę, że powinniśmy, prawda? Przecież zazwyczaj gry strategiczne określają złożone reguły poruszania pionków, coś w stylu: ta jednostka może przesunąć się tylko o tyle i tyle pól, bo dźwiga zbyt ciężki ekwipunek. %HDWD Uważam, że przeważająca część zasad określających poruszanie pionków po planszy zależy od konkretnej gry. Według mnie powinniśmy to zostawić projektantom gry, którzy będą używali naszego szkieletu. Wszystko, co nasz szkielet powinien robić, to kontrolować, na którego gracza przypada teraz kolej ruchu, i udostępniać podstawowe czynności związane z obsługą przesuwania pionków. =X]DQQD Tak, to dobry pomysł. Możemy napisać szkielet do tworzenia ambitnych, interesujących gier strategicznych, a przy okazji dobrze na nim zarobić. 5REHUW To wszystko zaczyna wyglądać naprawdę interesująco. Chodźmy z tym wszystkim do Gerarda i do wynajętych przez niego programistów. Opowiemy im wszystko, by mogli zacząć działać.

jesteś tutaj 

311

Informacje, cechy i wymagania

Określanie możliwości Dowiedziałeś się już całkiem sporo o oczekiwaniach Gerarda i jego zespołu dotyczących możliwości tworzonego systemu. A zatem zbierzmy te informacje i na ich podstawie określmy możliwości systemu. system powinien Beata stwierdziła, że . To jest właśnie esy okr ne róż ć iwa obsług jektowanego jedna z możliwości pro gier. nia rze two szkieletu do

%HDWD Dodatkowo będziemy potrzebowali różnych okresów. Moglibyśmy wypuścić wersję z czasów wojny secesyjnej z bitwami pod Antietam i Vicksburgiem, wersję z czasów I wojny światowej w Europie… Założę się, że graczom bardzo się spodoba to całe historyczne uzbrojenie.

Oto kolejna możliwość : różnych rodzajów terenuudostępnianie możliwość przyczyni się . Już ta jedna kilku różnych wymagań do powstania .

%HDWD Tak… a oprócz tego musimy obsługiwać wszelkie możliwe rodzaje terenu: góry, rzeki, równiny, pastwiska… =X]DQQD …może jeszcze przestrzeń kosmiczną, kratery lub asteroidy, albo coś podobnego — z myślą o twórcach gier kosmicznych… 5REHUW A może nawet podwodny świat, na przykład: wodorosty lub muł. Co wy na to?

Ale czym są w ogóle te „możliwości”? Otóż możliwość to podany na wysokim poziomie abstrakcji opis czegoś, co system jest w stanie wykonać. Zazwyczaj możliwości określa się na podstawie rozmów z klientami (lub podczas przysłuchiwania się ich dyskusjom, tak jak robiliśmy to na kilku ostatnich stronach). Stosunkowo często analiza jednej możliwości tworzonego oprogramowania pozwala określić kilka różnych wymagań, które da się zastosować, by zrealizować daną możliwość. A zatem określanie możliwości jest doskonałym sposobem rozpoczęcia tworzenia listy wymagań.

312

Rozdział 6.

W dużych projektach — takich jak system do obsługi gier Gerarda — rozpoczynanie pracy od określen możliwości oprogramowania jest ia bardzo pomocne, gdyż jeszcze nie znamy tysięcy detali, a potrzebu jemy jakiegoś punktu zaczepienia, od którego moglibyśmy zacząć prac ę.

Rozwiązywanie naprawdę dużych problemów

Możliwości (od klienta)

Wymagania (do programisty)

Pole planszy jest skojarzone z typem terenu. Obsługa różnych rodzajów terenu. Oto jedna możliwość iliśmy, systemu, którą określ . nta klie jąc słucha

Projektanci gry mogą tworzyć własne typy terenu.

Jedna możliwość może nas zmuszać do spełnienia większej ilości wymagań.

Każdy typ terenu ma swoje charakterystyki, które mają wpływ na poruszanie się jednostek.

Określ możliwości na podstawie informacji podawanych przez klienta, następnie na ich podstawie określ wymagania potrzebne do zaimplementowania tych możliwości.

Zaostrz ołówek Potrzebujemy listy możliwości systemu do tworzenia gier Gerarda. Gerard oraz jego zespół dostarczyli Ci już wielu informacji, a obecnie wiesz już, w jaki sposób można przekształcić je w zbiór możliwości. Twoje zadanie polega na zapisaniu, w umieszczonych poniżej pustych liniach, niektórych spośród możliwości, jakimi, według Ciebie, powinien dysponować szkielet systemu gier.

jesteś tutaj  313

Możliwość czy wymaganie?

Zaostrz ołówek

Rozwiązanie

Potrzebujemy listy możliwości systemu do tworzenia gier Gerarda.

Gerard oraz jego zespół dostarczyli Ci już wielu informacji, a obecnie wiesz już, w jaki sposób można przekształcić je w zbiór możliwości. Twoje zadanie polega na zapisaniu, w umieszczonych poniżej pustych liniach, niektórych spośród możliwości, jakimi, według Ciebie, powinien dysponować szkielet systemu gier. Obsługa różnych typów terenu.

Obsługa różnych okresów, w tym także fikcyjnych, do gier science-fiction.

Obsługa wielu różnych rodzajów wojsk lub jednostek, charakterystycznych dla konkretnej gry.

Obsługa modułów dodatkowych, zawierających nowe kampanie lub scenariusze bitew.

Każda gra posiada planszę, składającą się z kwadratowych pól, a każde z tych pól ma określony typ terenu.

Szkielet śledzi, który z graczy powinien w danej chwili wykonać ruch, i udostępnia narzędzia do obsługi przesuwania pionków po planszy.

To wszystko wydaje się takie dowolne… niektóre z tych możliwości wyglądają zupełnie jak wymagania. Jaka jest różnica pomiędzy nazywaniem czegoś „możliwością" a „wymaganiem"?

Nie przejmuj się, jeś li możliwości nie odpow nasze iad tym, które samemu zap ają dokładnie jeśli swoje możliwośc isałeś, bądź i znacznie bardziej szc zanotowałeś zegółowo. To są po prostu nasze spostr zeżenia.

Nie popadaj w obsesję na punkcie „różnicy” pomiędzy możliwościami i wymaganiami. Wiele osób używa terminu „możliwości” do określania przeróżnych rzeczy, a zatem nie jest to termin, do którego znaczenia powinieneś przywiązywać dużą uwagę. Dla niektórych osób możliwość jest wymaganiem; a czasami można nawet usłyszeć sformułowanie „wymaganie możliwości” — to dopiero wywołuje zamęt w głowie. Inne osoby traktują jednak możliwości jako coś, co jest na wyższym poziomie niż wymagania. Właśnie w taki sposób prezentowaliśmy możliwości w tym rozdziale. A zatem udostępnienie jednej możliwości niekiedy wymaga spełnienia kilku wymagań. Najważniejsze jest jednak to, że jeśli utkniesz na samym początku pracy, zwłaszcza nad dużym projektem, to możesz spisać możliwości (lub wymagania!), aby zdobyć informacje o stosunkowo ogólnych zagadnieniach, jakimi będziesz musiał zająć się podczas tworzenia systemu.

314

Rozdział 6.

Rozwiązywanie naprawdę dużych problemów Nie istnieją

głupie pytania

P: Jaka jest zatem różnica

P: Napisaliście: „niektóre osoby”.

pomiędzy możliwościami i wymaganiami?

A zatem są także inne sposoby rozumienia możliwości i wymagań?

O: Cóż, to zależy od tego, kogo zapytasz. O: Owszem. Jest także wiele osób, które Dla niektórych możliwości są „dużymi rzeczami”, jakie system jest w stanie wykonywać, takimi jak „obsługa wszystkich możliwych rodzajów terenu”. Jednak aby uznać, że system je realizuje, musi wykonywać wiele „małych” zadań, takich jak „definiowanie podstawowego typu terenu”, „zagwarantowanie programistom możliwości rozbudowy podstawowego typu terenu”, czy też „zapewnienie możliwości, by z każdym polem planszy dało się skojarzyć dowolny teren”. Wszystkie te mniejsze zadania są uważane za wymagania. A zatem udostępnienie jednej możliwości może wymagać spełnienia kilku wymagań, jak w poniższym przykładzie:

nie stosują takiego rozróżnienia pomiędzy możliwościami i wymaganiami. W takim przypadku jedną z możliwości mogłaby być: „obsługa różnych okresów” (co jest całkiem rozległym zagadnieniem), a inną: „zapewnienie możliwości, by jednym z typów terenu była woda” (co jest raczej zagadnieniem bardzo szczegółowym). Przy takim podejściu nie ma raczej zbyt dużych różnic pomiędzy możliwościami i wymaganiami. A zatem osoby, które mają takie podejście, będą postrzegać wymagania i możliwości raczej w następujący sposób:

Możliwość

Możliwość

Wymagania

czami”, Cechy są „dużymi rze zakłada e eni pni stę których udo magań. zaspokojenie wielu wy

P: Kto ma zatem rację? O: Wszyscy! Albo nikt, jeśli tak wolisz.

Nie ma żadnego „jedynie słusznego” podejścia do tego, czym są możliwości i wymagania; zwłaszcza jeśli nie chcesz tracić zbyt dużo czasu na daremne spory z kolegami z zespołu. Najlepiej jest zapomnieć o całym problemie i uznać, że zarówno możliwości, jak i wymagania określają, co system musi robić. Jeśli chcesz uznać, że możliwości „to duże rzeczy”, a wymagania — „mniejsze zagadnienia”, to takie podejście także będzie uprawnione. Pamiętaj tylko, byś nie wdał się w żadną bójkę w barze z powodu odmiennej interpretacji.

Czy nie możemy przestać się tym przejmować?

Wymagania Przy takim podejściu pojęcia możliwości i wymagań „zachodzą” na siebie — oba te pojęcia są, w mniejszym lub większym stopniu, zamienne.

jesteś tutaj  315

Nie ma wartości, nie ma przypadków użycia

No dobrze, zatem załatwiliśmy sprawę możliwości i wymagań. A teraz możemy się zająć pisaniem przypadków użycia, prawda?

Przypadki użycia nie zawsze pomagają ujrzeć ogólny obraz tworzonego oprogramowania. Kiedy zaczynasz pisać przypadki użycia, automatycznie zagłębiasz się w szczegóły związane ze sposobem działania systemu oraz z tym, co on powinien robić. Problem jednak polega na tym, że zajmując się szczegółami, można stracić z oczu ogólną postać rozwiązania. Na obecnym etapie prac nad systemem gier dla Gerarda nie jesteśmy jeszcze gotowi na to, by zajmować się mnogością szczegółów… Na razie próbujemy określić, czym w ogóle ma być tworzony szkielet. A zatem, choć mógłbyś zacząć spisywać przypadki użycia, to najprawdopodobniej nie pomogłoby Ci to w zorientowaniu się, co tak naprawdę mamy stworzyć, i w określeniu ogólnej postaci rozwiązania. Pracując nad systemem, najlepiej jest możliwie długo odwlekać moment, w którym zajmiemy się szczegółami… W ten sposób nie będziesz zaprzątać sobie głowy szczegółami — „małymi rzeczami”, kiedy powinieneś zajmować się zagadnieniami ogólnymi, czyli „dużymi rzeczami”.

Zawsze staraj się jak najdłużej odwlekać moment, gdy zaczniesz zajmować się szczegółami. 316

Rozdział 6.

WYSIL

SZARE KOMÓRKI Jeśli zaczęlibyśmy pisać przypadki użycia do systemu gier Gerarda, to kto występowałby w nich jako aktorzy?

Rozwiązywanie naprawdę dużych problemów

Czym zatem mamy się teraz zająć? Przez ponad 200 stron tłumaczyliście mi, że powinienem wiedzieć, co tworzony system ma robić, a teraz nagle okazuje się, że przypadki użycia to nie jest najlepszy pomysł. Co mi więc pozostaje?

Wciąż musisz określić, co system powinien robić… jednak potrzebujesz WIDOKU OGÓLNEGO. Chociaż przypadki użycia mogą być zbyt szczegółowe jak na etap prac nad systemem, na jakim się obecnie znajdujesz, to jednak pomimo tego będziesz musiał zrozumieć, co system ma robić. A zatem będzie Ci potrzebny jakiś sposób, dzięki któremu skoncentrujesz się na ogólnym widoku systemu i zdołasz określić, co system powinien robić, a to wszystko bez wdawania się w niepotrzebne szczegóły.

Czy słyszałeś kiedyś, że jeden obraz jest wart więcej niż tysiąc słów? Przekonajmy się, czy będziemy w stanie pokazać, co system powinien robić.

jesteś tutaj  317

Od przypadków użycia do diagramów przypadków użycia

Diagramy przypadków użycia Czasami będziesz musiał wiedzieć, co system ma robić, lecz bez narażania się na wszystkie szczegóły, jakich wymaga pisanie przypadków użycia. Jeśli znajdziesz się w takiej sytuacji, to najprawdopodobniej optymalnym narzędziem, po jakie powinieneś sięgnąć, będą diagramy przypadków użycia:

stać Ta rysunkowa po ra. Aktor to ak je tu en ez repr ie, którym operuje na system u jest dk pa zy pr ym w nasz gier. nia ze or tw szkielet do

Ten duży pros system. To, cotokąt reprezentuje jest systemem jest wewnątrz niego, zewnątrz — , a to, co jest na uż A zatem sam ywa tego systemu. granicą systemprostokąt jest u.

Tworzenie gry

Modyfikacja istniejącej gry

Każdy z tych owali reprezentuje jeden z przypadków użycia systemu.

Projektant gry

Wdrożenie gry Pamiętaj, w tym systemie aktorem jest projektant gry, a nie gracz.

ewne zbyt w użycia nie jest zap Ten diagram przypadkó opisów działania systemu, niemniej szczegółowym zbiorem zystkie informacje o tym, co system ws w sposób prosty jednak przekazuje Ci więcej, odbywa się to założenia powinien robić, a co z Przypadki użycia i łatwy do zrozumienia.cej szczegółów i dlatego nie są tak wię ie czn zna ają ier temu jak dobry zaw ogólnego wyglądu sys pomocne w określaniu cia. diagram przypadków uży

318

Rozdział 6.

Rozwiązywanie naprawdę dużych problemów Ale to jest po prostu głupie. Co może nam dać taki diagram? Czy naprawdę musimy rysować takie rysunki, by domyślić się, że projektanci gier będą tworzyć lub modyfikować gry?

Diagramy przypadków użycia są planami systemu. Pamiętaj, że obecnie koncentrujemy się na ogólnej postaci systemu. Przedstawiony diagram może się wydawać nieco mało konkretny, jednak pomaga skoncentrować się na podstawowych zadaniach, jakie system musi realizować. Bez niego mogłoby się zdarzyć, że pochłonięty bez reszty szczegółami sposobu tworzenia gry całkowicie zapomniałbyś, iż projektant musi nie tylko stworzyć, lecz także wdrożyć swoją grę. Dzięki diagramowi przypadków użycia nigdy nie stracisz z oczu całości systemu.

A co z tymi wszystkimi możliwościami, które z takim trudem określaliśmy? Czy one nie zostaną w żaden sposób przedstawione na diagramie przypadków użycia?

Wykorzystaj listę możliwości systemu, by upewnić się, że diagram przypadków użycia jest kompletny. Kiedy już opracujesz listę możliwości i narysujesz diagram przypadków użycia, możesz upewnić się, czy projektowany system będzie robił wszystko, co powinien. Przeanalizuj diagram i sprawdź, czy wszystkie przypadki użycia obejmują wszystkie możliwości określone na podstawie informacji uzyskanych od klienta. Dzięki temu upewnisz się, że Twój diagram — będący planem całego systemu — jest kompletny, a Ty możesz w końcu przystąpić do tworzenia systemu.

jesteś tutaj  319

Dopasuj możliwości do diagramu przypadków użycia

Magnesiki możliwości 1DGV]HGâF]DVE\GRSDVRZDþPRİOLZRĤFLSURMHNWRZDQHJRV]NLHOHWXGRSU]\SDGNyZ Xİ\FLDXPLHV]F]RQ\FKQDGLDJUDPLH:LGRF]QHXGRâXVWURQ\PDJQHVLNLPRİOLZRĤFL XPLHĤþQDGLDJUDPLHSU]\SU]\SDGNXXİ\FLDNWyU\EĐG]LHUHDOL]RZDþGDQċ PRİOLZRĤþ-HĤOLGLDJUDPMHVWNRPSOHWQ\WRNDİG\]PDJQHVLNyZEĐG]LHV] ZVWDQLHXPLHĤFLþSU]\MDNLPĤSU]\SDGNXXİ\FLD3RZRG]HQLD

Tworzenie gry gram To jest nasz dia ia, plan przypadków użyc naszego systemu.

Modyfikacja istniejącej gry

Projektant gry

Wdrożenie gry To jest lista możliwości, które określiliśmy na stronie 314.

Szkielet systemu gier Gerarda Lista możliwości

6]NLHOHWREVïXJXMHUöĝQH URG]DMHWHUHQX

1. Szkielet obsługuje różne rodzaje terenu. 2. Szkielet obsługuje różne okresy, w tym także fikcyjne, takie jak science-fiction lub fantasy. 3. Szkielet obsługuje wiele różnych rodzajów jednostek wojskowych oraz typów uzbrojenia, charakterystycznych dla konkretnej gry. 4. Szkielet obsługuje moduły dodatkowe pozwalające na wzbogacanie gry o nowe kampanie lub scenariusze bitew. 5. Szkielet udostępnia planszę składającą się z kwadratowych pól, przy czym dla każdego z nich można określać odrębny rodzaj terenu. 6. Szkielet określa, na którego z graczy przypada w danej chwili kolejność wykonania ruchu, i koordynuje przesuwanie pionków po planszy.

6]NLHOHWREVïXJXMHUöĝQHRNUHV\ ZW\PWDNĝHILNF\MQHWDNLHMDN VFLHQFHILFWLRQOXEIDQWDV\ 6]NLHOHWREVïXJXMHZLHOHUöĝQ\FKURG]DMöZ MHGQRVWHNZRMVNRZ\FKRUD]W\SöZX]EURMHQLD FKDUDNWHU\VW\F]Q\FKGODNRQNUHWQHMJU\ 6]NLHOHWREVïXJXMHPRGXï\GRGDWNRZH SR]ZDODMÈFHQDZ]ERJDFDQLHJU\RQRZH NDPSDQLHOXEVFHQDULXV]HELWHZ 6]NLHOHWXGRVWÚSQLDSODQV]ÚVNïDGDMÈFÈVLÚ ]NZDGUDWRZ\FKSöOSU]\F]\PGODNDĝGHJR ]QLFKPRĝQDRNUHĂODÊRGUÚEQ\URG]DMWHUHQX

6]NLHOHWRNUHĂODQDNWöUHJR]JUDF]\SU]\SDGD ZGDQHMFKZLOLNROHMQRĂÊZ\NRQDQLDUXFKX LNRRUG\QXMHSU]HVXZDQLHSLRQNöZSRSODQV]\

320

Rozdział 6.

Każdą z możliwości powinieneś umieścić przy jednym z przypadków użycia.

Magnesiki z możliwościami.

Rozwiązywanie naprawdę dużych problemów Nie istnieją

głupie pytania

P: A zatem aktorem jest osoba używająca systemu? O: W rzeczywistości aktorem może być dowolna osoba lub

rzecz (okazuje się, że to nie musi być osoba) znajdująca się poza systemem i prowadząca z nim jakąś interakcję. A zatem, w przypadku bankomatu, aktorem niewątpliwie byłaby osoba używająca systemu, jednak innym aktorem mógłby być także bank, gdyż to w nim przechowywane są pieniądze, które bankomat wydaje. Jeśli jakaś osoba lub rzecz nie należy do systemu, lecz używa go lub operuje na nim, to jest ona aktorem.

P: Czym jest ten prostokąt, wewnątrz którego zostały umieszczone wszystkie przypadki użycia? I dlaczego aktorzy zostali ulokowani poza nim?

O: Ten prostokąt wyznacza granice systemu. A zatem będziesz

musiał zaprogramować wszystkie operacje umieszczone wewnątrz tego prostokąta, lecz nie musisz przejmować się niczym, co jest poza nim. Aktorzy — projektanci gier korzystający z naszego szkieletu — są umieszczeni poza prostokątem, gdyż używają systemu, a nie są jego częścią.

P: A każdy owal oznacza przypadek użycia? O: Tak. I właśnie dlatego diagramy przypadków użycia doskonale

nadają się do przedstawiania ogólnej postaci systemu: pokazują wiele różnych przypadków użycia i sposób, w jaki współpracują one ze sobą w celu zrealizowania naprawdę dużych zadań. Poza tym dzięki diagramom przypadków użycia łatwiej jest nam uniknąć zbyt wczesnego zagłębienia się w szczegóły związane z konkretnym wymaganiem (na przykład w takiej sytuacji, w jakiej znajdujemy się obecnie — kiedy powinniśmy koncentrować się na ogólnym projekcie systemu).

P: Widziałem diagramy przypadków użycia, na których przy strzałkach znajdowały się oznaczenia postaci: oraz . Co to takiego?

O: Język UML oraz diagramy przypadków użycia definiują sposób informowania o rodzaju związków pomiędzy poszczególnymi przypadkami użycia. A zatem można powiedzieć, że jeden przypadek użycia zawiera inny, bądź też że jakiś przypadek użycia rozszerza inny. Właśnie to oznaczają odpowiednio etykiety oraz .

Jednak można spędzić wiele czasu, spierając się o to, czy pewien przypadek użycia rozszerza inny, czy też go zawiera. I nagle może się okazać, że zamiast koncentrować się na ogólnej postaci systemu, zastanawiamy się nad tym, w jaki sposób pole planszy może określać rodzaj terenu, bądź czy pewien typ jednostek można wyposażyć w plecaki, czy nie. Oczywiście możesz używać etykiet i , jednak nie mają one kluczowego znaczenia, a ich stosowanie nigdy nie powinno odciągnąć Twej uwagi od podstawowego procesu projektowania.

P: A zatem diagramy przypadków użycia są raczej związane z ogólną postacią systemu i nie chodzi w nich o podawanie dużej liczby szczegółów?

O: Właśnie, w końcu to zrozumiałeś! Jeśli zbyt długo zastanawiasz

się, jaką nazwę nadać przypadkowi użycia, bądź jakiego rodzaju związki występują pomiędzy poszczególnymi przypadkami, to zapewne będzie to oznaczać, że straciłeś ogólny obraz systemu. Używaj diagramów przypadków użycia, by spojrzeć na swój system z wysokości 10 kilometrów, nie niższej!

Dzięki za te wszystkie informacje, ale czy możemy wrócić do naszego ćwiczenia z magnesikami? Utknąłem, próbując dopasować jeden z magnesików do przypadku użycia…

jesteś tutaj  321

Mamy problem z jedną z możliwości

No dobrze… ty zadanie będz m razem ie „rozwiązane pr awie do końc a”.

Magnesiki możliwości. Rozwiązania 1DGV]HGâF]DVE\GRSDVRZDþPRİOLZRĤFLSURMHNWRZDQHJRV]NLHOHWXGRSU]\SDGNyZ Xİ\FLDXPLHV]F]RQ\FKQDGLDJUDPLH&]\E\âHĤZVWDQLHGRSDVRZDþSU]\SDGNL Xİ\FLDGRZV]\VWNLFKPDJQHVLNyZ]PRİOLZRĤFLDPL" Niema

l ws możliwości zystkie z tworzen są związane iem nowe j gry.

UDF]\ SU]\SDGD MHUöĝQH  QDNWöUHJR]J 6]NLHOHWREVïXJX 6]NLHOHWRNUHĂOD DQLDUXFKX RQ \N Z ĂÊ  QR QX HM UH RO URG]DMH WH Z GDQHM FKZLOLN SR SODQV]\ HVXZDQLHSLRQNöZ 6]NLHOHWREVïXJXMHUöĝQHRNUHV\ L NRRUG\QXMHSU] ZW\PWDNĝHILNF\MQHWDNLHMDN VFLHQFHILFWLRQ OXEIDQWDV\

6]NLHOHWREVïXJXMHZLHOHUöĝQ\FKURG]DMöZ MHGQRVWHNZRMVNRZ\FKRUD]W\SöZ X]EURMHQLD FKDUDNWHU\VW\F]Q\FKGOD NRQNUHWQHMJU\

6]NLHOHWREVïXJXMHPRGXï\GRGDWNRZH SR]ZDODMÈFHQDZ]ERJDFDQLHJU\RQRZH NDPSDQLHOXEVFHQDULXV]HELWHZ

Wdrażanie gry jes elementem systemt istotnym nie wspominał jawu, choć klient możliwościach be nie o żadnych zpośrednio z nim związanych.

Jednak wciąż pozostaje nam do umieszczenia jedna możliwość… co możemy z nią zrobić? Przypuszczamy, że miałeś problemy z umieszczeniem na diagramie przypadków użycia jednej możliwości. Zastanów się nad nią dokładnie: w rzeczywistości to nie jest coś, z czym projektant prowadzi bezpośrednią interakcję lub na co zwraca uwagę. Wynika to z faktu, iż o tę możliwość zadbaliśmy już wcześniej. Jaki jest zatem związek tej możliwości z tworzonym systemem? Jacy aktorzy prowadzą z nią interakcję? I czy na naszym diagramie nie brakuje jakichś przypadków użycia?

-DNVąG]LV]" 322

Rozdział 6.

Większość możliwości dałoby się także umieścić przy przypadku użycia „Modyfikacja istniejącej gry”, gdyż mogą być stosowane także podczas wprowadzania zmian w starszych grach.

?

6]NLHOHWRNUHĂODQDNWöUHJR]JUDF]\SU]\SDGD ZGDQHMFKZLOLNROHMQRĂÊZ\NRQDQLDUXFKX LNRRUG\QXMHSU]HVXZDQLHSLRQNöZSRSODQV]\

to jest Wiemy, że ale ć, ś o iw żl mo nie ma dlaczego jsca ie m ią na n planie? w naszym

Rozwiązywanie naprawdę dużych problemów

Mały aktor Małe sokratyczne ćwiczenie w stylu książki „The Little Lisper” Jaki system projektujesz?

Szkielet do tworzenia gier!

Zatem jaki jest jego cel?

Zapewnić projektantom możliwość tworzenia gier.

Zatem to projektant jest aktorem dla tego systemu?

Tak. Zaznaczyłem to na swoim diagramie przypadków użycia.

No, a do czego projektant używa tego szkieletu?

Do projektowania gier. Chyba już to ustaliliśmy!

A czy gra jest tym samym co szkielet?

No… nie. Wydaje mi się, że nie.

Dlaczego nie?

Gra to coś kompletnego, można w nią grać. A szkielet dostarcza jedynie fundamentu, na którym taką grę można stworzyć.

A zatem szkielet jest zestawem narzędzi, z których może korzystać projektant?

Nie, to coś więcej. Chodzi o to, że możliwość, z którą mam problemy, jest czymś, co szkielet obsługuje dla każdej tworzonej gry. A zatem szkielet nie jest jedynie zestawem narzędzi dla projektanta.

O, to ciekawe. Zatem szkielet jest częścią gry?

Hm… na to by wyglądało. Ale wydaje się, że na niższym poziomie, tak jakby szkielet dostarczał pewnych podstawowych usług, z których gra może korzystać. Można powiedzieć, że gra znajduje się nad szkieletem.

A zatem gra używa szkieletu?

Tak, dokładnie.

A więc okazuje się, że w rzeczywistości gra używa tworzonego systemu?

Tak, przed chwilą właśnie to powiedziałem. Ale zaraz… w takim razie…

…jeśli gra używa systemu, to co?

Aktor! Gra jest aktorem!

jesteś tutaj  323

Aktorzy nie zawsze są ludźmi

Aktorzy to także ludzie (no dobrze… nie zawsze) Okazuje się, że oprócz projektanta gier także sama gra jest aktorem używającym tworzonego przez nas szkieletu. Przekonajmy się, w jaki sposób możemy dodać nowego aktora do naszego diagramu przypadków użycia:

Tworzenie gry

Modyfikacja istniejącej gry

Utworzenie planszy

Dodaliśmy nowego aktora. Reprezentuje on samą grę (tworzoną przez projektanta, któ ry używa szkieletu).

Przesuwanie jednostek

Projektant gry

Wdrożenie gry

Oto kilka przykładów zadań, które gra może realizować korzystając ze szkieletu.

Gra

Dodawanie i usuwanie jednostek

To kolejne przypadki użycia, które nasz system będzie musiał realizować, byśmy mogli uznać go za kompletny.

Czy te nowe przypadki użycia rozwiązują już problem możliwości, dla której nie mogliśmy znaleźć miejsca na diagramie przypadków użycia?

6]NLHOHWRNUHĂODQDNWöUHJR]JUDF]\SU]\SDGD ZGDQHMFKZLOLNROHMQRĂÊZ\NRQDQLDUXFKX LNRRUG\QXMHSU]HVXZDQLHSLRQNöZSRSODQV]\

324

Rozdział 6.

Pamiętaj, aktorzy nie muszą być ludźmi… W tym przypadku interakcję z systemem prowadzi gra.

Rozwiązywanie naprawdę dużych problemów

Diagram przypadków użycia… sprawdzony! Rozmieszczone możliwości… sprawdzone! Po dodaniu nowego aktora możemy w końcu dopasować wszystkie możliwości do przypadków obsługi umieszczonych na diagramie. 6]NLHOHWREVïXJXMHUöĝQHRNUHV\ SDGDZW\PWDNĝHILNF\MQHWDNLHMDN ]\ SU \ F] UD J ]  QDNWöUHJR VFLHQFHILFWLRQ OXEIDQWDV\ 6]NLHOHWRNUHĂOD LDUXFKX ROHMQRĂÊZ\NRQDQ N OL ZL FK \ M V] DQ SO Z GDQH R S öZ QN HVXZDQLHSLR Oto nasz nowy aktor L NRRUG\QXMHSU] MHUöĝQH 6]NLHOHWREVïXJX — gra — który także, podczas rozgrywki, URG]DMH WHUHQX

Tworzenie gry

używa naszego szkieletu.

Utworzenie planszy

6]NLHOHWREVïXJXMHZLHOHUöĝQ\FKURG]DMöZ MHGQRVWHNZRMVNRZ\FKRUD]W\SöZ X]EURMHQ HOHWREVïXJXMHPRGXï\GRGDWNRZH 6]NL LD QRZH FKDUDNWHU\VW\F]Q\FKGOD NRQNUHWQHMJU\ SR]ZDODMÈFH QDZ]ERJDFDQLHJU\R HZ ELW XV]H QDUL NDPSDQLHOXEVFH

Modyfikacja istniejącej gry Projektant gry wości Większość możli ma związek r, z projektantem gie który korzysta ze szkieletu.

Wdrożenie gry

Przesuwanie jednostek

6]NLHOHWRNUHĂODQDNWöUHJR]JUDF]\SU]\SDGD ZGDQHMFKZLOLNROHMQRĂÊZ\NRQDQLDUXFKXL NRRUG\QXMHSU]HVXZDQLHSLRQNöZSRSODQV]\

Dodawanie i usuwanie jednostek

Zaostrz ołówek Ostatnia możliwość cały czas jest nieco zabawna…

Gra

Nowe przypadki użycia związane z grą pozwolą nam umieścić na diagramie możliwość, z którą wcześniej mieliśmy problem.

6]NLHOHW RNUHĂOD QD NWöUHJR]JUDF]\SU]\ SDGD ZGDQHMFKZLOL NROHMQ RĂÊZ\NRQDQLDUXFKX LNRRUG\QXMHSU]HVXZD QLHSLRQNöZ SR SODQV] \

Druga część tej możliwości, dotycząca przesuwania pionków, doskonale pasuje do przypadku użycia „Przesuwanie jednostek”… ale co z pilnowaniem, na którego gracza aktualnie przypada kolej ruchu? Można sądzić, że na naszym diagramie przypadków użycia wciąż czegoś brakuje. Twoim zadaniem jest znalezienie odpowiedzi na dwa pytania: 1. Kto jest aktorem związanym z możliwością „Szkielet określa, na którego z graczy przypada w danej chwili kolejność wykonania ruchu”? 2. Jaki przypadek użycia dodałbyś do diagramu, by obsłużyć tę częściową możliwość?

* DODATKOWE PUNKTY: Wprowadź te zmiany w diagramie przedstawionym powyżej.

jesteś tutaj  325

Uzupełnianie diagramu przypadków użycia

Zaostrz ołówek

6]NLHOHWRNUHĂOD  QDNWöUHJR]J UDF]\ SU]\SDGD Z GDQHM FKZLOLN ROHMQRĂÊZ\NRQDQ LDUXFKX L NRRUG\QXMHSU] HVXZDQLHSLRQNöZ SR SODQV]\

Rozwiązanie Ostatnia możliwość cały czas jest nieco zabawna…

Druga część tej możliwości, dotycząca przesuwania pionków, doskonale pasuje do przypadku użycia „Przesuwanie jednostek”… ale co z pilnowaniem, na którego gracza aktualnie przypada kolej ruchu? Można sądzić, że na naszym diagramie przypadków użycia wciąż czegoś brakuje. Twoim zadaniem jest znalezienie odpowiedzi na dwa pytania: 1. Kto jest aktorem związanym z możliwością „Szkielet określa, na którego z graczy przypada w danej chwili kolejność wykonania ruchu”?

Także w tym przypadku aktorem jest sama gra, korzystająca ze szkieletu, by określać, na którego z graczy przypada teraz kolej ruchu. 2. Jaki przypadek użycia dodałbyś do diagramu, by obsłużyć tę częściową możliwość?

Potrzebujemy przypadku użycia o nazwie „Wykonaj ruch”, w którym szkielet będzie realizował proste zagadnienia związane z kolejnością wykonywania ruchów i pozwoli grom definiowanym przez projektanta obsługiwać szczegóły tego procesu. 6]NLHOHWREVïXJXMHPRGXï\GRGDWNRZH SR]ZDODMÈFHQD Z]ERJDFDQLHJU\ RQRZHNDPSDQLHOXEVFHQDULXV]HELWHZ

6]NLHOHWXGRVWÚSQLDSODQV]ÚVNïDGDMÈFÈVLÚ ]NZDGUDWRZ\FKSöOSU]\F]\PGODNDĝGHJR ]DMöZ öĝQ\FKURG MHZLHOHU JX ]QLFKPRĝQDRNUHĂODÊRGUÚEQ\URG]DMWHUHQX RMHQLD ïX EU EV X] R Z 6]NLHOHW RUD] W\Sö FK Z\ NR U\ MV ZR QNUHWQHMJ MHGQRVWHN \FK GOD NR ]Q \F VW U\ FKDUDNWH

Przypadek użycia „Zmiana kolejki” informuje nas o tym, że gra powinna obsługiwa wszystkie zagadnienia ć związane z obsługą kolejności ruchów poszczególnych graczy .

Utworzenie planszy

Tworzenie gry

6]NLHOHWREVïXJXMHUöĝQH URG]DMHWHUHQX 6]NLHOHWREVïXJXMHUöĝQHRNUHV\ ZW\PWDNĝHILNF\MQHWDNLHMDN VFLHQFHILFWLRQOXEIDQWDV\

6]NLHOHWRNUHĂODQDNWöUHJR ]JUDF]\SU]\SDGDZGDQHMFKZLOL NROHMQRĂÊZ\NRQDQLDUXFKX

Zmiana kolejki

Modyfikacja istniejącej gry

Przesuwanie jednostek

Projektant gry

6]NLHOHWNRRUG\QXMHSU]HVXZDQLH SLRQNöZSRSODQV]\

Wdrożenie gry

326

Rozdział 6.

Dodawanie i usuwanie jednostek

Gra Oprócz tego podzieliliśmy wcześniejszą możliwość na dwie mniejsze.

Rozwiązywanie naprawdę dużych problemów

A zatem, co właściwie zrobiliśmy?

Oto nasza lista możliwości… system musi wykonywać te operacje.

Przygotowałeś listę możliwości, które musi udostępniać szkielet systemu gier Gerarda i które jednocześnie określają wszystkie główne elementy tworzonego systemu. Ta lista możliwości w dużym stopniu przypomina listę wymagań, jaką stworzyłeś dawno temu, w rozdziale 2., kiedy zajmowaliśmy się systemem sterowania drzwiczek dla psa zamówionym przez Tadka i Jankę. Podstawowa różnica pomiędzy tymi listami polega jednak na tym, iż lista możliwości koncentruje się na ogólnej postaci tworzonego systemu.

Szkielet systemu gier Gerarda Lista możliwości

Stosuj listę możliwości lub listę wymagań, jeśli chcesz określić GŁÓWNE OPERACJE, jakie system musi realizować. Kiedy już przygotujesz listy możliwości lub wymagań, powinieneś określić, w jaki sposób system zostanie złożony z poszczególnych elementów w jedną całość. Na tym etapie prac przypadki użycia są zazwyczaj zbyt szczegółowe, dlatego diagram przypadków użycia może Ci pomóc w określeniu postaci systemu widzianej z wysokości 10 kilometrów. Taki diagram stanowi jakby plan całej tworzonej aplikacji.

Narysuj diagram przypadków użycia, aby zobaczyć, czym JEST tworzony system, bez wgłębiania się w niepotrzebne szczegóły.

Tworzenie gry

1. Szkielet obsługuje różne rodzaje terenu. 2. Szkielet obsługuje różne okresy, w tym także fikcyjne, takie jak science-fiction lub fantasy. 3. Szkielet obsługuje wiele różnych rodzajów jednostek wojskowych oraz typów uzbrojenia, charakterystycznych dla konkretnej gry. 4. Szkielet obsługuje moduły dodatkowe pozwalające na wzbogacanie gry o nowe kampanie lub scenariusze bitew. 5. Szkielet udostępnia planszę składającą się z kwadratowych pól, przy czym dla każdego z nich można określać odrębny rodzaj terenu. 6. Szkielet określa, na którego z graczy przypada w danej chwili kolejność wykonania ruchu. 7. Szkielet koordynuje przesuwanie pionków po planszy.

Utworzenie planszy

Zmiana kolejki Modyfikacja istniejącej gry

Przesuwanie jednostek

Projektant gry Oto nasz diagram przypadku użycia… Stanowi on plan całego systemu.

Gra

Wdrożenie gry

Dodawanie i usuwanie jednostek jesteś tutaj  327

Rozmawiajmy językiem klienta

Męska wymiana zdań

 Czy nie pora już zacząć rozmawiać o kodzie? To znaczy wiem, że potrzebujemy tej listy możliwości i diagramu przypadków użycia i czego tam jeszcze trzeba, ale przecież kiedyś musimy zacząć coś pisać, prawda?

)UDQHN No… czy ja wiem. Sądzę, że cały czas rozmawialiśmy o kodzie. .XED A niby dlaczego tak uważasz? Możesz mi zatem powiedzieć, na jaki wiersz kodu przekształcimy stwierdzenie: „Szkielet obsługuje różne rodzaje terenu”? )UDQHN Chodzi ci o te możliwości, które udało się nam określić? Cóż, nie odpowiadają one jednemu wierszowi kodu, ale zapewne całkiem sporemu kawałkowi kodu… prawda? .XED Pewnie… ale kiedy zaczniemy rozmawiać o klasach, które musimy napisać, i pakietach, w jakich je umieścimy? )UDQHN Bez wątpienia szybko się do tego momentu zbliżamy. Jednak klient nie rozumie większości spośród tych wszystkich informacji… Gdybyśmy mu pokazali same klasy i zmienne, to nigdy nie mielibyśmy do końca pewności, czy system będzie robić to, co powinien. Kuba

Franek

Analiza dziedziny pozwala sprawdzić projekt, korzystając przy tym z języka używanego przez klienta. 328

Rozdział 6.

.XED A co z diagramami klas? Czy nie moglibyśmy właśnie ich użyć do przedstawienia tego, co mamy zamiar napisać? )UDQHN Cóż, moglibyśmy… Ale czy przypuszczasz, że klient lepiej by je zrozumiał? To właśnie tych zagadnień dotyczy analiza dziedziny. Dzięki niej możemy rozmawiać z klientem o jego systemie, używając słownictwa, które klient zdoła zrozumieć. Na przykład, w przypadku Gerarda, oznacza to rozmowę o jednostkach, rodzajach terenu i polach planszy, a nie o klasach, obiektach i metodach.

Rozwiązywanie naprawdę dużych problemów

A zatem zabawmy się w analizę dziedziny! Spróbujmy złożyć w całość wszystkie informacje o systemie Gerarda, które udało się nam zdobyć, przy czym zróbmy to w taki sposób, by Gerard, czyli nasz klient, był w stanie pojąć, co do niego mówimy. Taki proces jest nazywany analizą dziedziny i oznacza mniej więcej tyle, że opisujemy problem za pomocą terminów, które zrozumie nasz klient. W tym przypa dziedziną jest dku do tworzenia system gier.

zapisane Te możliwości są żeń ra wy przy użyciu klient i zwrotów, które zrozumie.

stanowi możliwości Cała lista y, iz al ę an które swoistą form inającą te, — przypom zaliśmy ad przeprow ich rozdziałach. w poprzedn

Szkielet systemu gier Gerarda Lista możliwości 1. Szkielet obsługuje różne rodzaje terenu. 2. Szkielet obsługuje różne okresy, w tym także fikcyjne, takie jak science-fiction  lub fantasy. 3. Szkielet obsługuje wiele różnych rodzajów jednostek wojskowych oraz typów uzbrojenia, charakterystycznych dla konkretnej gry. 4. Szkielet obsługuje moduły dodatkowe pozwalające na wzbogacanie gry o nowe kampanie lub scenariusze bitew. 5. Szkielet udostępnia planszę składającą się z kwadratowych pól, przy czym dla każdego z nich można określać odrębny rodzaj terenu. 6. Szkielet określa, na którego z graczy przypada w danej chwili kolejność wykonania ruchu. 7. Szkielet koordynuje przesuwanie pionków po planszy.

Kącik naukowy analiza dziedziny — proces określania, gromadzenia, organizowania i reprezentowania ważnych informacji dotyczących dziedziny problemu, bazujący na studiowaniu istniejących systemów oraz przebiegów ich tworzenia, wiedzy pozyskanej od ekspertów w danej dziedzinie, teorii leżącej u jej podstaw oraz technologii, jakie są stosowane w obrębie tej dziedziny. jesteś tutaj  329

Wszyscy kochają szczęśliwych klientów

Co większość programistów daje swoim klientom… A cóż to takiego jest? Nie mam zielonego pojęcia, czy to będzie robić to, co bym chciał.

FODVV 8QLW^ 8QLW ` ` `

Unit type: String properties: Map setType(String) getType(): String setProperty(String, Object) Units getProperty(String): Object

Unit.java

Board

 Game Utilities

Controller

wie, ogóle nie , Gerard wdzi, a to dlatego o co cho st programistą! że nie je awiasz z nim Nie rozm zyku. w jego ję

To diagramy klas i pakietów, szcz bezpośrednio związane z kodem, egóły obrazujące, w jaki sposób będziemy pisali szkielet dla Gerarda.

Co my dajemy naszemu klientowi… Szkielet systemu gier Gerarda Lista możliwości 1. Szkielet obsługuje różne rodzaje terenu. 2. Szkielet obsługuje różne okresy, w tym także fikcyjne, takie jak science-fiction lub fantasy. 3. Szkielet obsługuje wiele różnych rodzajów jednostek wojskowych oraz typów uzbrojenia, charakterystycznych dla konkretnej gry. 4. Szkielet obsługuje moduły dodatkowe pozwalające na wzbogacanie gry o nowe kampanie lub scenariusze bitew. 5. Szkielet udostępnia planszę składającą się z kwadratowych pól, przy czym dla każdego z nich można określać odrębny rodzaj terenu. 6. Szkielet określa, na którego z graczy przypada w danej chwili kolejność wykonania ruchu. 7. Szkielet koordynuje przesuwanie pionków po

Ale super! To jest dokładnie to, co chciałem, by system robił.



planszy.

330

Rozdział 6.

Nasza lista możliwośc napisana w języku, i, który klient rozumie.

Gerard jest zachwycony, gdyż rozumie, co masz zamiar napisać, i wie, że system będzie robił to, czego od niego oczekuje.

Rozwiązywanie naprawdę dużych problemów

Dziel i rządź Po uspokojeniu klienta, że to, co robisz, odpowiada temu, czego on oczekuje, dysponując dokończonym zbiorem planów, możesz już zacząć dzielić duży problem na mniejsze fragmenty funkcjonalności. Kiedy to zrobisz, będziesz mógł wykorzystać wszystko, czego nauczyłeś się we wcześniejszej części książki, by kolejno — jeden po drugim — rozwiązać mniejsze problemy i zaimplementować odpowiadające im możliwości funkcjonalne. ólny Oto bardzo og ch szkic niektóry h podstawowyc kieletu sz w tó en em el gier. do tworzenia



Okresy czasu Być może zrealizowanie tej możliwości nie będzie nas kosztowało zbyt wiele pracy… O ile tylko zapewnimy możliwość obsługi różnych rodzajów terenu, typów jednostek i broni, to ten problem sam zniknie.

Pola planszy Szkielet powinien udostępniać podstawowe pole planszy dysponujące możliwością określania dowolnego typu terenu i prawdopodobnie także prowadzenia bitew.

Jednostki

Typy terenu

Potrzebujemy sposobu reprezentowania podstawowej jednostki wojskowej oraz musimy zapewnić projektantom gier możliwość jej rozszerzania i dostosowywania do konkretnej gry.

Każde pole planszy powinno obsługiwać przynajmniej jeden typ terenu, a projektant gry powinien mieć możliwość tworzenia i stosowania swoich własnych niestandardowych typów terenu, zaczynając od pastwisk, a kończąc na jeziorach i wydmach na asteroidach.

Możemy podzielić duż y szkielet na kilka mniej części, nad którymi łatszych nam będzie pracować. wiej

jesteś tutaj  331

Dzielenie dużego problemu na mniejsze

Wielki Podział Nadszedł czas, by podzielić nasz duży problem — czyli szkielet do tworzenia gier — na wiele mniejszych fragmentów funkcjonalności. Już widziałeś, w jaki sposób możemy podzielić grę i jej możliwości na kilka prostych grup funkcjonalności, a zatem podążasz już w dobrym kierunku. Poniżej przedstawiliśmy listę możliwości oraz diagramy, których używaliśmy w tym rozdziale, by pokazać, jak system Gerarda powinien działać. Powinieneś im się przyjrzeć i określić, jakich modułów będziesz chciał użyć do zaimplementowania tych wszystkich funkcjonalności oraz jak rozdzielisz poszczególne możliwości i wymagania. Upewnij się, że Twoje moduły obejmą wszystko, co system Gerarda powinien robić.

Szkielet systemu gier Gerarda Lista możliwości 1. Szkielet obsługuje różne rodzaje terenu. 2. Szkielet obsługuje różne okresy, w tym także fikcyjne, takie jak science-fiction lub fantasy. 3. Szkielet obsługuje wiele różnych rodzajów jednostek wojskowych oraz typów uzbrojenia, charakterystycznych dla konkretnej gry. 4. Szkielet obsługuje moduły dodatkowe pozwalające na wzbogacanie gry o nowe kampanie lub scenariusze bitew. 5. Szkielet udostępnia planszę składającą się z kwadratowych pól, przy czym dla każdego z nich można określać odrębny rodzaj terenu. 6. Szkielet określa, na którego z graczy przypada w danej chwili kolejność wykonania ruchu. 7. Szkielet koordynuje przesuwanie pionków po planszy.

żliwości muszą Wszystkie te mo ntowane me ple im za zostać w systemie…

…podobnie jak funkcjonalności przedstawione na diagramie przypa tym dków użycia.

332

Rozdział 6.

A to jest plansza ma Ci przypomina do gry, która podstawowych za ć o niektórych jakich masz się gadnieniach, na ale pamiętaj: to skoncentrować… nie wszystko!

Rozwiązywanie naprawdę dużych problemów

Units

Aby ułatwić Ci ro ćwiczenia, dodaliśzwiązanie moduł „Units”. Do my niego trafiłyby kla reprezentujące jedsy armie oraz wsze nostki, lki funkcjonalności z e inne nimi związane.

tu/ Dla każdego pakie czym, na , sz pi za łu du mo winien według Ciebie, po ć. wa tro en nc ko się on

Możesz dodać więcej modułów, jeśli uznasz, że ich potrzebujesz; równie dobrze nie musisz użyć wszystkich modułów, które tu narysowaliśmy. Wszystko zależy jedynie od Ciebie!

Mamy DUŻE problemy i po prostu nie jestem w stanie sobie z nimi poradzić. Nadszedł czas, by to wszystko podzielić.

jesteś tutaj  333

Dużo małych problemów

Nasz Wielki Podział

Ten moduł ob planszę do grsługuje samą terenów; zaw y, pionki, typy wszystkie po iera także zo służące do tw stałe klasy planszy wykor orzenia faktycznej w rozgrywce. zystywanej

Oto co zrobiliśmy, aby obsłużyć wszystkie możliwości systemu Gerarda oraz by podzielić duży problem na kilka mniejszych, łatwych do zaimplementowania fragmentów funkcjonalności.

Units Board Ten moduł zajmuje się z wojskami, armiami ora mi wszystkimi jednostka e. grz w iał udz i ym rąc bio

NIE zdecydowaliśmy się na tworzenie osobnego modułu obsługującego typy terenu lub pola planszy, gdyż znalazłyby się w nich nie więcej niż jedna lub dwie klasy. Dlatego też zdecydowaliśmy się umieścić wszystkie te klasy w module Board.

 Game

W module G podstawow ame umieścimy wsz ys e może rozsze klasy, które projekta tkie odnosić do rzać. Będą się one nt jest prowadokresu, w jakim gra właściwości zona, podstawowych danych okre gry oraz wszelkich in strukturę kaślających podstawow nych ą żdej gry.

Utilities

Zawsze warto tworzyć moduł Utilites, w którym będziemy umieszczali wszelkiego typu klasy narzędziowe i pomocnicze, używane w wielu innych modułach.

Controller

y obsługiwać To w tym module możem ich gracze jak w poszczególne etapy, podstawowe hy, ruc oje sw ują kon wy posunięć oraz zasady wykonywania związane ści nno czy e inn wszelkie lnych ona kcj fun m nie z zapewnie gry. Ten moduł możliwości prowadzeniatworzonego można by porównać dopolicjanta przez projektanta gry gowym. sterującego ruchem dro

334

Rozdział 6.

ie

Sp

ojn ok

Dla tego ćwiczenia nie można podać żadnego „jedynie słusznego” rozwiązania!

Nie przejmuj się, jeśli podana przez Ciebie odpowiedź nie odpowiada dokładnie naszej. System można zaprojektować na wiele różnych sposobów, a to jest akurat ten, który my zdecydowaliśmy się wybrać. Powinieneś jednak zwrócić baczną uwagę na to, czy planowane moduły obejmują wszystkie przygotowane wcześniej możliwości i diagramy użycia oraz czy mają sens… W końcu nie chcesz chyba tworzyć modułu, który zawierałby tylko jedną klasę bądź sto lub dwieście klas.

Rozwiązywanie naprawdę dużych problemów

Ludzie, przecież ta gra nie będzie się do NICZEGO nadawać! Nawet nie macie żadnego pakietu graficznego… Nie musi on być jakiś bardzo wymyślny, jednak przecież powinienem widzieć chociaż tę całą planszę i jednostki.

Nie zapominaj, kim tak naprawdę jest klient Z pozoru mogłoby się wydawać, że Antek ma dużo racji… aż do momentu kiedy zdasz sobie sprawę z tego, kto tak naprawdę ma być klientem szkieletu systemu gier Gerarda. Twoim zadaniem jest napisanie szkieletu przeznaczonego dla projektantów gier, a nie samej gry. Interfejsy użytkownika poszczególnych gier będą różne, więc to ich projektanci, a nie Ty, powinni zająć się obsługą grafiki.

Analiza dziedziny pomaga Ci uniknąć tworzenia tych części systemu, których opracowanie nie jest Twoim zadaniem.

co sprawia, Może Antek wie, ać uznana za st zo że gra może ętaj jednak, że „odlotową”, pami oim klientem. to nie on jest Tw

Graphics

To jest coś, co stworz projektanci konkretne ą j Tworzenie tego moduł gry… u należy do Twoich zadań.nie

ZAGADKOWE WZORCE Przyjrzyj się uważnie modułom i zwróć uwagę na to, w jaki sposób zostały zgrupowane klasy wchodzące w skład szkieletu systemu gier. Czy zauważasz w nich jakieś powszechnie znane wzorce projektowe? Oto podpowiedź dla Czytelników książki Wzorce projektowe. Rusz głową!.

FODVV3OD\HU^ SOD\ ^` ULS ^` EXUQ ^` `

jesteś tutaj  335

Model-Widok-Kontroler No wiesz, kiedy projektant gry doda moduł Graphics, to cały ten system będzie bardzo przypominać wzorzec projektowy Model-Widok-Kontroler.

a go skrótowo Większość osób określ co jest — C MV jako: wzorzec ym od angielskich akronimem pochodząc oller. ntr słów: Model-View-Co

To jest wzorzec M odel-W idok-K ontroler! Oto kontroler gry, Obsługuje on mo który napiszemy. gry etapami i ok żliwość prowadzenia się stać z plans reśla, co powinno zą, jednostkami, i tak dalej.

i Units są Moduły Board u… Modelują el od m ą znie części tko, co faktyc one to wszys grze. w się dzieje

gry Projektant zajmuje się daje do widokiem, lu ce w ę ik af gr nia przedstawie by k ta , u el mod li gracze mog cokolwiek zobaczyć.

Graphics Kontroler Widok

Board mo d o zm el info iana rmuje ch s tan widok u

FODVV3OD\HU^ SOD\ ^` ULS ^` EXUQ ^` `

Model

je

Units

 Game Utillities

336

Rozdział 6.

Controller

Te moduły nie pasują do wzorca MVC, jednak pomimo to stanowią część systemu.

eru op er l o tr elu kon mod a n

Projektant gry może rozszerzyć ten moduł, tworząc swój własny kontroler dopasowany do potrzeb konkretnej gry, niemniej jednak ten moduł udostępnia podstawowy kontroler gry.

Rozwiązywanie naprawdę dużych problemów

Czym jest wzorzec projektowy? I jak można takiego wzorca używać? Wszyscy używaliśmy kiedyś gotowych bibliotek lub szkieletów. Dodajemy je do naszego projektu, piszemy jakiś kod korzystający z ich interfejsu programowania aplikacji (API), kompilujemy i używamy ogromnych ilości kodu, które ktoś napisał za nas. Wyobraź sobie na przykład wszystkie interfejsy programowania dostępne w języku Java: obsługę sieci, graficznego interfejsu użytkownika, operacji wejścia-wyjścia i tak dalej. Biblioteki i szkielety przebywają bardzo długą drogę, abyśmy bez problemów mogli wybrać z nich potrzebny komponent i użyć go w naszej aplikacji. Ale… ani biblioteki, ani szkielety nie pomagają nam w trudnym zadaniu określenia struktury naszej własnej aplikacji, tak by można ją było łatwiej zrozumieć oraz by poprawić jej elastyczność i łatwość utrzymania. I właśnie do tego celu możemy zastosować wzorce projektowe. Wzorce projektowe nie trafiają bezpośrednio do kodu pisanych aplikacji, lecz w pierwszej kolejności wędrują do Twego MÓZGU. Wzorzec projektowy jest po prostu sposobem zaprojektowania rozwiązania problemu konkretnego typu. Kiedy już załadujesz do swego mózgu dobrą, praktyczną znajomość wzorców projektowych, będziesz mógł zacząć je stosować w nowych projektach oraz w starych i modyfikowanych, oczywiście jeśli uznasz, że powoli stają się one bezużyteczną plątaniną przydługiego kodu.

ych Kilka wzorców projektow

Twój mózg

ięki i poprawiony dz Twój kod, nowy orców projektowych. wz iu an wykorzyst

całą masę Tę stronę oraz informacji h szczegółowyc zorców projektowych dotyczących wh stosowania i sposobów ic ć w książce możesz znaleź towe. Rusz głową!. Wzorce projek

jesteś tutaj  337

Wzorce projektowe wydają Ci się trudne?

Nie czytałem książki Wzorce projektowe. Rusz głową! i nie do końca rozumiem, czym są wzorce projektowe. Co mam zrobić?

Czytaj dalej! Zastosowanie wzorców projektowych jest jednym z ostatnich etapów tworzenia projektu. Nie przejmuj się, jeśli jeszcze nie znasz wzorców projektowych. Pomagają one podczas pracy nad ostatnimi etapami projektu — kiedy już zastosowałeś wszystkie inne techniki projektowania obiektowego, takie jak hermetyzacja i delegowanie — i pozwalają poprawić elastyczność oprogramowania. Dobrze dobrany wzorzec projektowy może dodać nieco elastyczności do projektu, a przy okazji — zaoszczędzić Ci troszkę czasu. Jeśli jednak nie znasz wzorców projektowych, to też nie stanie się nic strasznego. Wciąż możesz czytać niniejszą książkę i opanować zasady oraz sposoby tworzenia naprawdę doskonałych projektów. Niemniej jednak chcielibyśmy polecić Ci przeczytanie książki Wzorce projektowe. Rusz głową!, abyś mógł przekonać się, w jaki sposób inni rozwiązywali klasyczne problemy związane z projektowaniem aplikacji, i abyś skorzystał z ich doświadczeń.

338

Rozdział 6.

Rozwiązywanie naprawdę dużych problemów

Czujesz się nieco zagubiony? W tym rozdziale zajmowaliśmy się wieloma zagadnieniami, a niektóre z nich nawet nie wydają się ze sobą powiązane…

Ë

Gromadzenie możliwości.

Ë

Analiza dziedziny.

Ë

Dzielenie systemu Gerarda na moduły.

Ë

Spostrzeżenie, że system Gerarda używa wzorca MVC.

Jednak w jaki sposób któreś z tych zagadnień może nam faktycznie pomóc w rozwiązywaniu DUŻYCH problemów? Pamiętaj, że wszystkie przedstawione tu informacje miały Ci pomóc w poznaniu sposobów radzenia sobie z tworzeniem naprawdę dużych aplikacji — takich jak szkielet systemu gier Gerarda — które wymagają znacznie więcej niż jedynie prostego projektu i kodowania.

Ale wyjawimy Ci wielką tajemnicę: zrobiłeś już wszystko, co jest niezbędne do rozwiązania DUŻEGO problemu szkieletu dla Gerarda.

No dobrze, musiało mi coś umknąć. Czy możesz mi powiedzieć, o czym nie wiem?

jesteś tutaj  339

Wiesz, jak rozwiązywać małe problemy

Potęga OOA&D (i trochę zdrowego rozsądku) *U\*HUDUGD 3UH]HQWDFMDSRP\VïX

l–u-Ÿ–¬ŸR–-–J-Ÿ ª|–®¬Ÿ˜®plRqR ¬GŸ¥¸¬ª-yRŸ‚–®R®Ÿ‚–|oRp -y }ªŸalR–ŸJ|Ÿ ‡Ÿ |‚–-A|ª¬ª-yl-ŸalR–Ÿ˜ –- RalA®y¬AiGŸªŸp }–¬AiŸ–|®a–¬ªp-ŸoR˜ Ÿ‚–|ª-J®|y-ŸR -‚-ul Ÿ 'Ÿ|J–}¸ylRyl¥Ÿ|JŸ-–p-J|ª¬AiŸ˜ –®Rq-yRpŸ|–-®ŸalR–GŸp }–RŸu-oԟ‚–®¬AlÔayÔɟa–-A®¬ Ÿ7ÖJԟ˜l֟ ¬uGŸy-˜®RŸa–¬ |uŸJ´ªlÖp|ª \lA®yRoŸlŸR\Rp  J®lÖplŸ- –-pA¬oyRoŸ˜®-AlRŸa–|‚lR–-ɟy-Ÿ RAiylA®y¬AiŸ˜®A®Ra}t-AiŸ˜ –- RallŸlŸ -p ¬pl‡Ÿ-˜®Ÿ˜®plRqR Ÿ¥J|˜ Ö‚ylŸ Ÿ ª˜®RqplRŸy-–®ÖJ®l-ŸylR®7ÖJyRŸJ|Ÿ˜ ª|–®Ryl-Ÿp|yp–R yRoŸa–¬ŸlŸoRJy|A®R²ylRŸ¥Ai–|yl ‚–|a–-ul˜ ÖŸ|JŸp|J|ª-yl-Ÿ‚|ª -–®-oÔA¬AiŸ˜l֟A®¬yy|²Al‡

Zaczęliśmy od tej nieco ogólnikowej i dalekosiężnej wizji systemu. To ona stała się naszym DUŻYM problemem.

liśmy, Kiedy już określi ć, rzy wo st my ma co gram narysowaliśmy dia ia, yc uż ów przypadk pomóc który miał nam ólnej w zrozumieniu og ia. postaci rozwiązan

®plRqR Ÿ RyGŸy-®ª-y¬Ÿ‚–®R®Ÿy-˜Ÿ Ÿ„|JŸ-yalRq˜plAiŸ˜t}ªFŸa-uRŸ˜¬˜ RuŸ Ÿ \–-uRª|–pGŸ˜®plRqR Ÿ˜¬˜ Ru¥ŸalR–…GŸ7ÖJ®lRŸ˜ -y|ªlɟ‚|J˜ -ªÖŸª˜®¬˜ plAiŸ‚–-AŸ\l–u¬ –¬ŸR–-–J-‡Ÿ–®¬7lR–®RŸ|yŸ‚|˜ -ɟ7l7ql| RplŸpq-˜Ÿ|Ÿ‚–RA¬®¬oylRŸ®JR\lyl|ª-y¬uŸ ly R–\Ro˜lRŸ‚–|a–-u|ª-yl-Ÿ„…GŸp }–¬Ÿ‚|ªlylRyŸ7¬ÉŸ‚–®¬J- y¬ŸJq-Ÿª˜®¬˜ plAiŸ Ÿ ®R˜‚|t}ªŸ®-ou¥oÔA¬AiŸ˜l֟ ª|–®RylRuŸalR–Ÿ‚q-y˜®|ª¬Ai‡Ÿ ®plRqR Ÿ7ÖJ®lR ¥J|˜ Ö‚yl-ɟ˜ -yJ-–J|ªRŸu|¸qlª|²AlŸ\¥ypAo|y-qyRŸ®ªlÔ®-yRŸ®F X

JR\lyl|ª-ylRuŸlŸ–R‚–R®Ry |ª-ylRuŸp|y\la¥–-AolŸ‚q-y˜®¬ŸJ|Ÿa–¬›

X JR\lyl|ª-ylRuŸoRJy|˜ RpŸª|o˜p|ª¬AiGŸp|y\la¥–|ª-ylRuŸ-–ullŸ|–-®Ÿ

ª˜®RqplAiŸlyy¬AiŸoRJy|˜ RpŸ7l|–ÔA¬AiŸ¥J®l-tŸªŸ–|®a–¬ªAR›

X ‚–®R˜¥ª-ylRuŸoRJy|˜ RpŸy-Ÿ‚q-y˜®¬› X |p–R²q-ylRuŸ‚|‚–-ªy|²AlŸ–¥Ai}ª› X ‚–|ª-J®RylRuŸ7l Rª› X J|˜ -–A®-ylRuŸly\|–u-AolŸy-Ÿ Ru- ŸoRJy|˜ Rp‡

Ÿ -˜®Ÿ˜®plRqR Ÿ‚|ªlylRyŸ¥‚–|²Alɟ‚–|AR˜Ÿ ª|–®Ryl-Ÿ˜ –- RalA®y¬AiŸalR–Ÿ‚q-y˜®|ª¬Ai –|®a–¬ª-y¬AiŸR -‚-ulGŸ -pŸ7¬Ÿp|–®¬˜ -oÔARŸ®ŸylRa|Ÿ|˜|7¬Ÿu|at¬Ÿ‚|²ªlÖAlɟ˜ª}oŸ A®-˜Ÿy-Ÿlu‚qRuRy -Ao֟˜-uRoŸa–¬‡

3 Narysowaliśmy plan

tworzonego systemu.

1

Przedstawiliśmy listę klientowi.

Szkielet systemu gier Gerarda Lista możliwości 1. Szkielet obsługuje różne rodzaje terenu. 2. Szkielet obsługuje różne okresy, w tym także fikcyjne, takie jak science-fiction lub fantasy. 3. Szkielet obsługuje wiele różnych rodzajów jednostek wojskowych oraz typów uzbrojenia, charakterystycznych dla konkretnej gry. 4. Szkielet obsługuje moduły dodatkowe pozwalające na wzbogacanie gry o nowe kampanie lub scenariusze bitew. 5. Szkielet udostępnia planszę składającą się z kwadratowych pól, przy czym dla każdego z nich można określać odrębny rodzaj terenu. 6. Szkielet określa, na którego z graczy przypada w danej chwili kolejność wykonania ruchu. 7. Szkielet koordynuje przesuwanie pionków po planszy.

2 Upewniliśmy się,

że rozumiemy system.

340

Rozdział 6.

analizy Dzięki zastosowaniu się, dziedziny upewniliśmy g dłu że rozumiemy, co, we ić. rob ma tem sys da, Gerar

Rozwiązywanie naprawdę dużych problemów

Dysponując przy go planem i listą mo towanym mogliśmy już po żliwości, dz Gerarda na wiele ielić aplikację fragmentów, odpo mniejszych konkretnym funk wiadających cjonalnościom.

,YK\N ?XS^]

 1KWO

-YX^\YVVO\

?^SVS^SO]

t ie użyliśmy nawe W naszym system ojektowego, który już pr jednego wzorca y. znamy i rozumiem

4 Podzieliliśmy duży problem

na mniejsze fragmenty funkcjonalności.

Spójrz! Wykorzystując wszystko, czego się już dowiedziałeś o analizie i projektowaniu, jesteś już w stanie rozwiązać te mniejsze problemy…

7@-

-YX^\YVVO\

1\KZRSM] 5YX^\YVO\ ASNYU

,YK\N WY N Y dW OV SXPY SKXK \W_TO MR ]

^KX aSNYU

_

FODVV3OD\HU^ SOD\ ^` ULS ^` EXUQ ^` `

7YNOV

…a nawet możesz określić, jak zastosować wzorzec MVC, który dokładniej opisaliśmy w książce Wzorce projektowe. Rusz głową!.

TO

?XS^]

O\_ YZ VO\

^\Y OV_ X UY WYN

XK

5 Zastosowaliśmy wzorce projektowe,

które pomogły nam rozwiązać mniejsze problemy.

Gratulujemy! Przekształciłeś DUŻY PROBLEM w grupę MNIEJSZYCH PROBLEMÓW, które już wiesz, jak rozwiązać. jesteś tutaj  341

Przybornik projektanta

Narzędzia do naszego projektanckiego przybornika Porwałeś się na ogromny problem i wciąż trzymasz się na nogach! Przypomnijmy sobie zatem kluczowe zagadnienia związane z rozwiązywaniem dużych problemów.

Wymagania iązyw Rozw kt anie Dużych że system proje za iują, nia gwarant Anali Dobre wymaga niami emów Probl będzie działał zgodnie z wymaga oprogramowanie

Dobrze zaprojektowane określ, czego on oczekuje i ać. Słuchaj iklienta rozszerz można łatwo zmieniać ęą tworzony system. obejmuj ma robić Upewnij się, że wymaganiai co użycia ku owe przypad wszystkie kroki zasady projektowania podstaw Stosuj . możliwo systemu ego dla tworzon i w języku zrozumiałym Zapisz zacjaści opracowanego hermety jak listę ego, takie obiektow klienta.ć elastyczność dlapoprawi by zenie, dziedzic by Wykorzystaj przypadki użycia,owania. oprogram rzeczach, oże określone przez Ciebie możliwości ich się o wszystk dowiedzieć swojego Upewnij się, ieć. powiedz Ciodpowia których klient zapomniał nie dają faktyczn ny, to ym oczekiwaniom klienta. jest elastycz Jeśli projekt j złego zostawia nie ie Nigdy wszystk ! ujawnią GO ZMIEŃ Przypadki użycia plan Twój w postaci diagramu tosystemu jest nia, jeśli wymaga eStwórz , nawet projektu lub brakując niekompletne użycia (i samych przypadków użycia). ków do przypad dodać będziesz musiał projekt. które zapewne tworzonego systemu. na i wiele mniejszych sekcji. aplikacj z klassystem każda duży Upewnij się, żePodziel nia Twoje zwymaga czasu, każda Wraz z upływem nich powinna jest spójna: zać). powięks poszczególnymi mniejszymi (i na prac zmieniać Podczas TYLKO jinad zawsze będą realizac ować koncentr sięsię wzorce projektowe. staj wykorzy i sekcjam powinna czym przy A, ZADANI JEDNEGO dobrze. je wykonywać bardzo Podczas projektowania i kodowania każdej systemu ch sekcji i ze stosuj podstawowe z mniejszy zawsze owanie, Modyfikując oprogram i projekto analizy ać jegowania obiektowego. poprawi staraj się wszystkich sił zasady spójność.

klienta.

ale W tym rozdzi się o nową y wzbogaciliśm nik. kategorię tech

Zasady projektowania obiektowego Poddawaj hermetyzacji to, co się zmienia. Stosuj interfejsy, a nie implementacje. Każda klasa w aplikacji powinna mieć tylko jeden powód do zmian. Klasy dotyczą zachowania i funkcjonalności.

CELNE SPOSTRZEŻENIA Q

Najlepszym sposobem postrzegania dużego problemu jest potraktowanie go jako zbioru mniejszych problemów.

Q

Podobnie jak w przypadku małych projektów, pracę nad dużymi projektami powinieneś zacząć od zgromadzenia możliwości i wymagań.

Q

Q

342

Możliwości to zazwyczaj jakieś „większe” zadania, które system może realizować, niemniej jednak termin „możliwość” można także potraktować jako odpowiednik terminu „wymaganie”. Podobieństwa i odmienności pozwalają na porównanie tworzonego systemu oraz rzeczy i rozwiązań, które już znasz.

Rozdział 6.

Q

Przypadki użycia koncentrują się na szczegółach; natomiast diagramy przypadków użycia prezentują ogólną postać systemu.

Q

Diagram przypadków użycia powinien obejmować i uwzględniać wszystkie możliwości określone dla systemu.

Q

Analiza dziedziny prezentuje system w języku zrozumiałym dla klienta.

Q

Aktor to cokolwiek, co prowadzi jakąś interakcję z systemem, lecz nie jest jego częścią.

$UFKLWHNWXUD

Porządkowanie chaosu No dobrze, już rozumiem te plany, ale wciąż nie jestem pewna, jak cośtamotaczka łączy się z rzeczochłaniaczem.

Gdzieś musisz zacząć, jednak uważaj na to, żeby wybrać właściwe „gdzieś”! Już wiesz, jak podzielić swoją aplikację na wiele małych problemów, ale oznacza to tylko i wyłącznie tyle, iż obecnie nie masz jednego dużego, lecz WIELE małych problemów. W tym rozdziale spróbujemy pomóc Ci w określeniu, gdzie należy zacząć, i upewnimy się, że nie będziesz marnował czasu na zajmowanie się nie tym, co trzeba. Nadszedł czas, by pozbierać te wszystkie drobne kawałki na Twoim biurku i zastanowić się, jak można je przekształcić w uporządkowaną i dobrze zaprojektowaną aplikację. W tym czasie poznasz niesłychanie ważne „trzy P dotyczące architektury” i dowiesz się, że Risk to znacznie więcej niż jedynie słynna gra wojenna z lat 80.

to jest nowy rozdział  343

Od czego należy zacząć?

Czy czujesz się nieco przytłoczony? A zatem dysponujesz wieloma niewielkimi fragmentami funkcjonalności, z którymi doskonale wiesz, co należy zrobić… Masz jednak znacznie więcej tematów do rozmyślań, takich jak diagramy przypadków użycia czy też lista możliwości.

,YK\N ?XS^]

Szkielet systemu gier Gerarda

 1KWO

Lista możliwości 1. Szkielet obsługuje różne rodzaje terenu. 2. Szkielet obsługuje różne okresy, w tym także fikcyjne, takie jak science-fiction lub fantasy. 3. Szkielet obsługuje wiele różnych rodzajów jednostek wojskowych oraz typów uzbrojenia, charakterystycznych dla konkretnej gry. 4. Szkielet obsługuje moduły dodatkowe pozwalające na wzbogacanie gry o nowe kampanie lub scenariusze bitew. 5. Szkielet udostępnia planszę składającą się z kwadratowych pól, przy czym dla każdego z nich można określać odrębny rodzaj terenu. 6. Szkielet określa, na którego z graczy przypada w danej chwili kolejność wykonania ruchu. 7. Szkielet koordynuje przesuwanie pionków po planszy.

-YX^\YVVO\

…poszczególne moduły do napisania…

No dobra, nawet ja wiem, jak sobie poradzić z tymi poszczególnymi fragmentami, ale od czego, do diabła, mam zacząć? Czy możecie mi chociaż powiedzieć, co zrobić w PIERWSZEJ kolejności?

Mamy listę możliwości…

o Czy pamiętasz naszeg o neg nio jaź rzy zap programistę? Oto w jakim stanie pozostawiliśmy go w rozdziale 6.

344

Rozdział 7.

?^SVS^SO]

Architektura

*U\*HUDUGD 3UH]HQWDFMDSRP\VïX

l–u-Ÿ–¬ŸR–-–J-Ÿ ª|–®¬Ÿ˜®plRqR ¬GŸ¥¸¬ª-yRŸ‚–®R®Ÿ‚–|oRp -y }ªŸalR–ŸJ|Ÿ |‚–-A|ª¬ª-yl-ŸalR–Ÿ˜ –- RalA®y¬AiGŸªŸp }–¬AiŸ–|®a–¬ªp-ŸoR˜ Ÿ‚–|ª-J®|y-ŸR -‚-ul ‡Ÿ 'Ÿ|J–}¸ylRyl¥Ÿ|JŸ-–p-J|ª¬AiŸ˜ –®Rq-yRpŸ|–-®ŸalR–GŸp }–RŸu-oԟ‚–®¬AlÔayÔɟa–-A®¬ Ÿ J®lÖplŸ- –-pA¬oyRoŸ˜®-AlRŸa–-\lA®yRoŸlŸR\Rp |uŸJ´ªlÖp|ª¬uGŸy-˜®RŸa–¬Ÿ7ÖJԟ˜l֟ |‚lR–-ɟy-Ÿ RAiylA®y¬AiŸ˜®A®Ra}t-AiŸ˜ –- RallŸlŸ -p ¬pl‡Ÿ-˜®Ÿ˜®plRqR Ÿ¥J|˜ Ö‚ylŸ ª˜®RqplRŸy-–®ÖJ®l-ŸylR®7ÖJyRŸJ|Ÿ˜ ª|–®Ryl-Ÿp|yp–R yRoŸa–¬ŸlŸoRJy|A®R²ylRŸ¥Ai–|yl Ÿ ‚–|a–-ul˜ ÖŸ|JŸp|J|ª-yl-Ÿ‚|ª -–®-oÔA¬AiŸ˜l֟A®¬yy|²Al‡

…ogólną postać rozwiązania, które należy napisać…

®plRqR Ÿ RyGŸy-®ª-y¬Ÿ‚–®R®Ÿy-˜Ÿ Ÿ„|JŸ-yalRq˜plAiŸ˜t}ªFŸa-uRŸ˜¬˜ RuŸ \–-uRª|–pGŸ˜®plRqR Ÿ˜¬˜ Ru¥ŸalR–…GŸ7ÖJ®lRŸ˜ -y|ªlɟ‚|J˜ -ªÖŸª˜®¬˜ plAiŸ‚–-AŸ\l–u¬ Ÿ –¬ŸR–-–J-‡Ÿ–®¬7lR–®RŸ|yŸ‚|˜ -ɟ7l7ql| RplŸpq-˜Ÿ|Ÿ‚–RA¬®¬oylRŸ®JR\lyl|ª-y¬uŸ ly R–\Ro˜lRŸ‚–|a–-u|ª-yl-Ÿ„…GŸp }–¬Ÿ‚|ªlylRyŸ7¬ÉŸ‚–®¬J- y¬ŸJq-Ÿª˜®¬˜ plAiŸ ®R˜‚|t}ªŸ®-ou¥oÔA¬AiŸ˜l֟ ª|–®RylRuŸalR–Ÿ‚q-y˜®|ª¬Ai‡Ÿ ®plRqR Ÿ7ÖJ®lR Ÿ ¥J|˜ Ö‚yl-ɟ˜ -yJ-–J|ªRŸu|¸qlª|²AlŸ\¥ypAo|y-qyRŸ®ªlÔ®-yRŸ®F X

JR\lyl|ª-ylRuŸlŸ–R‚–R®Ry |ª-ylRuŸp|y\la¥–-AolŸ‚q-y˜®¬ŸJ|Ÿa–¬›

X JR\lyl|ª-ylRuŸoRJy|˜ RpŸª|

o˜p|ª¬AiGŸp|y\la¥–|ª-ylRuŸ-–ullŸ|–-®Ÿ ª˜®RqplAiŸlyy¬AiŸoRJy|˜ RpŸ7l|–ÔA¬AiŸ¥J®l-tŸªŸ–|®a–¬ªAR›

X ‚–®R˜¥ª-ylRuŸoRJy|˜ RpŸy-

Ÿ‚q-y˜®¬›

X |p–R²q-ylRuŸ‚|‚–-ªy|²AlŸ–¥

Ai}ª›

X ‚–|ª-J®RylRuŸ7l Rª› X J|˜ -–A®-ylRuŸly\|–u-AolŸy-

Ÿ Ru- ŸoRJy|˜ Rp‡

-˜®Ÿ˜®plRqR Ÿ‚|ªlylRyŸ¥‚–|²Alɟ‚–|AR˜Ÿ ª|–®Ryl-Ÿ˜ –- RalA®y¬AiŸalR–Ÿ‚q-y˜®|ª¬Ai Ÿ –|®a–¬ª-y¬AiŸR -‚-ulGŸ -pŸ7¬Ÿp|–®¬˜ -oÔARŸ®ŸylRa|Ÿ|˜|7¬Ÿu|at¬Ÿ‚|²ªlÖAlɟ˜ª}oŸ A®-˜Ÿy-Ÿlu‚qRuRy -Ao֟˜-uRoŸa–¬‡

7@-

-YX^\YVVO\

...pomysł, jaki miał klient…

1\KZRSM] 5YX^\YVO\ ASNYU

,YK\N WY N Y dW OV SXPY SKXK \W_TO MR ]

^KX aSNYU

_

FODVV3OD\HU^ SOD\ ^` ULS ^` EXUQ ^` `

7YNOV



_TO

?XS^]

…a nawet wzorce projektowe, które możemy zastosować.

ZO\ \ Y YVO V_ \ ^ O UYX WYN

XK

WYSIL

SZARE KOMÓRKI Czy uważasz, że to, od czego zaczniesz, ma jakieś znaczenie? Jeśli tak uważasz, to dlaczego? I, w takim razie, od czego Ty byś zaczął?

jesteś tutaj  345

Potęga architektury

Potrzebujemy architektury Jednak samo wskazanie poszczególnych elementów, z jakich składa się duży problem, nie wystarcza. Oprócz tego będziesz musiał wiedzieć, jak te elementy do siebie pasują oraz które z nich mogą być ważniejsze od pozostałych. Informacje te pomogą Ci określić, co powinieneś zrobić w pierwszej kolejności. ieliśmy… To już wiedz pomaga nam ra tu ek archit aniu tych w projektowemów. st dużych sy

Architektura określa

strukturę projektu i wskazuje

To jest w końcu coś, czego potrzebujemy… Jak możemy określić, co jest najistotniejsze, by zacząć pisanie aplikacji od jej najważniejszych części?

najważniejsze elementy aplikacji oraz wzajemne Początkiem ty związków był ch diagram przy nasz pa użycia, jednak dków szczegóły wza interakcji pomjemnych modułami wci iędzy raczej niejas ąż są ne.

związki pomiędzy nimi.

Kącik naukowy architektura — struktura organizacyjna systemu; obejmuje jego rozkład na części, ich wzajemne połączenia, mechanizmy interakcji oraz zasady i decyzje, jakie miały wpływ na proces projektowania systemu.

346

Rozdział 7.

le a są niezwyk Te zagadnieni spółpracujesz w i śl je , ważne ramistami… z innymi prog ie zrozumieć ic us m wszyscy itekturę. tę samą arch

Architektura

Za pomocą architektury możemy zmienić chaotyczny bałagan… Nie mam zielonego pojęcia, co zrobić z tymi wszystkimi rzeczami. Czy kiedyś już czułeś to samo? Masz całą masę ważnych diagramów i planów, ale tworzą one jedynie jeden wielki bałagan. =dUSOVO^ ]c]^OW_ QSO\ 1O\K\NK

6S]^K WYÒVSaYÄMS 

=dUSOVO^ YL]¸_Q_TO \œÒXO \YNdKTO ^O\OX_ 

=dUSOVO^ YL]¸_Q_TO \œÒXO YU\O]c a ^cW ^KUÒO

PSUMcTXO ^KUSO TKU ]MSOXMOPSM^SYX V_L PKX^K]c 

=dUSOVO^ YL]¸_Q_TO aSOVO \œÒXcMR \YNdKTœa

TONXY]^OU aYT]UYacMR Y\Kd ^cZœa _dL\YTOXSK

MRK\KU^O\c]^cMdXcMR NVK UYXU\O^XOT Q\c 

=dUSOVO^ YL]¸_Q_TO WYN_¸c NYNK^UYaO ZYdaKVKT¦MO

XK adLYQKMKXSO Q\c Y XYaO UKWZKXSO V_L

]MOXK\S_]dO LS^Oa 

=dUSOVO^ _NY]^°ZXSK ZVKX]d° ]U¸KNKT¦M¦ ]S°

*U\*HUDUGD d UaKN\K^YacMR ZœV Z\dc MdcW NVK UKÒNOQY

3UH]HQWDFMDSRP\VïX d XSMR WYÒXK YU\OÄVK¨ YN\°LXc \YNdKT ^O\OX_

l–u-Ÿ–¬ŸR–-–J-Ÿ ª|–®¬Ÿ˜®p 

=dUSOVO^ YU\OÄVK XK U^œ\OQY d Q\KMdc Z\dcZKNK

lRqR ¬GŸ¥¸¬ª-yRŸ‚–®R®Ÿ‚–|oRp |‚–-A|ª¬ª-yl-ŸalR–Ÿ˜ –- RalA®y y }ªŸalR–ŸJ|Ÿ ¬AiGŸªŸp }–¬AiŸ–|®a–¬ªp-ŸoR˜ Ÿ 'Ÿ|J–}¸ylRyl¥Ÿ|JŸ-–p-J|ª¬Ai ‚–|ª-J®|y-ŸR -‚-ul‡Ÿ a NKXOT MRaSVS UYVOTXYĨ acUYXKXSK \_MR_ Ÿ˜ –®Rq-yRpŸ|–-®ŸalR–GŸp }–RŸuJ®lÖplŸ- –-pA¬oyRoŸ˜®-AlRŸa–-\lA® oԟ‚–®¬AlÔayÔɟa–-A®¬Ÿ yRoŸlŸR\Rp |uŸJ´ªlÖp|ª¬uGŸy!

=dUSOVO^ UYY\NcX_TO Z\dO]_aKXSO ZSYXUœa

|‚lR–-ɟy-Ÿ RAiylA®y¬AiŸ˜®A®Ra ˜®RŸa–¬Ÿ7ÖJԟ˜l֟ }t-AiŸ˜ –- RallŸl ®plRqR Ÿ¥J|˜ Ö‚ylŸ ZY ZVKX]dc ª˜®RqplRŸy-–®ÖJ®l-ŸylR®7ÖJyRŸJ|Ÿ˜ ª|–®Ryl-Ÿp|Ÿ -p ¬pl‡Ÿ-˜®Ÿ˜ yp–R yRoŸa–¬ŸlŸoR

,YK\N ?XS^]

 1KWO

-YX^\YVVO\

?^SVS^SO]

Te listy i wzorce powinny pomóc, jednak trudno jest zorientować się, jak to wszystko do siebie pasuje.

‚–|a–-ul˜ ÖŸ|JŸp|J|ª-yl-Ÿ‚|ª Jy|A®R²ylRŸ¥Ai–|ylŸ  -–®-oÔA¬AiŸ˜l֟A®¬yy|²Al‡ ®plRqR Ÿ RyGŸy-®ª-y¬Ÿ‚–®R®Ÿy-˜ Ÿ Ÿ„|JŸ-yalRq˜plAiŸ˜t}ªFŸa-uRŸ˜ \–-uRª|–pGŸ˜®plRqR Ÿ˜¬˜ Ru¥Ÿa ¬˜ RuŸ lR–…GŸ7ÖJ®lRŸ˜ -y|ªlɟ‚|J˜ -ªÖ –¬ŸR–-–J-‡Ÿ–®¬7lR–®RŸ|yŸ‚| Ÿª˜®¬˜ plAiŸ‚–-AŸ\l–u¬Ÿ ˜ -ɟ7l7ql| RplŸpq-˜Ÿ|Ÿ‚–RA¬®¬oyl ly R–\Ro˜lRŸ‚–|a–-u|ª-yl-Ÿ„ RŸ®JR\lyl|ª-y¬uŸ …GŸp }–¬Ÿ‚|ªlylRyŸ7¬ÉŸ‚–®¬J-  ®R˜‚|t}ªŸ®-ou¥oÔA¬AiŸ˜l֟ ª|–® y¬ŸJq-Ÿª˜®¬˜ plAiŸ RylRuŸalR–Ÿ‚q-y˜®|ª¬Ai‡Ÿ ®plRqR Ÿ7ÖJ®lRŸ ¥J|˜ Ö‚yl-ɟ˜ -yJ-–J|ªRŸu|¸ qlª|²AlŸ\¥ypAo|y-qyRŸ®ªlÔ®-yR Ÿ®F X JR\lyl|ª-ylRu ŸlŸ–R‚–R®Ry |ª-ylRuŸp|y\la¥–AolŸ‚q-y˜®¬ŸJ|Ÿa–¬› X JR\lyl|ª-ylRu ŸoRJy|˜ RpŸª|o˜p|ª¬AiGŸp|y\la ¥–|ª-ylRuŸ-–ullŸ|–-®Ÿ ª˜®RqplAiŸlyy¬AiŸoRJy|˜ RpŸ7l| –ÔA¬AiŸ¥J®l-tŸªŸ–|®a–¬ªAR› X ‚–®R˜¥ª-ylRuŸ oRJy|˜ RpŸy-Ÿ‚q-y˜®¬› X |p–R²q-ylRuŸ‚|

‚–-ªy|²AlŸ–¥Ai}ª›

X ‚–|ª-J®RylRuŸ X J|˜ -–A®-ylRuŸ

7l Rª›

ly\|–u-AolŸy-Ÿ Ru- ŸoRJy|˜ Rp‡

-˜®Ÿ˜®plRqR Ÿ‚|ªlylRyŸ¥‚–|²A lɟ‚–|AR˜Ÿ ª|–®Ryl-Ÿ˜ –- RalA®y –|®a–¬ª-y¬AiŸR -‚-ulGŸ -pŸ7¬Ÿ ¬AiŸalR–Ÿ‚q-y˜®|ª¬AiŸ p|–®¬˜ -oÔARŸ®ŸylRa|Ÿ|˜|7¬Ÿu|a A®-˜Ÿy-Ÿlu‚qRuRy -Ao֟˜-uRoŸa t¬Ÿ‚|²ªlÖAlɟ˜ª}oŸ –¬‡

7@-

1\K

-YX^\YVVO\

1\KZRSM] ASNYU 5YX^\YVO\

,YK\N WYN Y dW OV SX SKXK PY\W MR ]^ _TO a KX_ SNYU

FODVV3OD\HU ^ SOD\ ^` ULS ^` EXUQ ^` `



TO

7YNOV

?XS^]

\_

YZO \YVO\ UYX^ YNOV_ XK W

…w uporządkowaną aplikację. zastosować na by o chcemy… A oto czeg siadane informacje, ze br po wszystkie ie stworzyć miłą, do ich podstawaną aplikację. skonstruow SM]

1\KZR

WYNOV SXPY\W_TO aSNYU

Y dWSKXKMR ]^KX_

U

ASNY

7@

V

,YK\N

7YNO

^ D\HU V3O ^` FODV D\ ` SO S ^ ^` UL UQ EX `

?XS^]



YVVO\

UY XK X^\YVO\

W YN YZO OV_ \_

-YX^\

\YVO\ 5YX^

Game TO

O rany! Teraz już widzę, w jaki sposób wszystkie elementy tej układanki pasują do siebie.

Units

Board

Controller

Utilities

O\K\NK QSO\ 1 ^OW_

O^ ]c] VSaYÄMS  =dUSOV 6S]^K WYÒ dKTO ^O\OX_ ^KUÒO

^cW

XO \YN

  a \œÒ KX^K]c Q_TO

YU\O]c

YL]¸_ TO \œÒXO M^SYX V_L P a

USOVO^ Q_ PS KTœ



=d VO^ YL]¸_ U ]MSOXMO XcMR \YNd TOXSK USO

TK \œÒ 

=d XO ^KUSO TO aSOVO ^cZœa _dL\Y PSUMcT YL]¸_Q_

Y\Kd ^XOT Q\c KVKT¦MO

Yda USOVO^ UYacMR UYXU\O 

=d XY]^OU aYT] XcMR NVK NYNK^UYaO Z ¸c

cMd TON O V_L

N_ c]^ KXS WY ^O\ WZ MRK\KU YL]¸_Q_TO XYaO UK °

USOVO^ XSO Q\c Y ¦M¦ ]S



=d adLYQKMK U¸KNKT LS^Oa ZVKX]d° ] K UKÒNOQY XK

_

NV S_]dO

SK

]MOXK\ _NY]^°ZX \dc MdcW NdKT ^O\OX

ZœV Z LXc \Y Z\dcZKNK USOVO^ 

=d aKN\K^YacMRU\OÄVK¨ YN\° d Q\KMdc _ QY d U K \_MR YÒXK Y XK U^œ\O KXS

W d XSMR YU\OÄVK YĨ acUYX ZSYXUœa

USOVO^ SVS UYVOTX O]_aKXSO =d 

T MRa TO Z\d a NKXO UYY\NcX_ USOVO^ !

=d ZVKX]dc NIE YWA ZY

2OZWI’Z

UGD

*HUD

*U \

Ÿ |Ÿ RP\VïX ªŸalR–ŸJ ŸR -‚-ul‡ WDFMDS oRp -y } ª-J®|y- Ÿa–-A®¬Ÿ ]HQ –®R®Ÿ‚–|-ŸoR˜ Ÿ‚–| ®¬AlÔayÔÉ l֟ 3UH oԟ‚– Jԟ˜ ¬ª-yRŸ‚ ¬ªp ¬GŸ¥¸ AiŸ–|®a– p }–RŸu- RŸa–¬Ÿ7Ö Ö‚ylŸ ylŸ J|˜  y-˜® lR–GŸ Ÿ˜®plRqR GŸªŸp }–¬ –-®Ÿa p|ª¬uGŸ ®plRqR Ÿ¥ ylRŸ¥Ai–| -Ÿ ª|–®¬alA®y¬Ai RpŸ| –-–J Rq-y uŸJ´ªlÖ pl‡Ÿ-˜®Ÿ˜ Jy|A®R² Ÿ˜ –® ¬ŸR lR–Ÿ˜ –- R ª¬Ai ŸlŸR\Rp | llŸlŸ -p ¬ RoŸa–¬ŸlŸoR -Ÿ–

l–u ¬ª-yl-ŸaJŸ-–p-J| ®yRo - Ra |yp–R y –-\lA A|ª iŸ˜ – Ÿ l‡ l¥Ÿ| |‚– RuŸ l–u¬ ylRy oŸ˜®-AlRŸa A®Ra}t-A |–®Ryl-Ÿp A®¬yy|²A –-AŸ\ RŸ˜¬˜ J–}¸ ¬oyR iŸ˜® lAiŸ‚ ¬uŸ |Ÿ˜ ª A¬AiŸ˜l֟ 'Ÿ| FŸa-u –-pA ®y¬A yRŸJ ®¬˜ p ª-y ֟ª˜ plAiŸ˜t}ª J®lÖplŸ-  -Ÿ RAiylA-ŸylR®7ÖJ ª -–®-oÔ lyl| Ÿ ˜ -ª alRq˜ ®JR\  plAi –-ɟy ÖJ®l l-Ÿ‚| JŸ-y |ªlɟ‚|J |‚lR y-–® |J|ª-y

Ÿ„| A¬®¬oylRŸ q-Ÿª˜®¬˜ qplRŸ Ÿ˜ -y ª˜®R l˜ ÖŸ|JŸp -˜Ÿ|Ÿ‚–R ¬J- y¬ŸJ ÖJ®lRŸ ®Ÿy-˜Ÿ J®lR plŸpq ‚–®R alR–…GŸ7Ö Ÿ‚–® –-u qR Ÿ7 ‚–|a -®ª-y¬Ÿ u¥Ÿ Ÿ7l7ql| R lylRyŸ7¬É¬Ai‡Ÿ ®plR F RyGŸy qR Ÿ˜¬˜ R yŸ‚|˜ -É  }–¬Ÿ‚|ªq-y˜®|ª lÔ®-yRŸ® ®plRqR Ÿ –pGŸ˜®plR ¬7lR–®RŸ|Ÿ„…GŸpRuŸalR–Ÿ‚ y-qyRŸ®ª |Ÿa–¬› Rª| ˜®¬ŸJ \–-u -–J-‡Ÿ–®u|ª-ylŸ Ÿ‚q-y ª|–®Ryl Ÿ\¥ypAo| ullŸ|–-® –¬ŸR–lRŸ‚–|a–- ¬AiŸ˜l֟  ¸qlª|²Al \la¥–-Aol uŸ-– Ÿp|y -ylR › |ªRŸu| ylRu ly R–\Ro˜ Ÿ®-ou¥oÔA –|ª \la¥ J-–J  |ª¬ªAR |t}ª R®Ry ®R˜‚ yl-ɟ˜ -y ¬AiGŸp|y tŸªŸ–|®a– uŸlŸ–R‚– ¥J|˜ Ö‚ Ÿª|o˜p|ª AiŸ¥J®l-ylR yl|ª Jy|˜ Rp pŸ7l|–ÔA¬ X JR\l uŸoR |˜ R -ylR oRJy yl|ª q-y˜®¬› Ÿlyy¬AiŸ X JR\l Ÿy-Ÿ‚ qplAi Jy|˜ Rp ª˜®R i}ª› uŸoR ¬AiŸ |²AlŸ–¥A ˜¥ª-ylR ˜®|ª X ‚–®R ‚|‚–-ªy Ÿ‚q-y Rp‡ ŸalR– Alɟ˜ª}oŸ y|˜  ²q-ylRuŸ ª›  ŸoRJ alA®y¬Ai ²ªlÖ X |p–R Ÿ7l R RuylRu l-Ÿ˜ –- R u|at¬Ÿ‚| -AolŸy-Ÿ  -J®R –®Ry \|–u Ÿ ª| |Ÿ|˜|7¬Ÿ X ‚–|ª uŸly |AR˜ ŸylRa -–A®-ylR |²Alɟ‚– ˜ -oÔARŸ® J|˜  Ÿ¥‚– X lylRy Ÿ7¬Ÿp|–®¬ Ÿ‚|ª Ÿ -p lRqR  -ulG uRoŸa–¬‡ -˜®Ÿ˜®p y¬AiŸR -‚ o֟˜–¬ª- RuRy -A –|®a u‚q A®-˜Ÿy-Ÿl

Wszystkie diagramy i wzorce są używane po to, by stworzyć dokładnie taki system, jakiego pragnie klient, oraz by jego projekt zapewniał elastyczność i łatwość wielokrotnego stosowania.

jesteś tutaj  347

Wciąż piszemy wspaniałe oprogramowanie Wspaniałe oprogramowanie zawsze się pisze w taki sam sposób — niezależnie od tego, czy pracujesz nad małym, czy nad ogromnym projektem. Wciąż możesz stosować trzy kroki, które opisaliśmy w rozdziale 1.

Czy pamiętasz tę pierwszego rozd stronę zia Te trzy kroki z po łu książki? można stosować wodzeniem tworzenia naprawtakże podczas dę DUŻYCH programów.

$OBRZEZAPROJEKTOWANEAPLIKACJES’SUPER

A]ZKXSK¸O YZ\YQ\KWYaKXSO

a ^\dOMR Z\Y]^cMR U\YUKMR y Wiemy, że te trz m na gą mo po kroki żdego w stworzeniu ka ntów me z poniższych ele u gier. szkieletu system

 ?ZOaXST ]S°

ÒO YZ\YQ\KWYaKXSO

\YLS ^Y MdOQY

YMdOU_TO UVSOX^

,YK\N ?XS^]

 1KWO

-YX^\YVVO\

?^SVS^SO]

/BECNIEMOšE #ISIÅWYDAWAÁ šETOWCALE NIEJESTTAKIE ŽATWE*EDNAKPOKAšEMY šE ANALIZAIPROJEKTOWANIEOBIEKTOWE WRAZ ZKILKOMAPROSTYMIZASADAMI MOG”NA ZAWSZEZMIENIÁPOSTAÁTWORZONEGOPRZEZ #IEBIEOPROGRAMOWANIA

YUWAGÅNAKLIENCIE 7TYMKROKUKONCENTRUJEM#)POWINIENEw ZAPEWNIÁ 70)%273:%* +/,%*./iTO CZEGO KLIENTOD NIEJ OBIÁ ZIER šEAPLIKACJABÅD PRAC DUš”ROLÅODGRYWA OCZEKUJE .ATYMETAPIE PRZEPROWADZENIE PRZYGOTOWANIE WYMAGAÌI ODPOWIEDNIEJANALIZY

 DK]^Y]_T Z\Y]^O dK]KNc

Z\YTOU^YaKXSK YLSOU^YaOQY

Lc ZYZ\KaS¨ OVK]^cMdXYĨ

YZ\YQ\KWYaKXSK +IEDYOPROGRAMOWANIEBÅ MOšESZODSZUKAÁW NIMIDZIE JUšDZIAŽAÁ POWTARZAJ”CESIÅFRAGMEN USUN”Á UPEWNIÁSIÅ šEZASTOSOWTYKODUORAZ PROJEKTOWANIAOBIEKTOWEG AŽEw DOBRETECHNIKI O

#ZYUZYSKAŽEwDOBR”APLIKACJÅOBIEKTOW” KTÎRAROBITO COPOWINNA.ADSZEDŽ CZAS BYZASTOSOWAÁWZORCEIZASADY DZIÅKIKTÎRYM UPEWNISZSIÅ šE4WOJE OPROGRAMOWANIEJESTODPOWIEDNIO PRZYGOTOWANE DOWIELOLETNIEGO UšYTKOWANIA

 =^K\KT ]S° Lc Z\YTOU^

YZ\YQ\KWYaKXSK dKZOaXSK¸

¸K^aYĨ TOQY _^\dcWKXSK

S ZYdaKVK¸ XK TOQY

aSOVYU\Y^XO ]^Y]YaKXSO

JESTEuTUTAJ

SM]

1\KZR

WYNOV SXPY\W_TO aSNYU

Y dWSKXKMR ]^KX_

U

ASNY

V

,YK\N

7YNO

\HU^ FODVV3OD ` SOD\ ^ ULS ^` ` EXUQ ^ `

?XS^]



7@

1KWO

YVVO\

\YVO\

UYX^ XK W \YVO\ YNOV YZO \_TO

_

-YX^\

5YX^

ać także można stosow Te trzy kroki nad naprawdę dużymi y podczas prac zatem powinniśmy A i. m ja ac lik , według ap określenia, co a dopiero zaczynać od ć, bi ro a m acja klienta, aplik ę tworzeniem jej si potem zająć o projektu. szczegółoweg

K\NK

QSO\ 1O\

?XS^]

,YK\N

-YX^\YVVO\

?^SVS^SO]

]c]^OW_

MS

VSaYÄ =dUSOVO^ 6S]^K WYÒ TO ^O\OX_ O

W ^KUÒ

\YNdK c a ^c KX^K]c

\œÒXO

YU\O] ¸_Q_TO

\œÒXO PSM^SYX V_L P O^ YL] KTœa



=dUSOVO^ YL]¸_Q_TO MSOXMO 

R \YNd YTOXSK O TKU ] \œÒXcM 

=dUSOV O ^KUS a _dL\

aSOVO

c d ^cZœ

PSUMcTX ¸_Q_TO XOT Q\ VKT¦MO MR Y\K O^ YL] YXU\O^

ZYdaK T]UYac 

=dUSOV ^OU aY R NVK U K^UYaO V_L

¸c NYN cMdXcM TONXY] ^O\c]^

WYN_ O UKWZKXSO

MRK\KU ¸_Q_TO

O^ YL] SO Q\c Y XYa M¦ ]S°



=dUSOV YQKMKX ¸KNKT¦ ÒNOQY

LS^Oa ]d° ]U XK adL NVK UK _ S_]dO

K ZVKX MdcW

T ^O\OX ]^°ZXS ]MOXK\  Z\dc

KNK

\YNdK O^ _NY MR ZœV

Z\dcZ N\°LXc 

=dUSOV \K^Yac Q\KMdc ÄVK¨ Y _MR_ OQY d

K YU\O d UaKN KXSK \ K U^œ\

WYÒX Uœa

acUYX d XSMR OÄVK X TXYĨ

SO ZSYX O^ YU\ 

=dUSOV T MRaSVS UYVOO Z\dO]_aKX \NcX_T a NKXO O^ UYY !

=dUSOVX]dc WANIE ZY ZVK 2OZWI’ZY UGD

*HUD

*U\

lR–ŸJ|Ÿ ‚-ul‡Ÿ RP\VïX p -y }ªŸaJ®|y-ŸR -a–-A®¬Ÿ WDFMDS –®R®Ÿ‚–|oRR˜ Ÿ‚–|ª-¬AlÔayÔɟ Ÿ 3UH]HQ ¸¬ª-yRŸ‚ ®a–¬ªp-ŸoŸu-oԟ‚–® ¬Ÿ7ÖJԟ˜lÖ ‚ylŸ plRqR ¬GŸ¥p }–¬AiŸ–|lR–GŸp }–R GŸy-˜®RŸa– R Ÿ¥J|˜ Ö –|ylŸ Ÿ ª|–®¬Ÿ˜®®y¬AiGŸªŸ RpŸ|–-®ŸaªlÖp|ª¬u -˜®Ÿ˜®plRq R²ylRŸ¥Ai ¬ŸR–-–J-–Ÿ˜ –- RalA iŸ˜ –®Rq-yRp |uŸJ´  -p ¬pl‡Ÿ ŸlŸoRJy|A®

l–u-Ÿ– ª-yl-ŸalR –p-J|ª¬AlA®yRoŸlŸR\ –- RallŸlŸ –R yRoŸa–¬ |‚–-A|ª¬ lRyl¥Ÿ|JŸ-®-AlRŸa–-\Ra}t-AiŸ˜ Ryl-Ÿp|yp l–u¬Ÿ Ÿ˜¬˜ RuŸ ®¬yy|²Al‡ lAiŸ‚–-AŸ\ Ÿ 'Ÿ|J–}¸y-pA¬oyRoŸ˜ y¬AiŸ˜®A® J|Ÿ˜ ª|–® }ªFŸa-uR A¬AiŸ˜l֟A ֟ª˜®¬˜ plyl|ª-y¬u J®lÖplŸ- –-Ÿ RAiylA® lR®7ÖJyRŸ|ª -–®-oÔ lRq˜plAiŸ˜t |‚lR–-ɟy -–®ÖJ®l-Ÿy J|ª-yl-Ÿ‚ ˜Ÿ Ÿ„|JŸ-ya lɟ‚|J˜ -ª¬oylRŸ®JR\ ˜®¬˜ plAiŸ ®lRŸ˜ -y|ª˜Ÿ|Ÿ‚–RA¬® - y¬ŸJq-Ÿª ª˜®RqplRŸy˜ ÖŸ|JŸp| J®lRŸ ¬Ÿ‚–®R®ŸyŸalR–…GŸ7ÖJ l| RplŸpq- Ÿ7¬ÉŸ‚–®¬J ‚–|a–-ul ®plRqR Ÿ7Ö yGŸy-®ª-y  Ÿ˜¬˜ Ru¥ |˜ -ɟ7l7qŸ‚|ªlylRy ˜®|ª¬Ai‡Ÿ -yRŸ®F ®plRqR Ÿ RpGŸ˜®plRqR lR–®RŸ|yŸ‚…GŸp }–¬ |Ÿa–¬› ŸalR–Ÿ‚q-y qyRŸ®ªlÔ® \–-uRª|––J-‡Ÿ–®¬7|ª-yl-Ÿ„|–®RylRu ¥ypAo|yŸ Ÿ‚q-y˜®¬ŸJ –ullŸ|–-® –¬ŸR–- Ÿ‚–|a–-u¬AiŸ˜l֟ ª ¸qlª|²AlŸ\ y\la¥–-Aol ª-ylRuŸly R–\Ro˜lRŸ®-ou¥oÔA –J|ªRŸu| -ylRuŸp| p|y\la¥–| ®a–¬ªAR› –R®Ry |ª ®R˜‚|t}ª l-ɟ˜ -yJ˜p|ª¬AiGŸ J®l-tŸªŸ–| ylRuŸlŸ–R‚ ¥J|˜ Ö‚y |˜ RpŸª|ol|–ÔA¬AiŸ¥ X JR\lyl|ªylRuŸoRJyJy|˜ RpŸ7 q-y˜®¬› X JR\lyl|ª-Ÿlyy¬AiŸoR |˜ RpŸy-Ÿ‚ ª˜®RqplAi Ai}ª› ylRuŸoRJy |ª¬AiŸ ªy|²AlŸ–¥ X ‚–®R˜¥ªlR–Ÿ‚q-y˜® Ÿ RuŸ‚|‚–y|˜ Rp‡ ª› lA®y¬AiŸa lÖAlɟ˜ª}o X |p–R²q-yl Ÿ Ru- ŸoRJ ylRuŸ7l R l-Ÿ˜ –- Ra |at¬Ÿ‚|²ª –u-AolŸyX ‚–|ª-J®R ˜Ÿ ª|–®Ry |Ÿ|˜|7¬Ÿu ylRuŸly\| ²Alɟ‚–|AR ÔARŸ®ŸylRa X J|˜ -–A®ylRyŸ¥‚–| Ÿp|–®¬˜ -o RqR Ÿ‚|ªl ulGŸ -pŸ7¬Ÿa–¬‡ -˜®Ÿ˜®pl ¬AiŸR -‚-Ao֟˜-uRo –|®a–¬ª-y ‚qRuRy A®-˜Ÿy-Ÿlu

Naprawdę DUŻA aplikacja

348

Rozdział 7.



Architektura

Zacznijmy od funkcjonalności Pierwszy krok zawsze musi polegać na sprawdzeniu, czy aplikacja robi to, czego oczekujemy. W przypadku niewielkich projektów zapisywaliśmy funkcjonalność aplikacji w formie listy wymagań; natomiast w dużych projektach tę samą funkcję pełniły listy możliwości:

Szkielet systemu gier Gerarda Lista możliwości

Wszystkie te możliwości mają związek z funkcjonalnością… koncentrują się na tym, co system powinien robić, a nie na zasadach lub wzorcach, jakie należy zastosować podczas jego tworzenia.

1. Szkielet obsługuje różne rodzaje terenu. 2. Szkielet obsługuje różne okresy, w tym także fikcyjne, takie jak science-fiction lub fantasy. 3. Szkielet obsługuje wiele różnych rodzajów jednostek wojskowych oraz typów uzbrojenia, charakterystycznych dla konkretnej gry. 4. Szkielet obsługuje moduły dodatkowe pozwalające na wzbogacanie gry o nowe kampanie lub scenariusze bitew. 5. Szkielet udostępnia planszę składającą się z kwadratowych pól, przy czym dla każdego z nich można określać odrębny rodzaj terenu. 6. Szkielet określa, na którego z graczy przypada w danej chwili kolejność wykonania ruchu. 7. Szkielet koordynuje przesuwanie pionków po planszy.

Do tych wszystkich diagramów i wzorców wrócimy w dalszej części… na razie jednak koncentrujemy się wyłącznie na funkcjonalności systemu.

,YK\N ?XS^]

 1KWO

Jednak które z nich są najważniejsze? Chociaż wiemy, że należy rozpocząć od skoncentrowania się na funkcjonalności, to wciąż musimy określić, które z elementów funkcjonalności systemu są najważniejsze. To właśnie na nich chcemy skupić się w pierwszej kolejności.

?^SVS^SO]

-YX^\YVVO\

7@-

-YX^\YVVO\

1\KZRSM] ASNYU 5YX^\YVO\

,YK\N WYN Y dW OV SX SKXK PY\W MR ]^ _TO a KX_ SNYU

FODVV3OD\HU^ SOD\ ^` ULS ^` EXUQ ^` `

TO

7YNOV

?XS^]

\_

YZO \YVO\ UYX^ YNOV_ XK W

jesteś tutaj  349

Zacznij od funkcjonalności

Zaostrz ołówek Która z możliwości jest według Ciebie najważniejsza? Chociaż nasza lista możliwości składa się jedynie z siedmiu punktów, to jednak zrealizowanie ich wszystkich będzie wymagało wiele pracy. Twoim zadaniem jest wskazanie, które z możliwości na liście są według Ciebie najważniejsze, a następnie określenie kolejności, w jakiej należy nad nimi pracować.

Szkielet systemu gier Gerarda Lista możliwości 1. Szkielet obsługuje różne rodzaje terenu. 2. Szkielet obsługuje różne okresy, w tym także fikcyjne, takie jak science-fiction lub fantasy. 3. Szkielet obsługuje wiele różnych rodzajów jednostek wojskowych oraz typów uzbrojenia, charakterystycznych dla konkretnej gry. 4. Szkielet obsługuje moduły dodatkowe pozwalające na wzbogacanie gry o nowe kampanie lub scenariusze bitew. 5. Szkielet udostępnia planszę składającą się z kwadratowych pól, przy czym dla każdego z nich można określać odrębny rodzaj terenu. 6. Szkielet określa, na którego z graczy przypada w danej chwili kolejność wykonania ruchu. 7. Szkielet koordynuje przesuwanie pionków po planszy.

ości Wszystkie z tych możliwnio ied ow odp tać muszą zos o obsłużone, jednak tylk iej od Ciebie zależy, w jak nimi kolejności będziesz nad pracował.

W poniższych zapisz cztery pustych wierszach według Ciebie możliwości, które, zrealizowane , powinny być jako pierwsze.

1. 2. 3. 4.

350

Rozdział 7.

Architektura

Zaraz, chwileczkę… skoro architektura zajmuje się związkami pomiędzy fragmentami aplikacji, to dlaczego zastanawiamy się nad poszczególnymi fragmentami? Czy nie powinniśmy skoncentrować się na tym, w jaki sposób je ze sobą połączyć?

Te elementy aplikacji, które są naprawdę ważne, będą także miały znaczenie dla architektury systemu; to na nich powinieneś się skoncentrować w PIERWSZEJ kolejności.

Ale przecież musisz gdzieś zacząć! Jest niezwykle trudno rozważać związki pomiędzy częściami systemu, jeśli nimi nie dysponujemy. Załóżmy, że chcielibyśmy zastanowić się nad interakcjami pomiędzy modułami Board oraz Units:

Units

?

Board

Aby określić wzajemne interakcje tych modułów, musiałbyś cokolwiek o nich wiedzieć — posiadać choćby podstawowe informacje na ich temat. A zatem architektura nie zajmuje się jedynie związkami pomiędzy częściami aplikacji, lecz także określaniem, które z tych części są najważniejsze. Architektura pomaga określić, od jakich części aplikacji należy zacząć jej tworzenie.

jesteś tutaj  351

Co ma znaczenie dla architektury systemu?

Trzy „P” dotyczące architektury Kiedy starasz się określić, czy coś ma znaczenie dla architektury tworzonego oprogramowania, możesz zadać następujące trzy pytania:

1. Czy to jest część istoty systemu? Czy dana możliwość stanowi podstawę tego, czym jest system? Odpowiedz sobie na następujące pytanie: czy jesteś sobie w stanie wyobrazić system bez tej możliwości? Jeśli nie jesteś, to najprawdopodobniej będzie to oznaczać, że znalazłeś możliwość stanowiącą część istoty systemu.

2. A co to tiiiiit ma znaczyć ?

Uwaga od mar sugerujemy zaketingu: wulgaryzmy zwstępować rotem „u licha ”.

Jeśli nie masz pewności, co naprawdę oznacza opis pewnej możliwości, to można sądzić, że dosyć ważne będzie, byś zwrócił na nią baczną uwagę. Zawsze gdy nie jesteś pewny znaczenia jakiegoś elementu, to może się okazać, że jego realizacja zajmie dużo czasu lub przysporzy problemów podczas prac nad resztą systemu. Lepiej byś takimi możliwościami zajął się wcześniej niż później.

3. Jak „u licha” mam to zrobić ? Kolejnym miejscem, na którym powinieneś skoncentrować swą uwagę, są te możliwości, których implementacja wydaje się szczególnie trudna bądź będzie dla Ciebie całkowicie nowym wyzwaniem programistycznym. Jeśli nie masz bladego pojęcia o tym, jak rozwiązać konkretny problem, to lepiej, byś od razu poświęcił mu nieco czasu, tak by później nie przysporzył Ci zbyt wielu kłopotów podczas pracy nad systemem.

352

Rozdział 7.

Architektura

Bądź architektem Z prawej strony przedstawiliśmy listę możliwości, którą opracowałeś w poprzednim rozdziale. Twoim zadaniem jest wcielić się w rolę architekta i określić, za pomocą trzech „P” opisanych na poprzedniej stronie, które z możliwości mają największe znaczenie dla architektury tworzonego systemu.

Co ma znaczenie?

Szkielet systemu gier Gerarda Lista możliwości 1. Szkielet obsługuje różne rodzaje terenu. 2. Szkielet obsługuje różne okresy, w tym także fikcyjne, takie jak science-fiction lub fantasy. 3. Szkielet obsługuje wiele różnych rodzajów jednostek wojskowych oraz typów uzbrojenia, charakterystycznych dla konkretnej gry. 4. Szkielet obsługuje moduły dodatkowe pozwalające na wzbogacanie gry o nowe kampanie lub scenariusze bitew. 5. Szkielet udostępnia planszę składającą się z kwadratowych pól, przy czym dla każdego z nich można określać odrębny rodzaj terenu. 6. Szkielet określa, na którego z graczy przypada w danej chwili kolejność wykonania ruchu. 7. Szkielet koordynuje przesuwanie pionków po planszy.

Dlaczego? Zapisz, na podstawie którego „P” wybrałeś tę możliwość (w razie potrzeby nie musisz ograniczać się do wskazania tylko jednego „P”).

wiedzi Sprawdź, na ile podane tu odpo ymi ejsz ażni najw z się pokrywają możliwościami, które wybrałeś na stronie 350.

jesteś tutaj  353

Określanie, co ma znaczenie

Bądź architektem. Rozwiązanie Z prawej strony przedstawiliśmy listę możliwości, którą opracowałeś w poprzednim rozdziale. Poniżej przedstawiliśmy te możliwości, które według nas mają znacznie dla architektury systemu, oraz pytania, które pozwoliły nam je wybrać.

Szkielet systemu gier Gerarda Lista możliwości 1. Szkielet obsługuje różne rodzaje terenu. 2. Szkielet obsługuje różne okresy, w tym także fikcyjne, takie jak science-fiction lub fantasy. 3. Szkielet obsługuje wiele różnych rodzajów jednostek wojskowych oraz typów uzbrojenia, charakterystycznych dla konkretnej gry. 4. Szkielet obsługuje moduły dodatkowe pozwalające na wzbogacanie gry o nowe kampanie lub scenariusze bitew. 5. Szkielet udostępnia planszę składającą się z kwadratowych pól, przy czym dla każdego z nich można określać odrębny rodzaj terenu. 6. Szkielet określa, na którego z graczy przypada w danej chwili kolejność wykonania ruchu. 7. Szkielet koordynuje przesuwanie pionków po planszy.

stawowe Zdecydowaliśmy, że pod plansza… ma gry dla nie cze zna ie wyobrazić w końcu nie można sob nszy! pla bez gry nia prowadze

Dlaczego?

Co ma znaczenie? Plansza do gry

P1

Jednostki charakterystyczne dla konkretnej gry

P1, P2

Koordynacja przesuwania jednostek po planszy

P3 (i może P2)

przez szkielet

To jest nieco niejasne, a oprócz tego nie wiemy do końca, jak to zrobić. Bez wątpienia warto tej możliwości poświęcić nieco czasu już na samym początku prac i zastanowić się nad tym, co ona w rzeczywistości oznacza i co trzeba będzie zrobić, by ją zrealizować.

354

Rozdział 7.

Uznaliśmy, że jednostki wojskowe mają kluczowe znaczenie dla gry… a poza tym nie jesteśmy do końca pewni, co w rzeczywistości może oznaczać określenie „charakterystyczne dla konkretnej gry”. A zatem tę właściwość wskazaliśmy, odpowiadając sobie na dwa pytania.

Architektura Nie istnieją

głupie pytania

P: Nie całkiem rozumiem, co macie

P: Jeśli pracuję nad nowym

P: Czy to wszystko nie sprowadza

na myśli, mówiąc o „istocie” systemu. Możecie to trochę dokładniej wyjaśnić?

systemem, to najprawdopodobniej nie będę wiedział, jak zaimplementować możliwości, które pojawią się na mojej liście. Czy zatem nie oznacza to, że trzecie „P”, dotyczące właśnie takiej sytuacji, będzie zawsze obowiązywać?

się do subiektywnej oceny?

O: Poprzez istotę systemu rozumiemy to, czym system jest w swojej najprostszej, podstawowej postaci. Innymi słowy, czym rzeczywiście byłby system, gdybyś usunął z niego wszystkie fontanny i wodotryski dodane przez dział marketingu oraz wszystkie „odlotowe” pomysły, na które sam wpadłeś.

Analizując możliwości systemu, zadaj sobie pytanie: „Gdyby ta możliwość nie została zaimplementowana, to czy system wciąż byłby tym, czym ma być?”. Jeśli sam udzielisz sobie negatywnej odpowiedzi, będzie to oznaczało, że analizowana możliwość należy do „istoty systemu”. W przypadku systemu dla Gerarda doszliśmy do wniosku, że gra nie byłaby grą, gdyby zabrakło planszy oraz jednostek wojskowych. Kilka dodatkowych przykładów znajdziesz w ramce „Wysil szare komórki”, zamieszczonej u dołu strony.

P: Czy jeśli czegoś nie rozumiesz, to może to oznaczać jakieś problemy związane z wymaganiami?

O: Nie. Jednak może to oznaczać,

że będziesz musiał zgromadzić jakieś dodatkowe wymagania, a przynajmniej postarać się co nieco wyjaśnić. Podczas początkowych etapów pracy możesz pominąć pewne szczegóły, by poznać podstawy struktury i działania systemu. Jednak na późniejszych etapach powinieneś już uzupełnić brakujące, szczegółowe informacje — właśnie z tym jest związane drugie „P” dotyczące architektury.

O: Nie, absolutnie nie. Na przykład,

chociaż nigdy nie napisałeś kodu decydującego o tym, czy gracz nacisnął klawisz „q” czy „x”, to zapewne doskonale wiesz, jak zastosować podstawową instrukcję warunkową LIHOVH oraz jak określić, jaki klawisz został naciśnięty. A zatem zrealizowanie możliwości takiej jak pobranie danych wprowadzonych z klawiatury nie jest czymś, czego nie byłbyś w stanie zrobić — nawet jeśli jeszcze nigdy wcześniej nie napisałeś dokładnie takiego kodu. Okazuje się, że w całym zadaniu pojawia się jedynie parę drobnych, nowych szczegółów. Jeśli jednak musisz napisać wielowątkowy serwer pogawędek internetowych, a jeszcze nigdy wcześniej nie zajmowałeś się programowaniem wielowątkowym i sieciowym, to oczywiście będą to zagadnienia, które będziesz musiał poznać. I właśnie takich przypadków powinieneś szukać: szczególnie trudnych zadań, które nie do końca wiesz lub nie jesteś pewny jak należy wykonać.

O: W wielu przypadkach faktycznie tak

jest. Jeśli jednak zdecydujesz się zacząć prace od zagadnień, które wydają się mieć najważniejsze znaczenie dla systemu, to powinieneś zapewnić sobie dobry start. Jednak na pewno nie będziesz chciał zaczynać od problemów, które wyglądają znajomo, na przykład od jakiegoś rozwiązania, które zaimplementowałeś podczas prac nad innym projektem. Zaczynaj od możliwości mających podstawowe znaczenie dla systemu oraz problemów, których rozwiązanie może Ci przysporzyć najwięcej trudności. Jeśli będziesz się kierować tą zasadą, to powinieneś podążać prostą drogą do szczęśliwego zakończenia projektu.

Istotę systemu stanowi to, czym system jest na swoim najniższym, podstawowym poziomie.

WYSIL

SZARE KOMÓRKI Jak sądzisz, co stanowi istotę każdego z poniższych systemów: x Stacji meteorologicznej. x Pilota automatyzującego prace domowe. x Aplikacji sterującej urządzeniami używanymi przez DJ-ów?

jesteś tutaj  355

Podążając w kierunku porządku

Obecnie stopień chaosu jest już znacznie mniejszy… Ale potem skoncentrowaliśmy się na tym, by system robił to, co powinien.

Korzystając z trzech „P” dotyczących architektury, zaczęliśmy porządkować cały ten bałagan, od którego zaczęły się prace nad szkieletem gier: Pamiętasz to wszystko? Niezły bałagan jak na początek…

,YK\N

*U\*HUDUGD 3UH]HQWDFMDSRP\VïX

1

=dUSOVO^ ]c]^OW_ QSO\ 1O\K\NK

?XS^]



6S]^K WYÒVSaYÄMS 

=dUSOVO^ YL]¸_Q_TO \œÒXO \YNdKTO ^O\OX_ 

=dUSOVO^ YL]¸_Q_TO \œÒXO YU\O]c a ^cW ^KUÒO

PSUMcTXO ^KUSO TKU ]MSOXMOPSM^SYX V_L PKX^K]c 

=dUSOVO^ YL]¸_Q_TO aSOVO \œÒXcMR \YNdKTœa

TONXY]^OU aYT]UYacMR Y\Kd ^cZœa _dL\YTOXSK

MRK\KU^O\c]^cMdXcMR NVK UYXU\O^XOT Q\c 

=dUSOVO^ YL]¸_Q_TO WYN_¸c NYNK^UYaO ZYdaKVKT¦MO

XK adLYQKMKXSO Q\c Y XYaO UKWZKXSO V_L

]MOXK\S_]dO LS^Oa 

=dUSOVO^ _NY]^°ZXSK ZVKX]d° ]U¸KNKT¦M¦ ]S°

d UaKN\K^YacMR ZœV Z\dc MdcW NVK UKÒNOQY

d XSMR WYÒXK YU\OÄVK¨ YN\°LXc \YNdKT ^O\OX_ 

=dUSOVO^ YU\OÄVK XK U^œ\OQY d Q\KMdc Z\dcZKNK

a NKXOT MRaSVS UYVOTXYĨ acUYXKXSK \_MR_ !

=dUSOVO^ UYY\NcX_TO Z\dO]_aKXSO ZSYXUœa

ZY ZVKX]dc

1KWO

-YX^\YVVO\

7@-

l–u-Ÿ–¬ŸR–-–J-Ÿ ª|–®¬Ÿ˜® plRqR ¬GŸ¥¸¬ª-yRŸ‚–®R®Ÿ‚–|oRp  |‚–-A|ª¬ª-yl-ŸalR–Ÿ˜ –- RalA -y }ªŸalR–ŸJ|Ÿ ®y¬AiGŸªŸp }–¬AiŸ–|®a–¬ªp-ŸoR 'Ÿ|J–}¸ylRyl¥Ÿ|JŸ-–p-J|ª¬A ˜ Ÿ‚–|ª-J®|y-ŸR -‚-ul‡Ÿ iŸ˜ –®Rq-yRpŸ|–-®ŸalR–GŸp }–RŸu J®lÖplŸ- –-pA¬oyRoŸ˜®-AlRŸa–-\ -oԟ‚–®¬AlÔayÔɟa–-A®¬Ÿ lA®yRoŸlŸR\Rp |uŸJ´ªlÖp|ª¬uG Ÿy-˜®RŸa–¬Ÿ7ÖJԟ˜l֟ |‚lR–-ɟy-Ÿ RAiylA®y¬AiŸ˜®A®R a}t-AiŸ˜ –- RallŸlŸ -p ¬pl‡Ÿ-˜® ª˜®RqplRŸy-–®ÖJ®l-ŸylR®7ÖJyRŸ Ÿ˜®plRqR Ÿ¥J|˜ Ö‚ylŸ J|Ÿ˜ ª|–®Ryl-Ÿp|yp–R yRoŸa–¬ ‚–|a–-ul˜ ÖŸ|JŸp|J|ª-yl-Ÿ‚| ŸlŸoRJy|A®R²ylRŸ¥Ai–|ylŸ ª -–®-oÔA¬AiŸ˜l֟A®¬yy|²Al‡ ®plRqR Ÿ RyGŸy-®ª-y¬Ÿ‚–®R®Ÿy-˜ Ÿ Ÿ„|JŸ-yalRq˜plAiŸ˜t}ªFŸa-uR \–-uRª|–pGŸ˜®plRqR Ÿ˜¬˜ Ru¥Ÿa Ÿ˜¬˜ RuŸ lR–…GŸ7ÖJ®lRŸ˜ -y|ªlɟ‚|J˜ -ª –¬ŸR–-–J-‡Ÿ–®¬7lR–®RŸ|yŸ‚ ֟ª˜®¬˜ plAiŸ‚–-AŸ\l–u¬Ÿ |˜ -ɟ7l7ql| RplŸpq-˜Ÿ|Ÿ‚–RA¬®¬ ly R–\Ro˜lRŸ‚–|a–-u|ª-yl-Ÿ„ oylRŸ®JR\lyl|ª-y¬uŸ …GŸp }–¬Ÿ‚|ªlylRyŸ7¬ÉŸ‚–®¬J®R˜‚|t}ªŸ®-ou¥oÔA¬AiŸ˜l֟ ª|  y¬ŸJq-Ÿª˜®¬˜ plAiŸ –®RylRuŸalR–Ÿ‚q-y˜®|ª¬Ai‡Ÿ ®plRqR Ÿ7ÖJ®lRŸ ¥J|˜ Ö‚yl-ɟ˜ -yJ-–J|ªRŸu| ¸qlª|²AlŸ\¥ypAo|y-qyRŸ®ªlÔ®-y RŸ®F X JR\lyl|ªylRuŸlŸ–R‚–R®Ry |ª-ylRuŸp|y \la¥–-AolŸ‚q-y˜®¬ŸJ|Ÿa–¬› X JR\lyl|ªylRuŸoRJy|˜ RpŸª|o˜p|ª¬AiGŸ p|y\la¥–|ª-ylRuŸ-–ullŸ|–-®Ÿ ª˜®RqplAiŸlyy¬AiŸoRJy|˜ RpŸ7 l|–ÔA¬AiŸ¥J®l-tŸªŸ–|®a–¬ªAR› X ‚–®R˜¥ª-y lRuŸoRJy|˜ RpŸy-Ÿ‚q-y˜®¬›

?^SVS^SO]

X |p–R²q-ylR

uŸ‚|‚–-ªy|²AlŸ–¥Ai}ª›

X ‚–|ª-J®R

ylRuŸ7l Rª›

X J|˜ -–A®-

ylRuŸly\|–u-AolŸy-Ÿ Ru- ŸoRJ y|˜ Rp‡ -˜®Ÿ˜®plRqR Ÿ‚|ªlylRyŸ¥‚–|²A lɟ‚–|AR˜Ÿ ª|–®Ryl-Ÿ˜ –- RalA® –|®a–¬ª-y¬AiŸR -‚-ulGŸ -pŸ7¬ y¬AiŸalR–Ÿ‚q-y˜®|ª¬AiŸ Ÿp|–®¬˜ -oÔARŸ®ŸylRa|Ÿ|˜|7¬Ÿu A®-˜Ÿy-Ÿlu‚qRuRy -Ao֟˜-uRoŸ |at¬Ÿ‚|²ªlÖAlɟ˜ª}oŸ a–¬‡

2

=dUSOVO^ ]c]^OW_ QSO\ 1O\K\NK

6S]^K WYÒVSaYÄMS



=dUSOVO^ YL]¸_Q_TO \œÒXO \YNdKTO ^O\OX_ 

=dUSOVO^ YL]¸_Q_TO \œÒXO YU\O]c a ^cW ^ KUÒO

PSUMcTXO ^KUSO TKU ]MSOXMOPSM^SYX V_L PKX^K]c 

=dUSOVO^ YL]¸_Q_TO aSOVO \œÒXcMR \YNdKTœ a

TONXY]^OU aYT]UYacMR Y\Kd ^cZœa _dL\YTOX SK

MRK\KU^O\c]^cMdXcMR NVK UYXU\O^XOT Q\c 

=dUSOVO^ YL]¸_Q_TO WYN_¸c NYNK^UYaO ZY daKVKT¦MO

XK adLYQKMKXSO Q\c Y XYaO UKWZKXSO V_L

]MOXK\S_]dO LS^Oa 

=dUSOVO^ _NY]^°ZXSK ZVKX]d° ]U¸KNKT¦M¦ ] S°

d UaKN\K^YacMR ZœV Z\dc MdcW NVK UKÒNOQ Y

d XSMR WYÒXK YU\OÄVK¨ YN\°LXc \YNdKT ^O\O X_ 

=dUSOVO^ YU\OÄVK XK U^œ\OQY d Q\KMdc Z\ dcZKNK

a NKXOT MRaSVS UYVOTXYĨ acUYXKXSK \_MR _ !

=dUSOVO^ UYY\NcX_TO Z\dO]_aKXSO ZSYXUœa

ZY ZVKX]dc

-YX^\YVVO\

1\KZRSM] 5YX^\YVO\ ASNYU

,YK\N WYN Y dW OV SXPY SKXKM \W_TO

a R ]^ KX_ SNYU

FODVV3OD\HU^ SOD\ ^` ULS ^` EXUQ ^` `

7YNOV

3

O

?XS^]

O\_T \ YZ \YVO OV_ UYX^ YN

W XK

Szkielet systemu gier Gerarda KLUCZOWE możliwości

1. Plansza do gry — istota systemu. 2. Jednostki charakterystyczne dla konkretnej gry — istota systemu oraz co to oznacza? 3. Koordynacja przesuwania jednostek — co to jest i jak to zrealizować?

Wiem, że kocham mężczyzn w mundurach, jednak nawet wśród nich trzeba dokonywać wyborów…

…ale wciąż pozostaje jeszcze wiele do zrobienia. Zredukowaliśmy cały system dla Gerarda do trzech kluczowych możliwości; wciąż jednak nie znaleźliśmy odpowiedzi na najistotniejsze pytanie: od której z nich mamy rozpocząć pracę?

356

Rozdział 7.

nam W końcu udało się e ow cz klu y trz ć wskaza u, na możliwości systemy się których powinniśm skoncentrować.

Architektura

Cóż, to chyba oczywiste, że najpierw należy stworzyć planszę… przecież wiadomo, że to właśnie ona jest istotą systemu.

Męska rozmowa

Błąd! Trzeba zacząć od najtrudniejszego problemu — koordynacji przesuwania jednostek.

Co wy gadacie! Jeśli nawet nie wiecie, co to znaczy „jednostki charakterystyczne dla konkretnej gry”, to chyba jasne, że od tego należy zacząć.

.XED O czym wy, panowie, do diabła, mówicie? Po co w ogóle zwracać uwagę na coś, co nie jest istotą systemu? -HU]\ To śmieszne. Chociaż plansza jest istotą systemu, to i tak będziesz musiał dokładnie określić, czym są „jednostki charakterystyczne dla konkretnej gry”. Jeśli ten problem jest trudniejszy, niż myślimy, to zaimplementowanie go może potrwać tygodnie!

Franek Jerzy Kuba

)UDQHN Może… ale już wiemy, że koordynowanie przesuwania jednostek będzie trudnym zadaniem, bo nie mamy zielonego pojęcia, jak to zrobić! Dlaczego w ogóle chcecie myśleć o czymś innym, skoro wiecie, że to przesuwanie jednostek będzie trudne? -HU]\ Ale te sprawy związane z jednostkami charakterystycznymi dla danej gry też mogą być trudne! Po prostu nic o tym nie wiemy i właśnie o to mi chodzi! Musimy rozpracować te części systemu, o których nic nie wiemy, w przeciwnym wypadku mogą nam przysporzyć prawdziwych problemów! .XED W takim razie, panowie, przystąpcie do pisania mechanizmu zarządzającego przesuwaniem jednostek. Jeśli o mnie chodzi, to zajmę się planszą… ponieważ… coś mi mówi, że Gerard zechce zobaczyć planszę do swojego systemu strategicznych gier planszowych. Poza tym nie mam zamiaru odkładać robienia planszy na potem… Dlatego ja biorę się za nią w pierwszej kolejności. )UDQHN Obaj macie nierówno pod sufitem. Jeśli chcecie, to odwlekajcie moment, kiedy trzeba się będzie zmierzyć z trudnymi problemami; ja mam zamiar w pierwszej kolejności zająć się tymi zagadnieniami, o których na razie nie mam zielonego pojęcia. ok pole ob Zaznacz tej osoby, imienia się zgadzasz. z którą

A jak Ty uważasz, który z nich ma rację? Czy zgadzasz się z:

Kubą (chce zacząć od stworzenia planszy)

Jerzym (zamierza zająć się jednostkami charakterystycznymi dla gry)

Frankiem (chce stworzyć mechanizm zarządzający przesuwaniem jednostek)

jesteś tutaj  357

Wszystko sprowadza się do problemu RYZYKA

Julka pra z Frankiemcuje oraz Kubą , Jerzym odgrywa ro i zazwyczaj w ich spo lę mediatora rach.

Wystarczy zostawić sprawy grupie facetów, a kłótnia murowana. Uważam, że KAŻDY z nich ma rację… problem nie polega na tym, od której możliwości należy zacząć — problem tkwi w RYZYKU!

Wszystkie te możliwości mają znaczenie dla architektury systemu, gdyż każda z nich wprowadza do projektu pewne RYZYKO. Nie ma znaczenia to, od której z nich zaczniesz — o ile tylko prace będą zmierzać w kierunku obniżenia poziomu ryzyka. Przyjrzyjmy się jeszcze raz wszystkim kluczowym możliwościom:

Szkielet systemu gier Gerarda KLUCZOWE możliwości

1. Plansza do gry — istota systemu. 2. Jednostki charakterystyczne dla konkretnej gry — istota systemu oraz co to oznacza? 3. Koordynacja przesuwania jednostek — co to jest i jak to zrealizować?

Ponieważ nie wiemy, co to oznacza, zrealizowanie tej możliwości może wymagać prawdziwego nawału pracy; zatem RYZYKO polega na możliwości przekroczenia terminów realizacji i oddania projektu.

358

Rozdział 7.

Jeśli chodzi o tę możliw ość, to nie mamy pewności, jak ją a zatem istnieje RYZY zrealizować; z nią nie poradzimy bądKO, że sobie implementacja zabierze ź że jej nam naprawdę dużo czasu.

Jeśli podstawowe możliwości systemu nie zostaną zaimplementowane, to istnieje RYZYKO, że cały system nie będzie się podobał klientowi.

A zatem kluczowy problem polega na REDUKCJI RYZYKA, a nie na spieraniu się, od której z kluczowych możliwości należy zacząć pracę nad systemem. Można zacząć od KAŻDEJ z nich pod warunkiem, że będziesz skoncentrowany na tym, by stworzyć to, co masz stworzyć.

Architektura

A ja i tak uważam, że moje ryzyko jest większe od twojego…

Zaostrz ołówek Odszukaj ryzyko w swoim własnym projekcie. Zastanów się nad projektem, nad którym aktualnie pracujesz. A teraz zapisz pierwszą czynność, od której zacząłeś pracę nad tym projektem:

A teraz zastanów się nad trzema „P” dotyczącymi architektury, które przedstawiliśmy na stronie 352. Zadaj sobie te pytania w odniesieniu do realizowanego projektu i poniżej zapisz kilka możliwości, które mogą mieć znaczenie dla architektury tworzonego systemu:

Jeśli przeanalizujesz te możliwości nieco dokładniej, to na pewno zauważysz, że z każdą z nich łączy się pewne RYZYKO. Są to zapewne rzeczy, które mogłyby Ci przysporzyć wielu problemów lub opóźnić oddanie całego projektu. W pustych wierszach poniżej zapisz te możliwości, od których, według Ciebie, powinieneś zacząć prace nad swoim projektem, i wyjaśnij, dlaczego powinieneś tak postąpić. Jakie zagrożenia stwarzają te możliwości? Jakich zagrożeń mógłbyś uniknąć, gdybyś na samym początku zajął się tymi możliwościami?

jesteś tutaj  359

Pisanie interfejsu planszy

=DJDGNDSURMHNWRZD Zdecydowaliśmy, że prace nad szkieletem systemu gier Gerarda zaczniemy od modułu planszy. Twoim zadaniem będzie napisanie interfejsu %RDUG, który projektanci będą rozszerzać i używać podczas tworzenia własnych gier. 3UREOHP Potrzebujesz typu bazowego %RDUG, którego projektanci będą mogli używać podczas tworzenia swoich nowych gier. Wysokość oraz szerokość planszy mają być określane przez projektanta. Oprócz tego plansza może zwracać pole o podanych współrzędnych, zapewnia możliwości umieszczania nowych jednostek na wskazanym polu oraz zwracania wszystkich jednostek zajmujących pole o podanych współrzędnych.

To są szczegóło w wymagania e które okre , Gerard oraślił jego zesp z ół.

1

Utworzyć nową klasę o nazwie %RDUG.

2

Dodać konstruktor %RDUG. Konstruktor ma pobierać dwa argumenty określające odpowiednio wysokość oraz szerokość klasy i na tej podstawie ma tworzyć nową planszę o zadanych wymiarach. Konstruktor powinien także wypełnić planszę kwadratowymi polami, po jednym dla każdej pary współrzędnych X i Y.

3

Napisać metodę, która na podstawie przekazanych współrzędnych X i Y będzie zwracać pole o podanym położeniu.

4

Napisać metody pozwalające na umieszczanie (dodawanie) jednostek w polu określonym na podstawie współrzędnych X i Y.

5

Napisać metodę, która będzie zwracać wszystkie jednostki znajdujące się w polu o podanych współrzędnych X i Y.

a do Oto przykładowa plansz choć m, ate adr kw t gry. Ta jes kątami inne mogą być prosto o różnych wymiarach.

To są współrzęd ne w pionie (Y).

10

ma To pole ędne (8, 9). z łr ó p s w

5 4 3 2 1

Natomiast to pole ma współrzędne (4, 2).

1 2 3 4 5

360

Rozdział 7.

10

ne To są współrzęd ). (X e mi zio po w

2GSRZLHG]L]QDMG]LHV]QDVWURQLH

7ZRMH]DGDQLH

Architektura

:\dcZKNUS_řcMSKLOd^KTOWXSM W tym tygodniu:

Scenariusze pomagają zredukować ryzyko Rusz głową!: Witam, Scenariuszu, jesteśmy bardzo wdzięczni, że znalazłeś czas, by z nami dziś porozmawiać. Scenariusz: Jestem naprawdę bardzo zadowolony, że tu jestem, zwłaszcza iż nie jest to rozdział poświęcony jedynie przypadkom użycia. Rusz głową!: Tak, to prawda. Szczerze mówiąc, byłem naprawdę zaskoczony, kiedy się dowiedziałem, że to właśnie ty będziesz dziś naszym gościem. W tym rozdziale koncentrujemy się przede wszystkim na architekturze i zajmujemy pracą nad możliwościami, które mogą nam pomóc zredukować ryzyko. Scenariusz: Doskonale! Wydaje się, że to jest doskonała metoda podejścia do dużych problemów. Rusz głową!: Cóż, zatem… właściwie… co my tu razem robimy? Scenariusz: Och! Przepraszam… zakładałem, że wiesz, o co chodzi. Także jestem tu po to, by pomóc ci ograniczyć ryzyko.

Rusz głową!: Ale ja byłem przekonany, że jesteś jedynie określoną ścieżką przejścia przez przypadek użycia. A my jeszcze nie napisaliśmy nawet żadnego przypadku użycia! Scenariusz: Nie ma sprawy. Pomimo to mogę wam się bardzo przydać. Chodzi o to… bądźmy szczerzy… wielu programistów nigdy nie znajduje nawet czasu, by spisać przypadki użycia. Zobacz sam… przekonanie czytelników do narysowania samego diagramu przypadków użycia zabrało wam w rozdziale 6. całe cztery strony. A przecież narysowanie diagramu jest o niebo łatwiejsze od napisania przypadku użycia! Rusz głową!: Cóż, faktycznie masz rację… przekonanie programistów do pisania przypadków użycia spotyka się z dużym oporem. Ale one naprawdę są bardzo pomocne… Podczas tworzenia systemu sterowania drzwiczkami dla psa, zamówionego przez Tadka i Jankę, na pewno oszczędziły nam co najmniej jednego dnia pracy. Scenariusz: Jasne, całkowicie się z tobą zgadzam! Jednak w przypadkach gdy programiści po prostu nie mają czasu lub gdy przypadek użycia jest zbyt formalny w stosunku do ich potrzeb, ja mogę zapewnić korzyści w porównaniu z przypadkami użycia, i to bez tej całej papierkowej roboty. Rusz głową!: Hm... to naprawdę bardzo kuszące. Opowiedz zatem, jak to działa. Scenariusz: Dobrze. Na przykład weźmy taką planszę, którą właśnie napisaliście. Załóżmy, że chcesz zredukować ryzyko związane z tym, że Gerard ją zobaczy, i myślisz o czymś ważnym, czego zapomniałeś do niej dodać… Rusz głową!: No tak… pominięcie jakiegoś ważnego wymagania zawsze jest poważnym ryzykiem! Scenariusz: Właśnie… mógłbyś zatem napisać prosty scenariusz określający, w jaki sposób plansza ma być używana — i w tym momencie ja wkraczam do akcji — a potem mógłbyś upewnić się, że plansza działa dokładnie tak, jak to przewiduje scenariusz. Rusz głową!: Ale przecież nie mamy żadnego przypadku użycia… Jakie kroki mamy zatem wybrać do takiego scenariusza? Scenariusz: Wiesz… taki scenariusz nie musi być aż tak formalny. Na przykład mógłbyś stwierdzić: „Projektant tworzy nową planszę o szerokości 8 pól i wysokości 10” oraz „Gracz 1 zabija jednostki Gracza 2 na polu o współrzędnych (4, 5), zatem plansza usuwa jednostki Gracza 2 z tego pola”. Rusz głową!: A… czyli myślisz o takim krótkim opisie sposobów korzystania z planszy? Scenariusz: Właśnie! Później będziesz mógł przejrzeć każdy z takich opisów i upewnić się, że działanie planszy jest z nimi zgodne. Oczywiście, takie rozwiązanie nie jest aż tak dokładne jak przypadki użycia, jednak, jak widać, naprawdę mogę wam pomóc w upewnieniu się, że nie zapomnieliście o żadnych ważnych wymaganiach. Rusz głową!: To naprawdę fantastyczne! W dalszej części ponownie powrócimy z kolejnymi informacjami o Scenariuszu.

jesteś tutaj  361

Określanie scenariusza

Scenariusz układanka Wymyśl scenariusz dla interfejsu Board, który właśnie napisałeś.

Właśnie to jest ry staramy się zmniezyko, które całkowicie wyeli jszyć lub mi dzięki zastosowan nować iu scenariusza.

W tym rozdziale gra toczy się o zmniejszenie ryzyka. Dwie strony wcześniej, na podstawie kilku wymagań, jakie określiliśmy, napisałeś interfejs Board. Obecnie jednak Twoim zadaniem jest określenie, czy o czymś nie zapomnieliśmy. Powinieneś to zrobić szybko — zanim Gerard zobaczy efekty Twojej pracy i znajdzie w niej jakieś błędy. Twoje zadanie polega na wybraniu fragmentów scenariusza umieszczonych u dołu strony i poprzypinaniu ich na korkowej tablicy w sensownej kolejności. Scenariusz powinien obejmować realistyczny fragment gry. Kiedy skończysz, sprawdź, czy czegoś nie brakuje w interfejsie Board, a jeśli faktycznie znajdziesz jakieś braki, to uzupełnij je w kodzie. Może się okazać, że nie wszystkie fragmenty scenariusza będą Ci potrzebne. Powodzenia!

Szkielet systemu gier Gerarda Scenariusz użycia planszy

Na tablicy przypięliśmy dwa elementy scenariusza, aby ułatwić Ci rozpoczęcie pracy.

Gracz 2. przesuwa swoje czołgi na pole (4, 5).

Gra prosi o informacje na temat terenu na polu o współrzędnych (2, 2).

Elementy scenariusza umieszczaj w obu kolumnach.

z Graczem 2. Gracz 1 rozpoczyna bitwę ywają bitwę. Gracz 2 przesuwa swoją arm ię na pole (4, 5). Jednostki Gracza 1 przegr Projektant gry tworzy planszę w określonej erokości i wysok Gra prosi o informacje o terenie na polu (4,sz5). ości. Jednostki Gracza 1 zostają usunięte z pola (4, 5). Jednostki Gracza 2 wygrywają bitwę.

Projektant gry określa wysokość i szerokość.

Gra prosi o informacje o jednostkach um Gracz 1 przesuwa artylerię na pole (4, 5). ieszczonych na polu (4, 5). yjne

przesuwa jednostki inżynier Projektant gry tworzy nową planszę. Gracz 1 na pole (2, 2).

362

Rozdział 7.

Przypinaj fragmenty scenariusza na tej tablicy.

Architektura

:\dcZKNUS_řcMSKLOd^KTOWXSM W tym tygodniu:

Scenariusze pomagają zredukować ryzyko (ciąg dalszy) Rusz głową!: Ponownie wracamy do wywiadu ze Scenariuszem. Scenariuszu, otrzymujemy całkiem dużo telefonów. Czy nie miałbyś nic przeciwko temu, by odpowiedzieć na kilka pytań naszych widzów? Scenariusz: Jasne, będę bardzo zadowolony. Rusz głową!: Doskonale. W takim razie oto pierwsze pytanie, które się często powtarza. Zadał je pan Niecierpliwy z Warszawy: „Zatem twierdzisz, że nie będę już musiał nigdy więcej pisać przypadków użycia? Wystarczy posługiwać się scenariuszami?”. Scenariusz: Och, dziękuję panu, panie Niecierpliwy; faktycznie, to pytanie pojawia się dosyć często. Jestem jednak głęboko przekonany, że cały czas należy pisać przypadki użycia, zawsze gdy jest to tylko możliwe. Osobiście mogę pomóc w nagłych przypadkach i przydać się do określania najważniejszych wymagań. Pamiętajcie jednak, że jestem jedynie jedną ścieżką przejścia przez przypadek użycia. Jeśli w przypadku użycia jest wiele takich ścieżek, to poprzestając jedynie na scenariuszach, możecie pominąć jakieś ważne wymagania. Rusz głową!: Tak, to prawda. Jakiś czas temu gościliśmy w naszym programie Szczęśliwą Ścieżkę oraz Ścieżkę Alternatywną.

Scenariusz: No tak… Jeśli chcecie znać prawdę, to one są po prostu wyspecjalizowanymi rodzajami scenariusza. Wolimy raczej nie mówić o naszych rodzinnych koligacjach, wszyscy chcemy samodzielnie radzić sobie w życiu. Ale faktycznie tworzymy jedną rodzinę Scenariuszy. I prawdę rzekłszy, abyś mógł mieć pewność, że system będzie działał prawidłowo, będziesz potrzebował każdego z nas. Jeśli jednak dopiero zaczynasz prace nad projektem i wydaje Ci się, że tworzenie przypadków użycia byłoby nieco przedwczesne, to zastosowanie mnie będzie dobrym punktem startowym. Rusz głową!: No dobrze, zatem następne pytanie, od pana Zdenerwowanego z Gdańska: „Powiedziałeś, że możesz mi pomóc zredukować ryzyko, a ja bardzo nie lubię ryzyka. Czy możesz mi dokładniej wyjaśnić, w jaki sposób możesz się przyczynić do zmniejszenia tego ryzyka?”. Scenariusz: Kolejne dobre pytanie. Jak zapewne pamiętasz, określając wymagania — niezależnie od tego, czy czynisz to, posługując się przypadkami użycia, diagramem przypadków użycia, czy też scenariuszem — próbujesz upewnić się, że tworzony system będzie robił to, co powinien — czego chce klient. Bez dobrych wymagań zwiększa się ryzyko, że klient będzie niezadowolony lub zawiedziony, gdyż uzyska nie to, czego pragnął. Rusz głową!: A zatem jesteś w stanie zredukować ryzyko podczas fazy określania wymagań? Scenariusz: Tak, w większości przypadków. To właśnie dlatego piszesz przypadki użycia, gromadzisz wszelkie wymagania i korzystasz ze scenariuszy, by wytyczyć wszystkie możliwe ścieżki przejścia przez przypadek użycia. Rusz głową!: Ale jesteś także w stanie pomóc podczas określania architektury dużych systemów, prawda? Przecież właśnie z tego względu przeprowadzamy z Tobą wywiad w tym rozdziale. Scenariusz: Dokładnie. Czasami nie będziesz dysponować pełną listą wymagań ani grupą przypadków użycia, a pomimo to będziesz musiał wykonać jakieś proste prace, by upewnić się, że system zadziała prawidłowo. To właśnie taki jest cel mojej wizyty — zastosować scenariusz dla uzyskania podstawowych informacji o module lub fragmencie kodu, tak byś mógł przygotować podstawowe elementy konstrukcyjne swojej aplikacji. Rusz głową!: A zatem naprawdę jesteś bardzo użytecznym i pomocnym facetem, nieprawdaż? Scenariusz: Chciałbym móc tak o sobie myśleć. Pomagam w gromadzeniu wymagań, w uzyskaniu pewności, że przypadki użycia są kompletne, jestem przydatny w zredukowaniu ryzyka, chaosu i pomagam określić, co powinny robić poszczególne moduły lub fragmenty kodu.

jesteś tutaj  363

Redukcja ryzyka drogą do sukcesu

Scenariusz układanka. Rozwiązanie Wymyśl scenariusz dla interfejsu %RDUG, który właśnie napisałeś. Poniżej przedstawiliśmy opracowany przez nas scenariusz. Twój może być nieco inny, ale w minimalnym zakresie musi obejmować utworzenie planszy przez projektanta gry, bitwę pomiędzy Graczem 1 i 2 oraz dodawanie i usuwanie jednostek z planszy.

Szkielet systemu gier Gerarda Scenariusz użycia planszy Gra prosi o informacje o terenie na polu (4, 5). Projektant gry tworzy planszę w określonej szerokości i wysokości.

Jednostki Gracza 2. wygrywają bitw

ę.

Gracz 2 przesuwa swoje czołgi na pole (4, 5).

Gracz 2 przesuwa swoją armię na pole (4, 5). Jednostki Gracza 1 zostają usunięte z pola (4, 5). Gracz 1 przesuwa artylerię na pole

(4, 5). yjne

Gracz 1 przesuwa jednostki inżynier na pole (2, 2).

Gra prosi o informacje o jednostkach umieszczonych na polu (4, 5). Gracz 1 rozpoczyna bitwę

Ta część scenariusza doprowadza do bitwy pomiędzy Graczem 1 i 2.

Gra prosi o informacje na temat terenu na polu o współrzędnych (2, 2).

z Graczem 2.

Ta część jest opcjonaln pokazuje nam, że pla a, jednak nsz zapewne potrzebować a będzie o typie terenu w dan informacji ym planszy, by móc obsłuż miejscu przesunięcia jednostek yć żądanie .

Scenariusz jest kontynuowany od początku drugiej kolumny.

Jednostki Gracza 1 przegr

ywają bitwę.

Projektant gry tworzy planszę w określonej szerokości i wysokości.

Projektant gry określa wy

sokość i szerokość.

364

Rozdział 7.

W naszym scenariuszu te fragmenty nie zostały wykorzystane.

Architektura Nie istnieją

głupie pytania

P: Skąd się pojawiły wymagania, które podaliście

P: Teraz twierdzicie, że przypadki użycia zwiększają

w „Zagadce projektowej” na stronie 360?

ryzyko? To przecież nie może być prawda!

O: Od Gerarda, z nieznaczną pomocą zdrowego rozsądku.

O: Nie, przypadki użycia nie zwiększają ryzyka, jeśli zostaną użyte

P: Ale dlaczego nie napisaliśmy przypadku użycia,

Kiedy jednak określimy już wszystkie podstawowe możliwości i uwzględnimy główne zagrożenia, to ponownie zajmiemy się każdym z modułów i zaczniemy dodawać do nich więcej szczegółów; na tym etapie prac przypadki użycia będą nam niezwykle pomocne.

Jeśli się zastanowisz nad tym, o co Gerard prosił — czyli o szkielet systemu gier — a następnie przeczytasz rozmowę klientów zamieszczoną w rozdziale 6., to najprawdopodobniej stwierdzisz, że sam podałbyś takie same wymagania. Dorzuciliśmy kilka bardziej szczegółowych wymagań, takich jak możliwość dodawania jednostek i umieszczania ich na konkretnym polu planszy, jednak same się one nasuwają po nieco dokładniejszym przemyśleniu problemu.

by określić te wymagania?

O: Oczywiście mogliśmy to zrobić. Pamiętaj jednak, że nie

chodzi nam o zakończenie prac nad modułem planszy, a raczej o uporządkowanie wszystkich elementów systemu. To nam całkowicie wystarczy, by zredukować ryzyko związane z zakończeniem prac nad tym fragmentem systemu Gerarda. W rzeczywistości, gdybyśmy zagłębili się w szczegóły, to moglibyśmy powiększyć ryzyko, ponieważ zwracalibyśmy uwagę na zagadnienia, które nie mają znaczenia na tym etapie prac.

w odpowiednim czasie. Na razie zajmujemy się kilkoma kluczowymi zagadnieniami, które mogą nam przysporzyć problemów, jeśli ich nie opracujemy. Nie oznacza to jednak, że musimy całkowicie zakończyć prace nad interfejsem Board. Musimy jedynie zrozumieć, jak on działa, co, w razie wystąpienia potencjalnych problemów, pozwoli nam je zawczasu rozwiązać i uniknąć większych kłopotów na późniejszych etapach realizacji projektu. A zatem, na obecnym etapie prac, szczegóły konieczne do napisania dobrego przypadku użycia byłyby całkowicie niepotrzebne.

P: Czy to właśnie dlatego zdecydowaliśmy się na zastosowanie scenariusza? By uniknąć zbyt wielu niepotrzebnych szczegółów?

O: Właśnie. Scenariusz zapewnia nam wiele zalet, które dają także przypadki użycia, lecz nie zmusza nas do wdawania się w szczegóły, którymi na razie nie musimy się przejmować.

=DJDGNDSURMHNWRZD 5D]MHV]F]H  Czego brakuje w klasie %RDUG Przyjrzyj się uważnie scenariuszowi przedstawionemu na poprzedniej stronie. Czy wymagania podane na stronie 360 obejmują wszystkie operacje wykonywane w scenariuszu? Jeśli uważasz, że brakuje jakichś operacji, to zapisz je w poniższych pustych wierszach, a następnie dodaj do pliku %RDUGMDYD kod implementujący te możliwości funkcjonalne.

jesteś tutaj  365

Jak napisaliśmy interfejs planszy

=DJDGNDSURMHNWRZD5R]ZLÈ]DQLH Prace nad szkieletem gier dla Gerarda zacznijmy od modułu planszy. Poniżej przedstawiliśmy interfejs Board, który napisaliśmy w celu obsługi podstawowych operacji naszej przyszłej planszy. Porównaj swoje rozwiązanie z naszym. SDFNDJHKHDGILUVWJVIERDUG



Klasę Board umieścili

śmy w odrębnym  pakiecie, w którym  znajdzie się cały kod

związany z planszą i jej to podziałowi aplikacji obsługą. Odpowiada przedstawiliśmy w roz na moduły, który dziale 6.

LPSRUWMDYDXWLO$UUD\/LVW LPSRUWMDYDXWLO,WHUDWRU LPSRUWMDYDXWLO/LVW LPSRUWKHDGILUVWJVIXQLW8QLW



SXEOLFFODVV%RDUG^ SULYDWHLQWZLGWKKHLJKW SULYDWH/LVWWLOHV SXEOLF%RDUG LQWZLGWKLQWKHLJKW ^ WKLVZLGWK ZLGWK WKLVKHLJKW KHLJKW LQLWLDOL]H  `

szemy już Tojest klasa, którą napi ie nam potrzebna będz gdyż m, awe nieb d. do dokończenia prac nad klasą Boar

Taka postać konstrukt ora została pod  ana w wy  maganiac informacje o szerokośch. Pobiera on planszy, a następnie i i wysokości wy initialize(), by ją zainic wołuje metodę jować.

jektowania: Dodatkowa Zasada Pro

y do odrębnej SULYDWHYRLGLQLWLDOL]H ^ wydzielaj kod inicjującnie będzie mu cze ęki dzi me WLOHV QHZ$UUD\/LVW ZLGWK    tody,   ostałych on utrudniał analizy poz IRU LQWL LZLGWKL ^ Planszę sy. kla fragmentów przedstawimy WLOHVDGG LQHZ$UUD\/LVW KHLJKW  jako tablicę tablic, określającIRU LQWM MKHLJKWM ^ ich wymiary  $UUD\/LVW WLOHVJHW L DGG MQHZ7LOH    na podstawie  przekazanej ` szerokości i wysokości. `

W każdym polu planszy, określon ym konkretną parą współrzędnych, zapi nowy obiekt Tile. Będziemy mus sujemy ieli napisać tę klasę, by nasza klasa Board mogła działać. Definicję klasy Tile znaj dziesz na następnej stronie.

FODVV %RDUG ^JH W8QLW `

Board.java

366

Rozdział 7.

Architektura

Te metody nie wymagają żadnego tłumaczenia, zaspokajają one wymagania podane na stronie 360.

Zdecydowaliśmy, że to klasa Tile będzie obsługiwać te operacje, i w tym miejscu jedynie delegujemy dodawanie i usuwanie jednostek właśnie do tej klasy.

SXEOLF7LOHJHW7LOH LQW[LQW\ ^ UHWXUQ 7LOH  $UUD\/LVW WLOHVJHW [ JHW \  ` SXEOLFYRLGDGG8QLW 8QLWXQLWLQW[LQW\ ^ 7LOHWLOH JHW7LOH [\  WLOHDGG8QLW XQLW  ` SXEOLFYRLGUHPRYH8QLW 8QLWXQLWLQW[LQW\ ^ 7LOHWLOH JHW7LOH [\  Konieczność napi WLOHUHPRYH8QLW XQLW     pozwalającej na sania metody  powinieneś okre usuwanie jednostek śli scenariusza zami ć na podstawie ` es SXEOLFYRLGUHPRYH8QLWV LQW[LQW\ ^ 7LOHWLOH JHW7LOH [\  WLOHUHPRYH8QLWV  ` SXEOLF/LVWJHW8QLWV LQW[LQW\ ^ UHWXUQJHW7LOH [\ JHW8QLWV  `      `

zczonego na stronie 364. To brakowało w nasz właśnie jej ych wymaganiach .

jsce, w którym A to jest kolejne mie operacji e   ani kon wy y jem egu del aż klasa ta do klasy Tile. Poniew anie jednostek, yw ow ech prz umożliwia tek także zatem pobieranie jednosobowiązków. powinno należeć do jej

Nie istnieją

głupie pytania

P: Czy zastosowanie tablicy tablic nie ogranicza nas

P: A zatem, czy to nie jest ograniczenie? Dlaczego nie

do stosowania kwadratowej planszy?

zastosować grafu albo choćby klasy Coordinate, tak by nasze możliwości nie ograniczały się do stosowania par współrzędnych (x, y) na prostokątnej planszy?

O: Nie, choć ogranicza nas do stosowania planszy, której

poszczególne pola mają współrzędne o postaci (x, y). Na przykład takich par współrzędnych można także używać na planszy, której pola nie są kwadratami, lecz sześciobokami foremnymi; oczywiście, wymaga to odpowiedniego uporządkowania samych pól. Niemniej jednak w większości przypadków zastosowanie tablicy tablic jest najwygodniejsze w sytuacjach, gdy plansza jest prostokątna, a jej pola kwadratowe.

O: Gdyby nam zależało na maksymalnej elastyczności, to

oczywiście takie rozwiązanie byłoby dobre. Jednak w naszej sytuacji określone wymagania (znajdziesz je na stronie 360) wyraźnie wskazywały, że współrzędne mają mieć postać (x, y). Właśnie dlatego wybraliśmy rozwiązanie może nie aż tak elastyczne, lecz bez wątpienia łatwiejsze. Pamiętaj, że na tym etapie prac próbujemy minimalizować zagrożenia, a nie powiększać je poprzez stosowanie rozwiązań, które są znacznie bardziej skomplikowane, niż tego wymaga system.

jesteś tutaj  367

Klasy pól planszy i jednostek

Klasy Tile oraz Unit Abyśmy mogli skompilować klasę %RDUG, musimy także stworzyć klasy 7LOH oraz 8QLW. Poniżej przedstawiliśmy początkową wersję ich kodu: Nasza klasa Unit jest to tylko możliwe. Póź tak prosta, jak nie do niej dodać wiele szc j trzeba będzie nie są nam one potrze zegółów, jednak zależy nam jedynie na bne teraz, kiedy skompilowaniu klasy Board.

SDFNDJHKHDGILUVWJVIXQLW SXEOLFFODVV8QLW^  SXEOLF8QLW ^ ` `

FODVV 8QLW^ 8QLW ^ ` `

Unit.java

e się w tym Klasa Tile znajdujco klasa Board… samym pakiecie sobą ściśle obie klasy są ze związane.

Klasa Tile zawiera list jednostek, które są umę na danym polu plansz ieszczone y.

z używane prze To są metody operowania na do d my je klasę Boar Zdefiniowaliś jednostkach. ronione, aby były ch jako metody nych klas ącznie dla in dostępne wył pakietu headfirst.gfs. należących do board.

SDFNDJHKHDGILUVWJVIERDUG LPSRUWMDYDXWLO/LQNHG/LVW LPSRUWMDYDXQLW/LVW LPSRUWKHDGILUVWJVIXQLW8QLW SXEOLFFODVV7LOH^  SULYDWH/LVWXQLWV SXEOLF7LOH ^ XQLWV QHZ/LQNHG/LVW  ` SURWHFWHGYRLGDGG8QLW 8QLWXQLW ^ XQLWVDGG XQLW  ` SURWHFWHGYRLGUHPRYH8QLW 8QLWXQLW ^ XQLWVUHPRYH XQLW  ` FODVV 7LOH `

^JH W8QLW `

Koncentruj się na właściwych zagadnieniach Nie musisz przejmować się wszystkimi operacjami, które kiedyś klasy 7LOH i 8QLW będą musiały realizować. Skoncentruj się na uruchomieniu klasy %RDUG oraz jej kluczowych możliwości, a nie na dokończeniu klas 7LOH i 8QLW. Właśnie dlatego klasa 8QLW jest praktycznie pusta, a klasa 7LOH zawiera jedynie kilka podstawowych metod.

368

Rozdział 7.

Tile.java

Architektura

Nie istnieją

głupie pytania

P: Skoro klasa Tile obsługuje dodawanie i usuwanie jednostek,

Aby zminimalizować ryzyko, skup się jedynie na jednej możliwości w danej chwili. Nie pozwól się zdekoncentrować — nie zajmuj się możliwościami, które nie pomogą zmniejszyć ryzyka.

a pole o określonych współrzędnych można pobrać przy użyciu metody getTile(), to po co dodawać do klasy Board te metody addUnit() i removeUnit()? Czy nie wystarczyłoby wywoływać metodę getTile(), a następnie skorzystać z jej metod?

O: Mógłbyś zastosować takie rozwiązanie i wszystkie operacje

związane z jednostkami realizować przy wykorzystaniu obiektu 7LOH zwróconego przez metodę JHW7LOH . Jednak my zdecydowaliśmy, że dodamy odpowiednie metody do klasy %RDUG, gdyż chcemy, by to właśnie ona była podstawową klasą używaną przez projektantów gier. Jak przekonasz się na następnej stronie, zdecydowaliśmy, że metody klasy 7LOH zostaną zdefiniowane jako metody chronione, tak by mogły być bezpośrednio wywoływane wyłącznie przez metody innych klas należących do tego samego pakietu co klasa 7LOH (na przykład przez metody DGG8QLW i UHPRYH8QLW klasy %RDUG). W ten sposób sprawimy, że to właśnie obiekt %RDUG będzie używany do operowania na polach planszy, jednostkach, a później także na typach terenu.

P: Wciąż uważam, że prościej by było dodać do klas Unit i Tile trochę więcej metod — przynajmniej te, o których wiemy, że w przyszłości będą nam potrzebne. Dlaczego nie chcecie poświęcić teraz nieco więcej czasu na rozbudowę tych klas?

O: Pamiętaj, że na obecnym etapie prac nie dążysz do napisania kodu

dla całego szkieletu, starasz się jedynie zabrać za kilka kluczowych możliwości i zredukować największe zagrożenia dla całego projektu. Poświęcanie czasu na pisanie klasy 8QLW lub dopracowywanie klasy 7LOH w żaden sposób nie pomoże Ci zredukować ryzyka. Zamiast tego postaraj się uruchomić klasę %RDUG, gdyż to właśnie ona, zgodnie z tym, co już stwierdziliśmy, jest istotą systemu, a problemy w jej tworzeniu stanowią poważne ryzyko dla całego projektu. Kiedy już uda Ci się zaimplementować kluczowe możliwości systemu i zredukować lub całkowicie wyeliminować najpoważniejsze zagrożenia, będziesz mieć dużo czasu, by zająć się pozostałymi możliwościami, takim jak klasa 8QLW. Jednak na obecnym etapie prac powinieneś starać się o to, by nie tracić czasu na nic innego oprócz możliwości, które zmniejszają ryzyko niepowodzenia całego projektu.

jesteś tutaj  369

Architektura ogranicza chaos

Więcej porządku, mniej chaosu Architektura oraz lista kluczowych możliwości pomogły nam przygotować klasę %RDUG i upewnić się, że rozumiemy istotę systemu. Przyjrzyjmy się jeszcze raz naszej liście kluczowych możliwości:

dstawową Stworzyliśmy po d, ar Bo sy kla wersję waliśmy a zatem zrealizo tę możliwość m stopniu, w wystarczający ępną. st na się jąć za by

Szkielet systemu gier Gerarda KLUCZOWE możliwości 1. Plansza do gry — istota systemu. 2. Jednostki charakterystyczne dla konkretnej gry — istota systemu oraz co to oznacza? 3. Koordynacja przesuwania jednostek — co to jest i jak to zrealizować?

Przy okazji udało się nam także określić strukturę… Oprócz przygotowania klasy %RDUG udało się nam także stworzyć kilka innych podstawowych klas systemu; możemy zatem zacząć zastanawiać się nad kolejną z kluczowych możliwości oraz dopasowaniem jej do istniejącej struktury klas.

Chociaż klasa Bo zawiera żadnych ard nie typu Unit, to jes zmiennych z nią powiązana, t jednak metody klasy Bo gdyż ar wymagają przeka d zania obiektu Unit.

Board

Unit

width: int height: int tiles: Tile[*][*]

Tile

getTile(int, int): Tile addUnit(Unit, int, int) removeUnit(Unit, int, int) removeUnits(int, int) getUnits(int, int): List

units: Unit[*] addUnit(Unit) removeUnit(Unit) getUnits(): List removeUnits()

Język UML nie udostępnia żadnego dobrego sposobu przedstawiania wielowymiarowych tablic, a właśnie taką zmienną jest tiles. Dlatego też musimy użyć normalnej asocjacji.

370

Rozdział 7.

unit *

Na razie w projekcie e trzy zdefiniowaliśmy jedyni cznie klasy, ale to i tak zna ześniej. wc więcej, niż mieliśmy

Architektura

Którą możliwością powinniśmy się zająć teraz? Skoro dysponujemy już klasą Unit, dlaczego nie moglibyśmy się zabrać za „jednostki charakterystyczne dla konkretnej gry"? Przy okazji moglibyśmy się także dokładniej przyjrzeć wzajemnym interakcjom klas Board i Unit.

Kiedy to tylko możliwe, staraj się korzystać z pracy, którą już wykonałeś. Kiedy nie miałeś niczego poza wymaganiami i kilkoma diagramami, musiałeś wybierać, od czego zacząć. Jednak teraz, kiedy dysponujesz już kodem i kilkoma klasami, znacznie łatwiej jest wybrać kolejną kluczową możliwość, związaną z tymi fragmentami systemu, które już stworzyłeś. A czy pamiętasz naszą definicję architektury?

Board

Architektura określa

?

strukturę projektu i wskazuje

najważniejsze Units

elementy aplikacji oraz wzajemne

Skoro już stworzyliśmy podstawowe klasy naszego systemu, możemy przyjrzeć się ich wzajemnym interakcjom i zacząć opierać dalsze prace na tym, co już zrobiliśmy.

związki pomiędzy nimi. Tak naprawdę nie możesz mówić o związkach pomiędzy częściami systemu, jeśli nie dysponujesz dwiema częściami, które są ze sobą powiązane. Doskonale wiemy, że klasy %RDUG i 8QLW są ze sobą powiązane oraz że „jednostki charakterystyczne dla konkretnej gry” są jedną z kluczowych możliwości tworzonego szkieletu, a zatem jest oczywiste, iż to właśnie tą możliwością się teraz zajmiemy.

jesteś tutaj  371

Poznaj to, czego jeszcze nie wiesz

Jednostki charakterystyczne dla konkretnej gry… co to może znaczyć? Najlepszym sposobem, by zrozumieć, co może oznaczać wyrażenie „jednostki charakterystyczne dla konkretnej gry”, jest odbycie krótkiej rozmowy z klientami Gerarda — czyli z projektantami gier, którzy będą używali tworzonego szkieletu. Posłuchajmy, co mają nam do powiedzenia:

W naszych grach najważniejsza jest strategia. Stosujemy zaawansowany system prowadzenia walki, w którym każda jednostka ma określoną siłę ataku, siłę obrony oraz współczynnik doświadczenia.

Ja piszę gry science-fiction, w których bitwy prowadzą wielkie statki kosmiczne i całe planety. Dlatego muszę mieć armie wyposażone w lasery i możliwość tworzenia mnóstwa różnych statków kosmicznych.

372

Rozdział 7.

Architektura

Naszych klientów interesują wyłącznie bitwy powietrzne, nawet nie potrzebujemy jednostek naziemnych. Wystarczy mi możliwość tworzenia różnych typów samolotów, o różnych szybkościach, uzbrojeniu i innych parametrach. Żadna gra wojenna nie będzie dobra bez uzbrojenia… wielu różnych rodzajów uzbrojenia. A w naszej grze jednostki mogą mieć dwa rodzaje uzbrojenia, więc będzie niezła zabawa.

Nasze gry są bardzo realistyczne, a rozgrywka zazwyczaj trwa dosyć długo… Dbamy nawet o wiek oraz o wzajemne relacje pomiędzy poszczególnymi postaciami.

Zaostrz ołówek Co oznacza określenie „jednostki charakterystyczne dla konkretnej gry”? Teraz, kiedy już poznałeś oczekiwania kilku projektantów gier, którzy mają zamiar używać szkieletu systemu gier Gerarda, powinieneś już bardzo dobrze rozumieć, z czym wiąże się druga spośród kluczowych możliwości systemu. W poniższych pustych liniach zapisz, czego, według Ciebie, będziesz potrzebował, by zapewnić możliwość tworzenia „jednostek charakterystycznych dla konkretnej gry”.

Kiedy skończysz, porównaj swoją odpowiedź ziesz z naszą — znajd ją na następnej stronie.

jesteś tutaj  373

Rozwiązania zadań

Zaostrz ołówek

Rozwiązanie

Co oznacza określenie „jednostki charakterystyczne dla konkretnej gry”?

Rozwiązania ćwiczeń

Teraz, kiedy już poznałeś oczekiwania kilku projektantów gier, którzy mają zamiar używać szkieletu systemu gier Gerarda, powinieneś już bardzo dobrze rozumieć, z czym wiąże się druga spośród kluczowych możliwości systemu. W poniższych pustych liniach zapisz, czego, według Ciebie, będziesz potrzebował, by zapewnić możliwość tworzenia „jednostek charakterystycznych dla konkretnej gry”.

Każda gra bazująca na szkielecie Gerarda wykorzystuje inne typy jednostek, posiadające odmienne atrybuty i możliwości. A zatem musimy mieć możliwość przypisywania jednostkom dowolnych właściwości o różnych typach danych.

...w końcu w symulatorach lotów będą potrzebne różnego rodzaju samoloty, odrzutowce i rakiety.

h W niektórych wojennyc ą będ ch zny gic ate str ch gra wykorzystywane czołgi i jednostki piechoty…

...natomiast w grach fantastycznych wystąpią strażnicy, magowie i rycerze…

374

Rozdział 7.

Architektura

Podobieństwa po raz kolejny Powoli zaczynamy coraz lepiej uświadamiać sobie, co oznacza określenie „jednostki charakterystyczne dla konkretnej gry”, wciąż jednak musimy określić, w jaki sposób zapewnić obsługę tej możliwości w szkielecie systemu gier Gerarda. Zacznijmy od przeanalizowania kilku rodzajów jednostek, o których wspominali klienci Gerarda, i wskazania podobieństw pomiędzy nimi.

DWWDFN  H[SHULHQFH 

po raz O podobieństwach wspomnieliśmy 309, nie pierwszy w rozdziale 6., na stro tawowe kiedy próbowaliśmy określić pods ak wymagania naszego systemu. Jedna informacje o podobieństwach możn wania iązy wykorzystać także podczas rozw „jednostki mniejszych problemów, takich jak gry”. charakterystyczne dla konkretnej

GHIHQVH 

Oto kilka przykładów jednostek charakterystycznych dla konkretnej gry, które zostały wymienione na kilku ostatnich stronach, oraz ich właściwości.

ZHDSRQ %D]RRND QDPH ĵ6LPRQĵ

Co wspólnego mają te wszystkie różne typy jednostek? Jakie są podstawowe cechy, które można podać dla każdej z nich?

VSHHG  JXQ *DWOLQJ PRGHO ĵ$7KXQGHUEROW,,ĵ

jesteś tutaj  375

Jakie są wspólne możliwości

=DJDGNDSURMHNWRZD Twoim zadaniem jest wskazanie podobieństw, a zarazem właściwości, które powinny się znaleźć w klasie Unit, oraz różnic — czyli właściwości, jakie trzeba będzie umieścić w klasach pochodnych. W przedstawionym poniżej diagramie klas zapisz wszystkie właściwości i metody, które, według Ciebie, powinny się znaleźć w klasie Unit, następnie w podobny sposób uzupełnij klasy reprezentujące jednostki charakterystyczne dla poszczególnych gier.

Unit

która klasa Unit, Oto nasza ma żadnych jeszcze nie ani metod. właściwości ej umieścić Należy w ni acje wspólne dla cechy i opertypów jednostek. wszystkich

Podobieństwa Różnice

Tank

Airplane Soldier

zapewne Większość gier będzie sę Unit. kla ą ow baz ć rza rozsze e cechy Dzięki temu wszystki konkretnego charakterystyczne dla umieszczone ą typu jednostek zostan hodnych. poc w odrębnych klasach

376

Rozdział 7.

Architektura

DWWDFN  H[SHULHQFH  GHIHQVH 

Jak sądzisz, które z tyc wydają się odnosić do h właściwości typów jednostek? Któ wszystkich re do jednostek charakter z nich należą ystycznych dla konkretnej gry?

ZHDSRQ %D]RRND QDPH ĵ6LPRQĵ

VSHHG  JXQ *DWOLQJ PRGHO ĵ$7KXQGHUEROW,,ĵ

jesteś tutaj  377

Spójrz nieco głębiej

5R]ZLÈ]DQLHQU:V]\VWNLHMHGQRVWNLVÈUöĝQH Po pierwszej, pobieżnej próbie rozwiązania naszej zagadki mógłbyś narysować schemat klas o następującej postaci:

Unit weapon: Weapon

tkie typy dę te wszys Tak napraw e posiadały żadnych jednostek ni ch. Wszystkie mają ego ny cech wspól iwości, lecz dla każd jakieś właśctek są one inne. e typu jednos asie bazowej Unit ni ci. kl oś w iw śc go ła te w Dla iśmy żadnej zdefiniowal

Ewentualnie można by dodać do klasy Unit właściwość weapon, gdyż wygląda na to, że wszystkie jednostki będą wyposażone w jakąś broń. Ale co w takim przypadku zrobić z jednostkami, n które mogą mieć więcej niż jede rodzaj uzbrojenia? Jak widać, trzeba jeszcze będzie dopracować to rozwiązanie, gdyż przysparza ono pewnych problemów.

Podobieństwa Różnice

Tank attack: float experience: float defence: float getAttack(): float setAttack(float) ...etc...

Czołgi mają kilka unikalnych właściwości, umieścimy je zatem bezpośrednio w klasie Tank.

378

Rozdział 7.

Airplane Soldier weapon: Weapon name: String getWeapon(): Weapon setWeapon(Weapon) getName(): String setName(String) ...itd.

speed: int gun: Weapon model: String getSpeed(): int setSpeed(int) getSun(): Weapon ...itd.

łeś do wniosku, Być może sam doszed ysłem będzie pom rym dob dzo bar że n; w ten stworzenie klasy Weapogdzie li mie my zie sposób będ cje dotyczące przechowywać informa obiekty tym a uzbrojenia, a poz używane Weapon będą mogły być it. Un ach ekt obi lu wie w

Architektura

To było trochę głupie… Po co zajmować się tymi wszystkimi podobieństwami, skoro typy jednostek używanych w różnych grach nie mają żadnych wspólnych cech? Wydaje się, że to czysta strata czasu.

Podobieństwa to nieco więcej niż jedynie takie same nazwy właściwości… musisz spojrzeć nieco głębiej. Pozornie może się wydawać, że różne typy jednostek używane w poszczególnych grach nie mają żadnych wspólnych właściwości; cofnijmy się jednak nieco i nie koncentrujmy wyłącznie na nazwach właściwości. Zastanówmy się, jakie cechy wspólne faktycznie mają wszystkie typy jednostek?

Każda jednostka ma określony, odmienny typ; mogą to zatem być: czołgi, jednostki piechoty, samoloty, statki kosmiczne i tak dalej. DWWDFN  H[SHULHQFH  GHIHQVH 

ZHDSRQ %D]RRND QDPH ĵ6LPRQĵ

W\SH XQLW7\SH SURSHUW\1DPH SURSHUW\9DOXH Oprócz tego każda jednostka będzie także miała kilka innych właściwości…

VSHHG  JXQ *DWOLQJ PRGHO ĵ$7KXQGHUEROW,,ĵ

...z których każda będzie posiadać jakąś wartość.

A zatem wspólną cechą wszystkich jednostek będzie posiadanie określonego typu oraz kilku właściwości, z których każda będzie zwyczajną parą nazwa-wartość.

jesteś tutaj  379

Podobieństwa zapewniają elastyczność

5R]ZLÈ]DQLHQU:V]\VWNLHW\S\MHGQRVWHNVÈLGHQW\F]QH Po pierwszej, pobieżnej próbie rozwiązania naszej zagadki mógłbyś narysować schemat klas o następującej postaci:

ap można ów aj kcie typu M W tym obiebroń (nawet kilka rodzsiłę, zapisywać dla jednej jednostki), az uzbrojenia świadczenie, wiek or ości, jakie szybkość, do ne właściw tantowi gry. in e ki el sz ek w głowy proj przyjdą do

Unit type: String properties: Map setType: String getType(): String setProperty(String, Object) getProperty(String): Object

To rozwiązanie bardzo przypomina sposób przechowywania właściwości instrumentów, który zastosowaliśmy w rozdziale 5.

Tym razem nasza klasa Unit jest znacznie bardziej ogólna. Pozwala na określenie typu jednostki i zawiera obiekt Map umożliwiający przechowywanie par nazwa-wartość.

Podobieństwa Różnice

Tank attack: float experience: float defence: float attack: float setAttack(float) ...etc...

Airplane Soldier weapon: Weapon name: String getWeapon(): Weapon setWeapon(Weapon) getName(): String setName(String) ... itd.

hkolwiek Nie ma potrzeby tworzenia jakic klasy od ch zący dzic dzie ch klas pochodny któw Unit… Teraz możemy używać obiewiedni typu Unit, określając w nich odpo typ jednostki i odpowiedni zbiór właściwości.

380

Rozdział 7.

speed: int gun: Weapon model: String getSpeed(): int setSpeed(int) getSun(): Weapon ... itd.

Architektura

Chwila, przecież to jest niedorzeczne. Najpierw nic nie było podobne, a teraz wszystko jest identyczne? Niby jakim cudem takie informacje mają mi pomóc zmniejszyć ryzyko albo pisać lepsze oprogramowanie?

Analiza podobieństw: ścieżka do elastycznego oprogramowania Zastanawiasz się, dlaczego w ogóle poświęciliśmy ten czas na rozważania związane z analizą podobieństw? Przyjrzyj się zatem pierwszemu z przedstawionych rozwiązań (patrz strona 378), a następnie drugiemu, opisanemu na poprzedniej stronie. Następnie wypełnij poniższą tabelkę i samemu przekonaj się, czy analiza podobieństw cokolwiek nam dała, jeśli chodzi o jakość projektu:

Ten pierwszy wiersz tabeli reprezentuje sytuację przedstawioną w tekście — trzy różne typy jednostek.

Liczba typów jednostek

Liczba klas jednostek — Rozwiązanie nr 1

Liczba klas jednostek — Rozwiązanie nr 2

3 5 10 25 50

Sto typów jednostek to może się wydawać dosyć dużo, jednak w dużych wojennych grach strategicznych nie jest to wcale przesadzona liczba.

100 Jak sądzisz, które rozwiązanie jest lepsze? Dlaczego:

jesteś tutaj  381

Podobieństwa i elastyczność Liczba typów jednostek

Liczba klas jednostek — Rozwiązanie nr 1

Liczba klas jednostek — Rozwiązanie nr 2

3

4

1

5

6

1

10

11

1

25

26

1

50

51

1

100

101

1

W rozwiązaniu nr 2 jedna klasa Unit obsługuje wszystkie możliwe typy jednostek, posiadające dowolną ilość właściwości i atrybutów różnych typów.

y 1 zawsze używam W rozwiązaniu nr it, a oprócz niej Un sy bazowej kla jednej klasie dla będziemy mieli ponostki. jed każdego typu

Unit

Określiliśmy, co jest wspólne dla wszystkich typów jednostek i umieściliśmy to w klasie bazowej Unit. W efekcie okazuje się, że teraz projektanci gier mogą posługiwać się tylko JEDNĄ klasą reprezentującą jednostki, a nie muszą tworzyć 25, 50 lub 100 odrębnych klas!

type: String properties: Map setType(String) getType(): String setProperty(String, Object) getProperty(String): Object Dysponując jedną zaprojektowaną , dobrze kla możemy tworzyć są Unit, liczbę różnych jeddowolną nostek wojskowych.

Dobry projekt zawsze zmniejsza ryzyko. 382

Rozdział 7.

Nie istnieją

głupie pytania

P: Rozumiem, jak to rozwiązanie mogłoby mi pomóc w moim projekcie, jednak co to wszystko ma wspólnego z redukcją ryzyka?

O: Dobry projekt zawsze zmniejsza ryzyko. Określając najlepszy

z możliwych sposobów implementacji klasy 8QLW, możemy stworzyć ją prawidłowo już za pierwszym razem… zanim zaczniemy pracować nad wszystkimi pozostałymi elementami szkieletu systemu gier, dzięki czemu możemy uniknąć konieczności wprowadzania drastycznych zmian w jej kodzie, które mogłyby mieć wpływ także na kod innych fragmentów systemu. Nie tylko udało się nam określić, co oznaczają „jednostki charakterystyczne dla konkretnej gry”, lecz także zdefiniowaliśmy podstawową klasę 8QLW, dzięki czemu inne klasy systemu, takie jak %RDUG, mogą już bezpiecznie z niej korzystać bez obawy, że jej projekt ulegnie drastycznym zmianom w połowie lub pod koniec prac nad projektem.

Architektura

Coraz więcej porządku…

Szkielet systemu

Udało się nam zrozumieć i zaimplementować kolejną z kluczowych możliwości i w jeszcze większym stopniu zredukować ryzyko niepowodzenia całego projektu. A zatem została nam już tylko jedna z kluczowych możliwości.

gier G

erarda KLUCZOWE moż liwości 1. Plansza do gr y 2. Jednostki char — istota systemu. aktery — istota system styczne dla konkretnej gry u oraz co to ozna 3. Koordynacja cza? przesuwania jedn ostek — co to je i jak to zrealizow st ać?

Zaraz, chwileczkę… Nie napisaliśmy jeszcze żadnego kodu klasy Unit. Czy nie musimy tego zrobić, zanim zajmiemy się ostatnią z kluczowych możliwości?

Koncentrujemy się jedynie na tym, co zmniejszy ryzyko. Pamiętasz zapewne, że podstawowym celem architektury jest ograniczenie ryzyka i zapewnienie porządku. W naszej aplikacji będziemy musieli zająć się jeszcze wieloma sprawami, jednak zostawiamy je na chwilę, gdy określimy już strukturę aplikacji i zmniejszymy ryzyko niepowodzenia projektu do wielkości, którą będzie można pominąć. Na razie staramy się opracować postać klasy 8QLW i dowiedzieć się, co znaczą „jednostki charakterystyczne dla konkretnej gry”. Jak na razie udało się nam określić, co następuje:

Unit Na obecnym etap prac ten diagram ie to wszystko, czeg klasy potrzeba. Określa o Ci on strukturę klasy Unit i odpowiada na py co oznaczają „je tanie, dn charakterystyczn ostki e konkretnej gry”. dla

type: String properties: Map setType(String) getType(): String setProperty(String, Object) getProperty(String): Object

jesteś tutaj  383

OOA&D a doskonałe oprogramowanie Nie istnieją

głupie pytania

P: Kiedy zajmowaliśmy się klasą

P: Czy to naprawdę dobre

P: A zatem analiza i projektowanie

Board, napisaliśmy jej kod; a teraz stwierdzacie, że nie powinniśmy pisać kodu klasy Unit. Co z tego wynika?

rozwiązanie — pytać klienta i użytkowników sytemu o to, co on powinien robić? Czy uzyskane w ten sposób informacje nie mogą wprowadzić nas w błąd lub zdekoncentrować?

obiektowe — OOA&D — nie wiążą się z pisaniem zbyt rozbudowanego kodu, nieprawdaż?

O: Na tym etapie prac nad projektem

zawsze powinieneś sobie zadawać pytanie: „Czy to, co robię, zmniejszy ryzyko niepowodzenia projektu?”. Jeśli na to pytanie możesz odpowiedzieć twierdząco, to pracuj dalej; w przeciwnym razie prawdopodobnie będziesz mógł odłożyć realizację danego problemu na późniejszy etap prac nad projektem. W przypadku klasy %RDUG musieliśmy przynajmniej w podstawowym stopniu zrozumieć, jak będzie działać plansza do gry; właśnie z tego powodu poszliśmy nieco dalej i stworzyliśmy prostą implementację tej klasy. Jeśli jednak chodzi o klasę 8QLW, to jej diagram oraz zrozumienie podstawowej funkcjonalności całkowicie nam wystarczało. W obu przypadkach zmniejszaliśmy ryzyko niepowodzenia całego projektu, a nie koncentrowaliśmy się na kodowaniu lub odłożeniu na później kodowania takiej czy innej klasy bądź pakietu.

P: Ale czy w przypadku klasy Board nie wystarczyłoby narysować diagramu klasy i na tym poprzestać, dokładnie tak samo jak zrobiliśmy dla klasy Unit?

O: Prawdopodobnie przygotowanie

diagramu klasy faktycznie by wystarczyło. W tym przypadku dużo zależy od subiektywnej opinii; jeśli jednak czujesz, że cały czas koncentrujesz się na zmniejszaniu ryzyka niepowodzenia projektu, to nic nie stoi na przeszkodzie, by poprzestać na przygotowaniu diagramu bądź przeciwnie — pójść o jeden lub dwa poziomy dalej i bardziej szczegółowo zająć się daną klasą.

384

Rozdział 7.

O: Pytanie klienta o działanie systemu

zazwyczaj jest dobrym rozwiązaniem, gdyż to przecież jego system tworzysz. A uzyskane w ten sposób informacje mogą Ci zamieszać w głowie lub skłonić do pracy nad niewłaściwym zagadnieniem tylko i wyłącznie wtedy, gdy sam nie masz pewności, czym powinieneś się zająć. Jeśli jednak rozpoczniesz rozmowę dokładnie wiedząc, do czego masz dążyć, i jeśli będziesz chciał uzyskać jedynie konkretne informacje, to taka rozmowa nie powinna Ci w żaden sposób utrudnić pracy.

P: Nie jestem pewny, czy samemu kiedykolwiek udałoby mi się wymyślić, by użyć klasy Map do przechowywania właściwości w klasie Unit.

O: Nie przejmuj się. Właśnie po to używamy

takich narzędzi jak analiza podobieństw oraz trzy „P” dotyczące architektury. Pomagają one opracować rozwiązania, o których w innych sytuacjach w ogóle byśmy nie pomyśleli, a co więcej, można je stosować we wszystkich projektach. W przypadku klasy 8QLW zastosowanie obiektu 0DS do przechowywania właściwości nie jest kluczowym zagadnieniem. Najważniejsze jest to, iż udało się nam zauważyć, że wszystkie jednostki sprowadzają się w zasadzie do typu oraz zbioru właściwości o postaci par nazwa-wartość. Kiedy już to określiliśmy, znalezienie konkretnego sposobu przechowywania tych właściwości nie przysporzyło nam najmniejszego problemu.

O: Analiza i projektowanie obiektowe

w całości dotyczą pisania kodu — pisania wspaniałego oprogramowania, i to za każdym razem. Jednak pisanie dobrego kodu nie zawsze oznacza, że od samego początku aż do końca prac nad projektem mamy tylko i wyłącznie siedzieć i pisać kod. Czasami najlepszym sposobem pisania doskonałego kodu jest odkładanie pisania tak długo, jak to tylko możliwe. Planuj, organizuj, redukuj ryzyko niepowodzenia… wszystkie te zabiegi sprawiają, że samo pisanie kodu może stać się bardzo prostym zadaniem.

Czasami najlepszym sposobem pisania doskonałego kodu jest odkładanie pisania tak długo, jak to tylko możliwe.

Architektura

Bądź autorem A zatem pozostała nam już tylko jedna kluczowa możliwość — koordynacja przesuwania jednostek. Ale zastanów się, co byś teraz zrobił, aby znaleźć rozwiązanie tego problemu? Twoim zadaniem jest określenie zawartości kilku kolejnych stron niniejszej książki i sposobu zrealizowania ostatniej z kluczowych możliwości szkieletu systemu gier. :APYTAJKLIENTA

 2OZDZIAŒ

#ZYJEJESTTOZADANIE 

!RCHITEKTURA

JESTEuTUTAJ 

!RCHITEKTURA

*Podpowiedź: Sp zajmowaliśmy sięrawdź, w jaki sposób możliwością, z któ poprzednią kluczową wiedzieliśmy, co rą początkowo nie zrobić.

 2OZDZIAŒ

JESTEuTUTAJ 

jesteś tutaj  385

Zapytaj klienta

Co to znaczy? Zapytaj klienta

zanie ćwiczenia Bądź Chcesz poznać rozwią ery kolejne strony czt j autorem? Przeczyta im stopniu Twoje i przekonaj się, w jak od tego, co myśmy zrobili. się niły róż zi ied odpow

Jeśli nie masz pewności co do tego, co naprawdę oznacza konkretna możliwość, to jedną z najlepszych rzeczy, jaką możesz zrobić, jest rozmowa z klientem. Właśnie w ten sposób postąpiliśmy, pracując nad „jednostkami charakterystycznymi dla konkretnej gry”, a zatem zróbmy tak samo i teraz, by dowiedzieć się czegoś o tym, co oznacza koordynacja przesunięć jednostek.

Wydaje się, że to jest całkiem proste…Takie obliczenia nie są skomplikowane.

Każda jednostka ma właściwość informującą, o ile pól można ją przesunąć w jednym ruchu, natomiast gra sprawdza, czy dany ruch jest dozwolony.

Nie cierpimy gier, które nie są realistyczne… na przykład takich, w których samoloty mogą przelatywać przez budynki! Nasza gra sprawdza wszystkie sąsiadujące pola, by upewnić się, że nie zajmują ich inne jednostki, a następnie modyfikuje właściwość szybkości samolotu o współczynnik wiatru.

Oni narzekają na kolejny symulator lotu, pozwalający latać w miejscach, w których w zasadzie latać nie powinieneś.

386

Rozdział 7.

ej To już jest nieco bardzi icie kow skomplikowane…i cał odmienne od wymagań ta. poprzedniego projektan

Architektura

Czy wiesz, co oznacza „koordynacja przesuwania jednostek”? Wysłuchanie klientów Gerarda powinno Ci dać stosunkowo dobry pogląd na to, czym ma być i jak powinna działać trzecia z kluczowych możliwości szkieletu systemu gier. Poniżej zapisz, co, według Ciebie, ona naprawdę oznacza:

Możesz stosować kroki zawsze wt te trzy podstawowe ed pewien, co ozna y, gdy nie jesteś cza analizowana możliwość oraz jak zaimplementować powinieneś ją w systemie.

1. Zapytaj klienta. Co ta możliwość oznacza?

A teraz przeprowadź analizę podobieństw: A teraz powinieneś postarać się określić, co wspólnego mają wszystkie scenariusze przesuwania jednostek przedstawione przez klientów Gerarda na stronie 386. Czy można wyróżnić jakieś podstawowe cechy wspólne dla wszystkich różnych typów ruchów? Jeśli uważasz, że istnieją takie podobieństwa, to zapisz je w poniższych pustych wierszach:

2. Analiza podobieństw. W jaki sposób mogę zrealizować tę możliwość

Jeśli już rozumiesz, co oznacza „koordynacja przesuwania jednostek”, i określiłeś jej cechy wspólne występujące we wszystkich grach, to powinieneś już także wiedzieć, co należy zrobić, by udostępnić tę możliwość. W poniższych pustych wierszach zapisz, co, według Ciebie, powinieneś teraz zrobić:

w tworzonym systemie?

A co powinieneś zrobić w następnej kolejności?

3. Plan implementacji. jesteś tutaj  387

Czyje jest to zadanie?

Czy tu są jakiekolwiek podobieństwa? Poniżej znajdziesz to, co według nas należy zrobić na podstawie informacji dotyczących przesuwania jednostek, uzyskanych od klientów Gerarda: Musi istnieć możliwość przesuwania jednostek z jednego pola planszy na inne. Ruchy jednostek bazują na pewnych wyliczeniach lub algorytmie, charakterystycznym dla konkretnej gry, i czasami do ich wyznaczenia potrzebne będą właściwości typowe dla konkretnej jednostki.

A zatem jakie są faktyczne podobieństwa pomiędzy wszystkimi scenariuszami przesuwania jednostek? Czy pamiętasz, co powiedzieli klienci?

Nie cierpimy gier, które nie są realistyczne… na przykład takich, w których samoloty mogą przelatywać przez budynki! Nasza gra sprawdza wszystkie sąsiadujące pola, by upewnić się, że nie zajmują ich inne jednostki, a następnie modyfikuje właściwość szybkości samolotu o współczynnik wiatru.

wany jest algorytm W tym przypadku uży ruch jest dozwolony, y określający, czy dan eko który wylicza, jak dal oraz drugi algorytm, esunąć, korzystając przy jednostka może się prz ściwości jednostki. wła tym między innymi z

Każda jednostka ma właściwość informującą, o ile pól można ją przesunąć w jednym ruchu, natomiast gra sprawdza, czy dany ruch jest dozwolony.

W tym przypadk u ruch jest dozwolo sprawdzamy, czy z jednostki infor ny, oraz odczytujemy ma można ją przesu cję, o ile pól nąć.

Jakie są podobieństwa?

Jakie są różnice?

Przed wykonaniem ruchu wykonywane jest sprawdzenie, czy dany ruch jest dozwolony.

Algorytm określający poprawność ruchu i możliwość jego wykonania jest unikalny dla każdej gry.

Odległość, na jaką jednostka może się przesunąć, jest określana na podstawie właściwości tej jednostki.

Liczba właściwości używanych do wyliczenia ruchu oraz ich nazwy mogą być różne w każdej z gier.

Na przesunięcie jednostki mają także wpływ czynniki zewnętrzne.

Czynniki zewnętrzne mające wpływ na ruchy jednostek mogą być inne w każdej z gier.

To właśnie w tym miejscu pojawiają się takie czynniki jak wiatr.

388

Rozdział 7.

Czy zauważasz tu jakiś powtarzający się wzorzec?

Architektura

To jest „inne w każdej z gier” Czy zauważyłeś jakieś stwierdzenie powtarzające się w naszym zestawieniu? Za każdym razem gdy udało się nam wskazać jakieś podobieństwo, w kolumnie zawierającej różnice pojawiał się zwrot „inne dla każdej z gier”.

Jeśli dla pewnej możliwości znajdujesz więcej różnic niż podobieństw, to być może nie istnieje żaden dobry, ogólny sposób jej realizacji.

Gerardzie, przemyśleliśmy całą sprawę dokładnie i uważamy, że obsługę przesuwania jednostek powinniśmy zostawić projektantom gier. Wszystko, co moglibyśmy zapewnić, przysporzyło by im więcej kłopotów niż pożytku.

W przypadku sy st jeśli nie istnieje emu dla Gerarda, ogólne, to rozwiązżadne rozwiązanie by zastosować, anie, które można nie powinno stać elementem szkie letu systemu gie się r.

W porządku, wydaje się, że dokładnie to przemyśleliście, więc nie ma żadnych zastrzeżeń. Poza tym projektanci gier lubią mieć dużą kontrolę nad tym, co się dzieje w ich grach.

Nie istnieją

głupie pytania

P: A czym tak naprawdę ta sytuacja różni się od „jednostek charakterystycznych dla konkretnej gry”?

O: W przypadku jednostek udało się

nam znaleźć pewne podobieństwa: każda jednostka miała swój typ i zbiór par nazwa-wartość. Jeśli jednak chodzi o przesuwanie jednostek, to wyglądało na to, że każda gra realizuje je w odmienny sposób. Dlatego pozostawienie tego problemu projektantom gier miało sens; w przeciwnym razie rozwiązanie, które moglibyśmy stworzyć, byłoby na tyle ogólne, że w praktyce okazałoby się właściwie nieprzydatne.

Jednak zadaj sobie następujące pytanie: jakie korzyści naprawdę daje Ci takie podobieństwa w obsłudze przesuwania rozwiązanie? Projektanci gier będą musieli jednostek w poszczególnych grach, nauczyć się Twoich interfejsów, a jeśli ich prawda? Chodzi mi o algorytm wyliczania gra nie będzie wymagać sprawdzania, przesunięcia oraz sprawdzanie, czy dany czy ruch jest dozwolony, będą zapewne ruch jest dozwolony. musieli przekazywać wartość QXOO zamiast obiektu /HJDO0RYH&KHFN; poza tym, : Owszem, masz rację. A zatem, jak miałby wyglądać interfejs obiektu teoretycznie rzecz biorąc, mógłbyś napisać 0RYHPHQW$OJRULWKP… Mamy wrażenie, że interfejs0RYHPHQW udostępniający metodę w rzeczywistości takie rozwiązanie jedynie PRYH , do której byłyby przekazywane zwiększa złożoność systemu, niż ją zmniejsza. obiekty 0RYHPHQW$OJRULWKPoraz A pamiętaj, że Twoim zadaniem jest /HJDO0RYH&KHFN albo jakieś podobne. zmniejszanie ryzyka niepowodzenia Następnie każdy projektant mógłby i złożoności rozwiązania, a nie powiększanie stworzyć swoje własne klasy dziedziczące ich. My uznaliśmy, że prościej będzie, jeśli od 0RYHPHQW$OJRULWKP oraz to sami projektanci gier będą obsługiwać /HJDO0RYH&KHFN. Jeśli pomyślałeś o podobnym rozwiązaniu, to nasze gratulacje ruchy jednostek i zmieniać ich położenie na — wykonałeś dobrą robotę! Jesteś naprawdę planszy (używając przy tym metod obiektu %RDUG, których im dostarczyliśmy). dobry.

P: Ale jednak można wskazać pewne

O

jesteś tutaj  389

Doskonały kod lub doskonałe oprogramowanie

Super. A zatem mam kartkę z kilkoma „odhaczonymi" możliwościami i kilka klas, które w żadnym razie nie są dokończone, no i całą masę różnych diagramów UML. I mam uwierzyć, że właśnie w taki sposób piszecie doskonałe oprogramowanie?

Oczywiście! Pamiętaj, że doskonałe oprogramowanie to coś więcej niż doskonały kod.

Klienci nie płacą Ci za doskonały kod, płacą za doskonałe oprogramowanie.

390

Rozdział 7.

Doskonały kod cechuje się dobrym projektem oraz tym, że generalnie rzecz biorąc działa zgodnie z oczekiwaniami. Jednak doskonałe oprogramowanie nie tylko musi być dobrze zaprojektowane; oprócz tego powinno być gotowe w odpowiednim czasie i zawsze musi robić dokładnie to, czego naprawdę chce klient. I właśnie w tym pomaga nam architektura — w zmniejszeniu ryzyka, że oprogramowanie nie zostanie dostarczone na czas bądź że nie będzie działać tak, jak sobie tego życzy klient. Nasza lista kluczowych możliwości, diagramy klas oraz wszystkie częściowo napisane klasy pomagają nam zapewnić, że nie tylko dostarczymy doskonały kod, lecz także doskonałe oprogramowanie.

Architektura

Zmniejszanie ryzyka pomaga pisać wspaniałe oprogramowanie

Szkielet systemu gier Gerarda KLUCZOWE możliwości 1. Plansza do gry — istota systemu. 2. Jednostki charakterystyczne dla konkretnej gry — istota systemu oraz co to oznacza? 3. Koordynacja przesuwania jednostek — co to jest i jak to zrealizować?

Skoro udało się już nam opracować wszystkie trzy kluczowe możliwości szkieletu, poradziliśmy sobie z największymi zagrożeniami związanymi z realizacją projektu. Przyjrzyjmy się, jak każda z czynności, jakie wykonaliśmy w tym rozdziale, przyczyniała się do zmniejszenia ryzyka niepowodzenia projektu: Oto gdzie zaczynaliśmy Wiedzieliśmy, co musim. napisać, i niewiele wię y cej

.

FODVV FODVV 7LOH 8QLW^ ^JH 8QLW ` FODVV W8QLW ` %RDUG ` ^JH W8QLW `

Unit.java

Tile.java

Board.java

Unit

liśmy, Następnie określi ostki dn co oznaczają „je e charakterystyczn y”, dla konkretnej gramu i używając diagr liśmy, jak klasy, zaplanowa żliwość. mo zrealizujemy tę

W końcu zastosowaliśmy analizę podobieństw, by zauważyć, że obsługą przesuwania jednostek po planszy powinien się zająć projektant gry, a nie nasz szkielet. W ten sposób zadbaliśmy o ostatnią z kluczowych możliwości systemu.

type: String properties: Map setType(String) getType(): String setProperty(String, Object) getProperty(String): Object

Najmniejszych szans na zdążenie w terminie.

Wielki Ryzyko-metr

Opracowaliśmy podstawowe klasy związane z planszą, lecz napisaliśmy tylko tyle kodu, ile było konieczne, by zmniejszyć ryzyko, że plansza będzie działać nieprawidłowo.

Jeden procent szans, by system działał prawidłowo.

Jedynie kilka rzeczy może poważnie nawalić.

Tak blisko ideału, jak tylko oprogramowanie może być! zbyt Fakt, że nie napisaliśmy jeszcze wiele kodu, jednak mamy projekt, który najprawdopodobniej pozwoli nam dostarczyć na czas aplikacjeymi dysponujące wszystkimi niezbędn możliwościami funkcjonalnymi.

jesteś tutaj  391

Co nas czeka dalej?

CELNE SPOSTRZEŻENIA

392

Rozdział 7.

Q

Architektura pomaga przekształcić wszystkie diagramy i listy możliwości w dobrze uporządkowaną aplikację.

Q

Te możliwości sytemu, które są najważniejsze dla projektu, mają także znaczenie dla architektury.

Q

Należy koncentrować się na możliwościach stanowiących istotę systemu, których znaczenia nie do końca rozumiemy, bądź też takich, których nie wiemy, jak zaimplementować.

Q

Wszystkie czynności wykonywane na etapie określania architektury systemu powinny zmniejszać ryzyko niepowodzenia całego projektu.

Q

Jeśli nie potrzebujesz wszystkich szczegółów, z jakimi wiąże się pisanie przypadków użycia, to napisanie scenariusza określającego sposób korzystania z aplikacji powinno Ci pomóc w szybkim i prostym określeniu wymagań.

Q

Jeśli nie jesteś całkowicie pewny, co oznacza pewna możliwość, powinieneś zapytać o to klienta, a następnie uogólnić uzyskaną odpowiedź, by zrozumieć przeznaczenie i działanie tej możliwości.

Q

Skorzystaj z analizy podobieństw, by tworzone oprogramowanie było elastyczne.

Q

Klientom znacznie bardziej zależy na oprogramowaniu, które robi to, czego chcą i jest dostarczone na czas, niż na kodzie, który, według programisty, jest genialnie napisany.

Architektura

Zaostrz ołówek

Rozwiązanie

Czego brakuje w klasie Board ?

Przyjrzyj się uważnie scenariuszowi przedstawionemu na poprzedniej stronie. Czy wymagania podane na stronie 360 obejmują wszystkie operacje wykonywane w scenariuszu? Jeśli uważasz, że brakuje jakichś operacji, to zapisz je w poniższych pustych wierszach, a następnie dodaj do pliku %RDUGMDYD kod implementujący te możliwości funkcjonalne.

W scenariuszu jest mowa o usuwaniu jednostek z planszy, jednak na stronie 360 nie podano żadnego wymagania związanego z tą operacją.

W celu zrealizowania tego wymagania dodaliśmy do pliku Board.java metody removeUnit() oraz removeUnits().

jesteś tutaj  393

394

Rozdział 7.

=DVDG\SURMHNWRZDQLD

Oryginalność jest przereklamowana Hej, maleńka, niech ktoś lepiej zadzwoni do nieba, bo chyba zgubił im się jeden anioł!

Słyszałam ten tekst już chyba z tysiąc razy, ale przyznam, że wciąż na mnie działa!

Powielanie jest najlepszą formą unikania głupoty. Nic chyba nie daje większej satysfakcji niż opracowanie całkowicie nowego i oryginalnego rozwiązania problemu, który męczy nas od wielu dni… aż do czasu, gdy okaże się, że ktoś rozwikłał ten sam problem już wcześniej, a co gorsza — zrobił to znacznie lepiej niż my. W tym rozdziale przyjrzymy się kilku zasadom projektowania, które udało się sformułować podczas tych wszystkich lat stosowania komputerów, i dowiemy się, w jaki sposób mogą one sprawić, że staniesz się lepszym programistą. Porzuć ambitne myśli o „zrobieniu tego lepiej” — ten rozdział pokaże Ci, jak pisać programy sprytniej i szybciej.

to jest nowy rozdział  395

Czym jest zasada projektowania?

Zasada projektowania — w skrócie Do tej pory całą uwagę poświęcałeś tym wszystkich czynnościom, jakie powinieneś wykonać, zanim przystąpisz do kodowania aplikacji, czyli: gromadzeniu wymagań, analizie, tworzeniu listy możliwości i rysowaniu diagramów przypadków użycia. Jednak bez wątpienia kiedyś nadejdzie moment, gdy będziesz musiał zabrać się za pisanie kodu. I właśnie w tym momencie zasady projektowania nabierają ogromnego znaczenia.

Zasada projektowania to proste narzędzie lub technika, które można zastosować podczas projektowania lub pisania kodu w celu poprawienia łatwości jego utrzymania, rozbudowy lub zwiększenia jego elastyczności.

W poprzednich rozdziałach poznaliśmy już kilka zasad projektowania:

Zasady projektowania obiektowego Poddawaj hermetyzacji to, co się zmienia. Stosuj interfejsy, a nie implementacje. Dla każdej klasy w aplikacji powinien istnieć tylko jeden powód do wprowadzania zmian. Klasy służą do reprezentowania zachowań i funkcjonalności.

W tym rozdziale przyjrzymy się kilku kolejnym zasadom projektowania i przekonamy się, w jaki sposób każda z nich może poprawić zarówno projekt kodu, jak i jego implementację. Jak sam zobaczysz, okaże się, że czasami będziesz nawet musiał wybierać spośród dwóch zasad… no, ale nieco wyprzedzamy fakty. Zacznijmy zatem od przedstawienia pierwszej z naszych zasad projektowania.

396

Rozdział 8.

Stosowanie sprawdzonych zasad projektowania obiektowego pozwala na stworzenie oprogramowania, które będzie łatwiejsze w utrzymaniu, bardziej elastyczne i łatwiejsze do modyfikacji.

Zasady projektowania

Zasada nr 1:

zasada otwarte-zamknięte Naszą pierwszą zasadą jest zasada otwarte-zamknięte (ang. Open-Close Principle, w skrócie 2&3). Jest ona bezpośrednio związana z ]H]ZDODQLHPQDUR]EXGRZÚ, jednak w taki sposób, by QLH Z\PDJDïRWRPRG\ILNRZDQLDLVWQLHMÈFHJRNRGX. Poniżej podaliśmy definicję tej zasady:

Zasada otwarte-zamknięte Klasy powinny być otwarte na rozbudowę i zamknięte na modyfikacje. klasę, Możesz zamknąć innym ąc iaj liw oż uniem dyfikacji wprowadzanie mo kodzie. m cy ają iał w jej dz

Zamknięte dla modyfikacji… Załóżmy, że dysponujesz klasą o określonym zachowaniu, które zostało już zakodowane i działa prawidłowo. Zadbaj o to, by nikt nie mógł zmienić kodu Twojej klasy, a tym samym sprawisz, że jej zachowanie stanie się zamknięte na modyfikacje. Innymi słowy, nikt nie będzie mógł zmienić tego zachowania, gdyż zamknąłeś je w klasie, która na pewno nie ulegnie żadnym modyfikacjom.

:UöFÚ

Proszę zajrzeć jeszcze raz

…a jednocześnie otwarte na rozbudowę A teraz załóżmy, że w pewnej chwili pojawi się ktoś, kto będzie musiał zmienić zachowanie Twojej klasy. Naprawdę nie życzysz sobie, by inne osoby modyfikowały Twój doskonały pod każdym względem kod, który działa prawidłowo niemal we wszystkich sytuacjach… lecz jednocześnie chcesz, by inni mogli korzystać z tego kodu i rozbudować go w razie potrzeby. I dlatego zapewniasz możliwość tworzenia klas pochodnych, w których będzie można przesłonić wybrane metody i dostosować ich działanie do bieżących potrzeb. A zatem, choć nie można modyfikować kodu klasy, to jednak wciąż jest ona otwarta na rozbudowę.

Otwierasz klasy, umożliwiając ich rozbudowę oraz tworzenie klas pochodnych.

jesteś tutaj  397

OCP w działaniu

Czy pamiętasz prace nad instrumentami strunowymi Ryśka? Być może nawet nie zdajesz sobie z tego sprawy, jednak stosowałeś już OCP w rozdziale 5., pisząc klasy InstrumentSpec podczas prac nad aplikacją dla Ryśka.

GuitarSpec InstrumentSpec model: String getBuilder(): Builder getModel(): Model getType(): Type getBackWood(): Wood getTopWood(): Wood matches (InstrumentSpec): boolean

abstrakcyjną InstrumentSpec jest ona uje fini De ą. ow baz są kla posiada ra któ ), es( tch ma ę metod awdzającą spr ę acj ent lem prostą imp w. ntó me tru ins specyfikacje

numStrings: int getNumStrings(): int matches(GuitarSpec): boolean

MandolinSpec

Te fragmenty metody matches(), które zmieniają się w klasach pochodnych, zostały wydzielone i usunięte z klasy bazowej InstrumentSpec.

getStyle(): Style matches(MandolinSpec): boolean Każda z tych klas pochodnych związanych z konkretnym typem instrumentów przesłania metodę matches()… W obu przypadkach nowa wersja metody używa wersji zdefiniowanej w klasie bazowej, lecz oprócz tego wykonuje dodatkowe operacje związane z porównywaniem instrumentów konkretnego typu.

Klasa InstrumentSpec jest zamknięta na modyfikacje; metoda matches() została zdefiniowana w klasie bazowej i nie zmienia się. Jednocześnie klasa InstrumentSpec jest otwarta na rozbudowę, gdyż wszystkie jej klasy pochodne mogą zmieniać zachowanie metody matches(). 398

Rozdział 8.

Zasady projektowania

OCP krok po kroku Wróćmy do tego, co zrobiliśmy w rozdziale 5., i szczegółowo przeanalizujmy to pod kątem OCP: 1

Zaimplementowaliśmy podstawową wersję metody matches() w klasie InstrumentSpec() i zamknęliśmy ją na modyfikacje. Ta wersja metody PDWFKHV działa całkiem dobrze i nie chcemy, by ktokolwiek ją zmieniał. Innymi słowy, kiedy zakończyliśmy pisanie kodu klasy ,QVWUXPHQW6SHF, a wraz z nią i metody PDWFKHV , to jej kod nie powinien już być w żaden sposób modyfikowany.

InstrumentSpec model: String getBuilder(): Builder getModel(): Model getType(): Type getBackWood(): Wood getTopWood(): Wood matches (InstrumentSpec): boolean

2

iem działa całk Ta metoda tem nie chcemy, dobrze, a za k ją tykał. by ktokolwie

Jednak musimy zmodyfikować metodę matches(), by przystosować ją do operowania na klasach reprezentujących specyfikacje konkretnych instrumentów. Chociaż metoda PDWFKHV doskonale radzi sobie z porównywaniem wielu obiektów ,QVWUXPHQW6SHF, to jednak zawodzi w przypadku porównywania gitar i mandolin. A zatem, choć metoda ta jest zamknięta na modyfikacje, musimy mieć jakiś sposób, by ją rozszerzyć i zmienić… W przeciwnym razie okaże się, że klasa ,QVWUXPHQW6SHF nie jest zbyt elastyczna, a to byłby poważny problem.

3

A zatem rozszerzyliśmy klasę InstrumentSpec i przesłoniliśmy metodę matches(), by zmienić jej działanie. Nie chcemy zmieniać kodu w klasie ,QVWUXPHQW6SHF, jednak możemy go rozszerzyć — czyli stworzyć klasy pochodne *XLWDU6SHF oraz 0DQGROLQ6SHF, a następnie przesłonić w każdej z nich metodę PDWFKHV i dostosować do porównywania instrumentów konkretnego typu.

InstrumentSpec

GuitarSpec

model: String

numStrings: int

getBuilder(): Builder getModel(): Model getType(): Type getBackWood(): Wood getTopWood(): Wood matches (InstrumentSpec): boolean

getNumStrings(): int matches(GuitarSpec): boolean

Nie zmieniamy orygin aln wersji metody matches( ej )…

sę ...lecz rozszerzamy klatak InstrumentSpec, co i entować pozwoli nam zaimplem y nowe zachowanie metod matches().

jesteś tutaj  399

Więcej niż dziedziczenie

Rany, dziedziczenie daje ogromne możliwości. Czy to naprawdę ma być jakaś super zasada projektowania? Dajcie spokój.

Zasada OCP dotyczy elastyczności i wykracza poza samo dziedziczenie. Bez wątpienia to prawda, że dziedziczenie jest prostym przykładem zasady otwarte-zamknięte, jednak zasada ta to znacznie więcej niż jedynie definiowanie klas pochodnych i przesłanianie metod. Za każdym razem, gdy napiszesz działający kod, zapewne zechcesz zrobić wszystko, co w Twojej mocy, by upewnić się, że będzie on działać także w przyszłości… a to oznacza, że będziesz musiał zabezpieczyć go przed potencjalnymi zmianami. Jednak może się zdarzyć, że konieczne będzie wprowadzenie zmian w gotowym kodzie, na przykład po to, by zapewnić jego prawidłowe działanie w jednej lub dwóch szczególnych sytuacjach. W takich przypadkach, zamiast wprowadzania kilku zmian w istniejącym i działającym kodzie, OCP pozwala rozbudowywać ten kod bez jego zmiany. OCP można realizować na wiele różnych sposobów i choć niejednokrotnie dziedziczenie jest najłatwiejsze do zaimplementowania, to bez wątpienia nie jest to jedyna możliwość, jaką dysponujemy. Przekonasz się o tym w dalszej części rozdziału, poświęconej kompozycji.

400

Rozdział 8.

Zasady projektowania

Zaostrz ołówek Znajdź przykład zastosowania OCP w swoim własnym projekcie. Przeanalizuj projekt, nad którym aktualnie pracujesz. Czy możesz w nim znaleźć jakieś przykłady zastosowania OCP? Jeśli tak, to napisz, w jaki sposób jej użyłeś.

A teraz zastanów się i spróbuj wskazać w swoim projekcie takie miejsca, w których powinieneś zastosować zasadę otwarte-zamknięte, lecz jeszcze tego nie zrobiłeś. W poniższych pustych wierszach zapisz, co według Ciebie powinieneś zrobić, by zastosować tę zasadę:

Nie istnieją

głupie pytania

P: Czy to taka wielka różnica

P: Czy zasada OCP nie jest jedynie

P: A zatem jedynym sposobem

zmieniać kod klasy bazowej bądź w samodzielnie napisanych klasach pochodnych?

kolejną postacią hermetyzacji?

zastosowania OCP jest rozszerzanie innych klas?

O: Kiedy już napiszesz klasę i zostanie

ona zastosowana w aplikacji, to naprawdę nie będziesz chciał jej zmieniać, chyba że będzie to absolutnie konieczne. Pamiętaj jednak, że ZMIANA jest pewnikiem programowania. Dzięki zasadzie otwarte-zamknięte zapewniamy możliwość wprowadzania zmian poprzez rozszerzanie, a nie poprzez modyfikowanie już istniejącego kodu. Klasy pochodne mogą rozbudowywać i rozszerzać zachowanie klas bazowych, i to bez modyfikowania ich działającego kodu, co bez wątpienia bardzo ucieszy klientów.

O: W rzeczywistości jest to kombinacja

hermetyzacji i wyodrębniania. Określasz bowiem zachowania, które pozostają takie same, wyodrębniasz je i definiujesz w klasie bazowej, uniemożliwiając tym samym modyfikowanie tego kodu. Z kolei potem, kiedy będziesz potrzebował nowego lub zmienionego zachowania, wystarczy, że stworzysz klasę pochodną, która obsłuży te odmienne zachowania. Właśnie w tym miejscu na scenę wkracza hermetyzacja: hermetyzujesz to, co ulega zmianom (czyli zachowanie — umieszczasz je w klasach pochodnych), oddzielając od tych fragmentów aplikacji, które pozostają niezmienione (czyli od wspólnego zachowania zdefiniowanego w klasie bazowej).

O: Nie. Zasada ta jest stosowana,

ilekroć kod jest zamknięty na modyfikacje i jednocześnie otwarty na rozbudowę. Na przykład, gdybyś miał w klasie kilka metod prywatnych, to można by rzec, że są one zamknięte na modyfikacje; żaden inny kod nie może ich w żaden sposób zmienić. Jednak nic nie stoi na przeszkodzie, byś dodał do klasy kilka metod publicznych, które będą wywoływać te metody prywatne na różne sposoby. A zatem rozszerzasz zachowanie metod prywatnych, bez jednoczesnego modyfikowania ich kodu. Oto kolejny przykład OCP.

jesteś tutaj  401

Nie powtarzaj się

Zasada nr 2: nie powtarzaj się Następna zasada nosi nazwę: nie powtarzaj się i będziemy ją oznaczali skrótem DRY (od angielskich słów: Don’t Repeat Yourself). Także i ta zasada brzmi całkiem prosto, lecz ma kluczowe znaczenie dla powstawania kodu, który będzie łatwy w utrzymaniu i pozwoli na wielokrotne stosowanie.

Nie powtarzaj się (DRY) Unikaj powtarzania kodu poprzez wyodrębnianie wspólnych fragmentów i umieszczanie ich w jednym miejscu.

Podstawowym miejscem do zastosowania zasady DRY… Widziałeś już zasadę DRY w działaniu, choć być może nawet nie zdajesz sobie z tego sprawy. Zastosowaliśmy ją w rozdziale 2., kiedy to Tadek i Janka poprosili nas o zrobienie drzwiczek dla psa wyposażonych w możliwość automatycznego zamykania po upływie określonego czasu.

SXEOLFYRLGSUHVV%XWWRQ ^ 6\VWHPRXWSULQWOQ ĵ1DFLĂQLÚWRSU]\FLVNQDSLORFLHĵ  LI GRRULV2SHQ ^ GRRUFORVH  `HOVH^ GRRURSHQ  ILQDO7LPHUWLPHU QHZ7LPHU  WLPHUVFKHGXOH QHZ7LPHU7DVN ^ SXEOLFYRLGUXQ ^ GRRUFORVH  WLPHUFDQFHO  ` `  ` `

402

Rozdział 8.

Remote.java

SXEOLFYRLGUHFRJQL]H 6WULQJEDUN ^ 6\VWHPRXWSULQWOQ ĵ %DUN5HFRJQL]HU8Vï\V]DQRIJĵ  EDUNĵijĵ  GRRURSHQ  ILQDO7LPHU WLPHU QHZ7LPHU  WLPHUVFKHGXOH QHZ7LPHU7DVN ^ SXEOLFYRLGUXQ  ^ GRRUFORVH  WLPHUFDQFHO  ` `  `

Darek zasugerował, byśmy powielili ten sam kod w klasie l BarkRecognizer… jednak w myś zasady DRY byłby to ZŁY pomysł.

FODVV 5HPRWH^ SUHVV %XWWRQ `

Pamiętasz, kiedy mieliśmy już goto w klasie Remote automatycznego wy kod służący do dla psa po pewn zamykania drzwiczek ym czasie?

FODVV %DUN5HF RJQL]HU ^ XSGDWH `

BarkRecognizer.java

Zasady projektowania

1. Wyodrębnijmy wspólny kod. Stosując zasadę DRY, musimy w pierwszej kolejności wskazać wspólny kod występujący w klasach 5HPRWH i %DUN5HFRJQL]HU i umieścić go w jednym miejscu. Już w rozdziale 2. doszliśmy do wniosku, że najlepszym miejscem, w jakim można umieścić ten kod, jest klasa 'RJ'RRU: SXEOLFYRLGRSHQ ^ 6\VWHPRXWSULQWOQ ĵ'U]ZLF]NLGODSVD]RVWDï\RWZRU]RQHĵ 

Opierając się na zasadzie DRY, usunęliśmy cały ten kod z klas Remote oraz BarkRecognizer i umieściliśmy go w jednym miejscu: klasie DogDoor. A zatem nie ma już powtarzającego się kodu i uniknęliśmy problemów, jakie mogłyby wystąpić podczas utrzymania aplikacji.

RSHQ WUXH ILQDO7LPHUWLPHU QHZ7LPHU  WLPHUVFKHGXOH QHZ7LPHU7DVN ^ SXEOLFYRLGUXQ ^ GRRUFORVH  WLPHUFDQFHO  ` `  FODVV 'RJ'RRU ^ RSHQ `

`

DogDoor.java

2. Teraz usuńmy ten kod z innych miejsc aplikacji… 3. …i odwołajmy się do kodu z kroku 1. Kolejne dwie czynności są wykonywane jednocześnie. Umieść cały kod, który wyodrębniłeś w kroku 1., i w razie potrzeby jawnie się do niego odwołaj:

Najpierw pozbyliśmy się całego tego kodu… teraz znajduje się on w metodzie open() klasy DogDoor.

SXEOLFYRLGUHFRJQL]H 6WULQJEDUN ^ 6\VWHPRXWSULQWOQ ĵ%DUN5HFRJQL]HU8Vï\V]DQRIJĵ EDUNĵijĵ  GRRURSHQ  ILQDO7LPHUWLPHU QHZ7LPHU  WLPHUVFKHGXOH QHZ7LPHU7DVN ^ SXEOLFYRLGUXQ ^ GRRUFORVH  WLPHUFDQFHO  ` `  `

Nie musimy odwoływa się do wyodrębnionegoć kodu w sposób jawny… wszystkim zajmuje się już nasza metoda doo r. open().

FODVV %DUN5HF RJQL]HU ^ XSGDWH `

BarkRecognizer.java

jesteś tutaj  403

Stosowanie zasady DRY we właściwy sposób

Zasada DRY dotyczy obsługi JEDNEGO wymagania w JEDNYM miejscu Wyodrębnienie powielającego się kodu jest doskonałym punktem wyjściowym do stosowania zasady DRY, jednak to nie wszystko. Dążąc do uniknięcia powielania kodu, tak naprawdę staramy się zapewnić, by jedna możliwość, bądź jedno wymaganie, były implementowane tylko w jednym miejscu aplikacji. Jeśli chodzi o aplikację do obsługi drzwiczek dla psa, to możliwością, którą mieliśmy zaimplementować, było automatyczne zamykanie drzwiczek po upływie pewnego czasu:

Drzwiczki dla psa, dla Tadka i Janki, wersja 2.0 Lista wymagań 1. Górna krawędź otworu drzwiczek musi być umieszczona co najmniej na wysokości 30 centymetrów. 2. Naciśnięcie przycisku na pilocie powoduje otworzenie drzwiczek, jeśli te są zamknięte, lub ich zamknięcie, jeśli są otwarte. 3. Po otworzeniu drzwiczek powinny one zostać automatycznie zamknięte, jeśli wcześniej nie zostaną zamknięte przez użytkownika.

Oto wymaganie, którym się obec na nie koncentrujemy.

Początkowo jednak możliwość tę zaimplementowaliśmy w dwóch miejscach, w plikach 5HPRWHMDYD oraz %DUN5HFRJQL]HUMDYD. FODVV 5HPRWH SUHVV %XWWRQ `

FODVV %DUN5HF RJQL]HU ^ XSGDWH `

Remote.java

BarkRecognizer.java

SUHVV%XWWRQ

UHFRJQL]H

Kod automatycznie zamykający drzwiczki jest umieszczony w OBU tych metodach.

Stosując zasadę DRY, usunęliśmy powtarzające się fragmenty kodu. Jednak, co ważniejsze, umieściliśmy implementację naszego wymagania — automatycznego zamykania drzwiczek — nie w dwóch miejscach, lecz w jednym miejscu: FODVV 'RJ'RRU ^ RSHQ `

RSHQ

DogDoor.java

404

Rozdział 8.

Teraz kod odpowiedzia automatyczne zamyka lny za nie drzwiczek dla psa jest umieszcz ony tylko w JEDNYM miejscu: open() klasy DogDoor. metodzie

Zasady projektowania

Nie istnieją

głupie pytania

P: A zatem zasada DRY dotyczy powielania kodu i unikania stosowania techniki „kopiuj-i-wklej”?

O: Zasada DRY faktycznie dotyczy unikania powielania kodu,

jednak w równie dużym stopniu wiąże się ona ze stosowaniem takich rozwiązań, które nie przysporzą nam większej liczby problemów na późniejszych etapach realizacji projektu. Zamiast umieszczać powtarzający się kod w jednej klasie, powinieneś się upewnić, że każda informacja oraz zachowanie dostępne w systemie zostanie zdefiniowane w jednym, logicznym i sensownym miejscu. Dzięki temu system zawsze będzie dokładnie wiedział, gdzie szukać potrzebnej informacji lub zachowania.

P: Jeśli zasada DRY wiąże się z możliwościami i wymaganiami, to czy nie powinniśmy stosować jej także podczas ich gromadzenia, a nie jedynie w trakcie pisania kodu?

O: Oczywiście, to wspaniały pomysł! Niezależnie od tego, czy

piszesz wymagania, opracowujesz przypadki użycia, czy też piszesz kod aplikacji, zawsze będziesz chciał mieć pewność, że nie powielasz żadnych fragmentów systemu. Każde wymaganie powinno być zaimplementowane tylko jeden raz, funkcjonalności opisywane przez przypadki użycia nie powinny się powielać, a fragmenty kodu — powtarzać w kilku miejscach aplikacji. Zasada DRY ma znacznie szersze zastosowanie i nie dotyczy wyłącznie samego kodu.

Zasada DRY zaleca, by każda informacja oraz zachowanie dostępne w systemie były umieszczane w jednym, sensownym miejscu.

P: A wszystko to ma na celu uniknięcie problemów z utrzymaniem aplikacji w przyszłości, tak?

O: Owszem. Jednak nie sprowadza się to wyłącznie do unikania

konieczności zmieniania kodu w więcej niż jednym miejscu aplikacji. Pamiętaj, że zasada zaleca, by każda informacja i zachowanie w systemie miały tylko jedno źródło. Jednak musi ono być sensowne i przemyślane! Zapewne nie chciałbyś, by analizator dźwięków był jedynym miejscem odpowiedzialnym za zamykanie drzwiczek dla psa, nieprawdaż? Czy sądzisz, że drzwiczki powinny za każdym razem prosić analizator, by je zamknął? A zatem zasada DRY nie polega jedynie na unikaniu powtórzeń, lecz także na podejmowaniu właściwych decyzji dotyczących podziału funkcjonalności systemu.

jesteś tutaj  405

Stosowanie zasady DRY do określania wymagań

=DJDGNDSURMHNWRZD Zasada DRY nie ogranicza się jedynie do odnajdywania i usuwania z systemu powtarzających się fragmentów kodu. Można ją także stosować w odniesieniu do możliwości i wymagań. Nadszedł zatem czas, byś samodzielnie spróbował użyć jej w praktyce, i to nie tylko do poprawienia kodu. 3UREOHP Tadek i Janka wpadli na pomysł wyposażenia swoich drzwiczek dla psa w kolejną możliwość. Twoim zadaniem jest upewnienie się, że zagadnienia zapisane na naszej liście wymagań nie powielają się oraz że każda z możliwości projektowanego systemu będzie obsługiwana wyłącznie w jednym miejscu. 7ZRMH]DGDQLD 1

Uważnie przeczytaj listę wymagań i możliwości, przedstawioną na następnej stronie. Te spośród wymagań i możliwości, które zostały dodane po tym, gdy zakończyliśmy prace nad systemem w rozdziale 4., wyróżniliśmy pogrubioną czcionką.

2

Przyjrzyj się nowym możliwościom i wymaganiom, i zastanów się, czy zauważasz ewentualne powtórzenia w nowych fragmentach aplikacji, które będziesz musiał zaimplementować.

3

Do listy możliwości i wymagań dopisz notatki z informacjami o tym, co według Ciebie się powtarza.

4

Powtarzające się wymagania zapisz ponownie u dołu listy w taki sposób, by zlikwidować powtórzenia.

5

W ramce zamieszczonej poniżej zapisz nową definicję zasady DRY i upewnij się, że nie wspomniałeś w niej jedynie o unikaniu powielania kodu. Tutaj za definicję pisz swoją włas wszystko zasady DRY, zaną na kilku , czego się dow wierającą ied ostatnich stronach ziałeś .

Nie powtarzaj się (DRY)

406

Rozdział 8.

Zasady projektowania

Drzwiczki dla psa, dla Tadka i Janki, wersja 3.0 Lista wymagań i możliwości

e agania, któr To są wym wcześniej… eś ał zn już po

...a to są nowe wymagania i mo żliwości drzwiczek.

1. Górna krawędź otworu drzwiczek musi być umieszczona co najmniej na wysokości 30 centymetrów. 2. Naciśnięcie przycisku na pilocie powoduje otworzenie drzwiczek, jeśli te są zamknięte, lub ich zamknięcie, jeśli są otwarte. 3. Po otworzeniu drzwiczek powinny one zostać automatycznie zamknięte, jeśli wcześniej nie zostaną zamknięte przez użytkownika. 4. System rozpoznawania dźwięków musi być w stanie określić, kiedy pies szczeka. 5. System rozpoznawania dźwięków musi być w stanie otworzyć drzwiczki, kiedy pies zacznie szczekać. 6. Drzwiczki powinny ostrzegać właściciela, kiedy coś wewnątrz domu znajdzie się zbyt blisko nich, by mogły się otworzyć bez uderzania w ten przedmiot. 7. W pewnych, określonych godzinach drzwiczki mają być cały czas otworzone. 8. Drzwiczki można zintegrować z domowym systemem alarmowym, by zagwarantować poprawne działanie tego systemu, gdy pies będzie wchodzić do domu i wychodzić z niego. 9. Drzwiczki powinny informować właściciela sygnałem dźwiękowym, jeśli na zewnątrz domu znajdzie się coś, co będzie je blokować i uniemożliwiać pełne otworzenie. 10. Drzwiczki będą rejestrować, ile razy pies wychodzi z domu i wchodzi do niego. 11. Kiedy pies wejdzie do domu, domowy system alarmowy powinien się aktywować, o ile był aktywny przed otworzeniem drzwiczek. W tym pustym we miejscu zapisz no e ion aw pr po lub wymagania, w których nic nie zać. ar będzie się powt

5R]ZLÈ]DQLH]QDMG]LHV]QDQDVWÚSQHMVWURQLH

jesteś tutaj  407

Nie powtarzaj niczego

=DJDGNDSURMHNWRZD5R]ZLÈ]DQLH Zasada DRY nie ogranicza się jedynie do odnajdywania i usuwania z systemu powtarzających się fragmentów kodu. Można ją także stosować w odniesieniu do możliwości i wymagań. Nadszedł zatem czas, byś samodzielnie spróbował użyć jej w praktyce, i to nie tylko do poprawienia kodu. 3UREOHP Tadek i Janka wpadli na pomysł wyposażenia swoich drzwiczek dla psa w kolejną możliwość. Twoim zadaniem jest upewnienie się, że zagadnienia zapisane na naszej liście wymagań nie powielają się oraz że każda z możliwości projektowanego systemu będzie obsługiwana wyłącznie w jednym miejscu. 7ZRMH]DGDQLD 1

Uważnie przeczytaj listę wymagań i możliwości, przedstawioną na następnej stronie. Te spośród wymagań i możliwości, które zostały dodane po tym, gdy zakończyliśmy prace nad systemem w rozdziale 4., wyróżniliśmy pogrubioną czcionką.

2

Przyjrzyj się nowym możliwościom i wymaganiom, i zastanów się, czy zauważasz ewentualne powtórzenia w nowych fragmentach aplikacji, które będziesz musiał zaimplementować.

3

Do listy możliwości i wymagań dopisz notatki z informacjami o tym, co według Ciebie się powtarza.

4

Powtarzające się wymagania zapisz ponownie u dołu listy w taki sposób, by zlikwidować powtórzenia.

5

W ramce zamieszczonej poniżej zapisz nową definicję zasady DRY i upewnij się, że nie wspomniałeś w niej jedynie o unikaniu powielania kodu. Oto nasz zasady Da wersja definic ji RY.

Nie powtarzaj się (DRY) Zasada DRY zaleca, by każda informacja oraz zachowanie dostępne w systemie były umieszczane w jednym, sensownym miejscu.

408

Rozdział 8.

Zasady projektowania

Drzwiczki dla psa, dla Tadka i Janki, wersja 3.0 Lista wymagań i możliwości

al i 9. są niem Punkty 6. Pierwszy identyczne. czy przeszkód z nich doty się znajdujących mu, a drugi wewnątrz do trz; jednak — na zewnąpadkach w obu przy możliwości e podstawow są funkcjonalne identyczne.

Wymagania 7. i w porządku, dlate10. były pozostały w niezmgo ienionej postaci.

Oto w jaki sposób połączyliśmy i przeredagowaliśmy punkty 6. i 9.

1. Górna krawędź otworu drzwiczek musi być umieszczona co najmniej na wysokości 30 centymetrów. 2. Naciśnięcie przycisku na pilocie powoduje otworzenie drzwiczek, jeśli te są zamknięte, lub ich zamknięcie, jeśli są otwarte. 3. Po otworzeniu drzwiczek, powinny one zostać automatycznie zamknięte, jeśli wcześniej nie zostaną zamknięte przez użytkownika. 4. System rozpoznawania dźwięków musi być w stanie określić, kiedy pies szczeka. 5. System rozpoznawania dźwięków musi być w stanie otworzyć drzwiczki, kiedy pies zacznie szczekać. 6. Drzwiczki powinny ostrzegać właściciela, kiedy coś wewnątrz domu znajdzie się zbyt blisko nich, by mogły się otworzyć bez uderzania w ten przedmiot. 7. W pewnych, określonych godzinach drzwiczki mają być cały czas otworzone. 8. Drzwiczki można zintegrować z domowym systemem alarmowym, by zagwarantować poprawne działanie tego systemu, gdy pies będzie wchodzić do domu i wychodzić z niego. 9. Drzwiczki powinny informować właściciela sygnałem dźwiękowym, jeśli na zewnątrz domu znajdzie się coś, co będzie je blokować i uniemożliwiać pełne otworzenie. 10. Drzwiczki będą rejestrować, ile razy pies wychodzi z domu i wchodzi do niego. 11. Kiedy pies wejdzie do domu, domowy system alarmowy powinien się aktywować, o ile był aktywny przed otworzeniem drzwiczek. Drzwiczki alarmują właściciela, jeśli na zewnątrz domu lub wewnątrz niego znajduje się jakaś przeszkoda, która uniemożliwia ich otworzenie. W momencie otwierania drzwiczek należy wyłączyć domowy system alarmowy, a kiedy drzwiczki zostaną zamknięte, alarm powinien być ponownie aktywowany (oczywiście, tylko w przypadku gdy system alarmowy jest włączony).

Oto jak zmodyfikowaliśmy list ę wymagań.

Punkty 8. i 11. dotyczą domowego systemu alarmowego… także i one powielają tę samą podstawową funkcjonalność.

Oto nasze nowe wymaganie, które uzyskaliśmy po połączeniu punktów 8. i 11.

jesteś tutaj  409

Zasada jednej odpowiedzialności

Zasada nr 3: zasada jednej odpowiedzialności Zasada jednej odpowiedzialności, nazywana w skrócie SRP (od angielskich słów: Single Responsibility Principle), jest związana z odpowiedzialnością oraz tym, co poszczególne obiekty w systemie mają robić. Chciałbyś, by każdy zaprojektowany przez Ciebie obiekt koncentrował się tylko na jednym zadaniu, dzięki czemu jeśli jakieś szczegóły związane z tym zadaniem ulegną zmianie, będziesz dokładnie wiedział, gdzie należy wprowadzić zmiany w kodzie.

Zasada jednej odpowiedzialności Każdy obiekt w kodzie powinien mieć tylko jedną odpowiedzialność, a wszystkie usługi tego obiektu powinny koncentrować się na jej realizacji.

Hej, już wcześniej o tym rozmawialiśmy… czy to mniej więcej to samo co jeden powód do modyfikacji klasy?

Jeśli dla każdego obiektu w systemie będzie istnieć tylko jeden powód do jego modyfikacji, to będzie to oznaczać, że prawidłowo zaimplementowałeś zasadę jednej odpowiedzialności. 410

Rozdział 8.

Zasady projektowania

Nie istnieją

głupie pytania

P: Ta nowa zasada SRP wydaje mi się

P: A wykorzystanie SRP sprawi, że

bardzo podobna do DRY. Czy obie nie dotyczą tego, by dana klasa wykonywała tylko jedno zadanie?

moje klasy będą mniejsze, gdyż będą realizować tylko jedno zadanie, prawda?

O: Obie te zasady są ze sobą powiązane.

Zasada DRY dotyczy umieszczania konkretnej funkcjonalności w jednym miejscu, na przykład w klasie; z kolei SRP dotyczy upewnienia się, by dana klasa realizowała tylko jedno zadanie, i by robiła to bardzo dobrze. W dobrych aplikacjach każda klasa realizuje tylko jedno zadanie; robi to bardzo dobrze, a żadne inne klasy nie dysponują taką samą funkcjonalnością.

P: Czy wymóg, by jedna klasa realizowała tylko jedno zadanie, nie jest dosyć ograniczający?

O

: Absolutnie nie, zwłaszcza jeśli zdasz sobie sprawę z tego, iż to „zadanie” może być naprawdę złożone i skomplikowane. Na przykład klasa %RDUG w szkielecie systemu gier Gerarda wykonuje sporo niewielkich operacji, jednak każda z nich wiąże się z jednym dużym zadaniem: obsługą planszy podczas rozgrywki. Klasa %RDUG zajmuje się tylko tą jedną rzeczą — obsługą planszy — i niczym więcej, zatem jest ona doskonałym przykładem wykorzystania SRP.

O: W rzeczywistości stosowanie SRP może

sprawić, że Twoje klasy będą większe. Dzieje się tak, gdyż stosując tę zasadę, nie rozdzielasz funkcjonalności na wiele różnych klas — a tak właśnie robi wielu programistów, którzy jej nie znają — lecz wszystko, co jest związane z danym zadaniem, umieszczasz w jednej klasie. Jednak stosowanie SRP zazwyczaj prowadzi do powstawania mniejszej liczby klas, a to z kolei sprawia, że cała aplikacja staje się znacznie prostsza i łatwiejsza do utrzymania.

P: To brzmi podobnie jak spójność… Czy spójność ma coś wspólnego z SRP?

O: Spójność to inna nazwa tej zasady.

Jeśli piszesz oprogramowanie o wysokim stopniu spójności, oznacza to jednocześnie, że stosujesz SRP.

jesteś tutaj 

411

Analiza SRP

Wykrywanie wielu odpowiedzialności W większości przypadków można wykryć klasy, które nie są zgodne z SRP, wykonując prosty test: 1

Na kartce papieru zapisz kilka wierszy tekstu o następującej postaci: Klasa [puste] [puste] się. Taki wiersz tekstu powinieneś zapisać dla każdej metody zdefiniowanej w analizowanej klasie.

2 W pierwszym pustym miejscu każdego wiersza tekstu wpisz nazwę analizowanej klasy,

a w drugim — nazwę metody. W taki sposób zapisz wiersze dla wszystkich metod analizowanej klasy. 3 Następnie każdy z wierszy przeczytaj na głos (być może, aby móc go normalnie

przeczytać, będziesz musiał dodać do niego jakąś literę lub słowo). Czy to, co odczytałeś, ma jakiś sens? Czy analizowana klasa naprawdę spełnia zadanie, jakie sygnalizuje dana metoda?

Jeśli to, co właśnie przeczytałeś, nie ma najmniejszego sensu, to najprawdopodobniej dana metoda narusza SRP. Być może powinieneś zdefiniować tę metodę w innej klasie… Przemyśl to.

Oto jak pow wyglądać ar inien analizy SR kusz P.

Analiza SRP klasy: miejscach W tych pustych sy, kla ę zw zapisz na do samego wszędzie — aż końca arkusza.

Klasa Klasa Klasa

W tym pustym mi nazwy metod zd ejscu zapisz efi w klasie, po jed niowanych ne w każdym wiersz j nazwie u.

się się się wtórz dla Taki wiersz po zdefiniowanej y od et m ej żd ka ej klasie. w analizowan

412

Rozdział 8.

Zasady projektowania

Zaostrz ołówek Zastosuj SRP na klasie Automobile. Sprawdź, czy przedstawiona poniżej klasa Automobile jest zgodna z SRP. Wypełnij arkusz analizy umieszczony u dołu strony, zgodnie z informacjami podanymi na poprzedniej stronie. Następnie zastanów się, czy według Ciebie sensowne jest, by klasa Automobile miała wszystkie metody, które w niej umieściliśmy.

Automobile klasy Poszukaj tej ci w drugiej częśpt. rozdziału 5., tastrofa. Obiektowa ka

Analiza SRP klasy: Klasa Klasa Klasa Klasa Klasa Klasa Klasa

start() stop() changeTires(Tire [*]) drive() wash() checkOil() getOil(): int

Owszem, zdajemy sob ie z tego, że możesz zaj sprawę rzeć do rozdziału 5. i zobaczyć, co tam jednak ufamy, że tego zrobiliśmy, Najpierw spróbuj wykonnie zrobisz. samodzielnie i szukaj ać to ćwiczenie w rozdziale 5. dopier pomocy o nie będziesz umiał sob wtedy, gdy ie poradzić.

Zgodna z SRP

Automobile

Narusza SRP

się się się się się się się Jeśli to, co przeczytasz, nie będz mieć sensu, będzie to zapewne ie oznaczać, że metoda umieszczona w danym wierszu narusza SRP.

jesteś tutaj  413

Pojedyncza odpowiedzialność

Zaostrz ołówek

Rozwiązanie

Zastosuj SRP na klasie Automobile.

Twoim zadaniem było wykonanie analizy SRP przedstawionej klasy Automobile. Miałeś wypełnić arkusz nazwami metod klasy Automobile i zdecydować, czy definiowanie każdej z nich w tej klasie ma sens.

Analiza SRP klasy: To całkiem sensowne, że auto odpowiada za ruszanie i zatrzymywanie się. Bez wątpienia są to funkcje auta.

Auto NIE jest odpowiedzialne za zmienianie opon, mycie ani sprawdzanie poziomu oleju.

Zgodna z SRP

Automobile Warto dodać literę, słowo lub polską nazwę metody, by zdanie stało się bardziej czytelne i nabrało sensu.

Klasa Klasa Klasa Klasa Klasa Klasa Klasa

Automobile

start (rusza)

Automobile

stop (zatrzymuje)

Automobile

changeTires (zmienia Opony)

Automobile

drive (prowadzi)

Automobile

wash (myje)

Automobile

check (sprawdza)

Automobile

getOil (pobierz poziom oleju)

uważnie To zdanie powinieneś ić się, co ow tan zas i eć yśl przem ć”. Metoda ta po oznacza słowo „pobra cje o poziomie rma info a prostu zwrac bez wątpienia oleju w silniku, a to konanie auto wy rej jest operacja, któ powinno umożliwiać.

się się się się się się się

To zdanie jest dosyć trudne do ocenienia… Uznaliśmy, że choć auto może ruszać i zatrzymywać się, to jednak za prowadzenie auta jest raczej odpowiedzialny kierowca, a nie samo auto.

Sytuacje takie jak ta dlaczego analiza SRP pokazują, wskazówką. Wciąż zat jest jedynie musiał odwoływać się em będziesz rozsądku i własnego do zdrowego doświadczenia.

414

Narusza SRP

Rozdział 8.

Zasady projektowania

Przechodzenie od wielu do jednej odpowiedzialności Kiedy już przeprowadziłeś analizę pewnej klasy, będziesz mógł usunąć z niej te metody, których funkcjonalność nie pasuje do odpowiedzialności klasy, i przenieść je do innych klas, których odpowiedzialności pokrywają się z funkcjonalnością metod.

Za prowadzenie auta odpowiedzialny kierow jest ca, a nie samo auto.

Driver Dzięki zastosowanej analizie ustaliliśmy, że tak naprawdę te cztery metody nie pokrywają się z odpowiedzialnością klasy Automobile.

Automobile

drive(Automobile)

start() stop() changeTires(Tire [*]) drive() wash() checkOil() getOil(): int

Obiekt CarWash może obsługiwać operację mycia samochodu.

CarWash wash(Automobile)

Mechanic Teraz klasa Automobile ma tylko jedną odpowiedzialność: obsługę swoich własnych, podstawowych funkcji.

changeTires(Automobile, Tire[*]) checkOil(Automobile)

Automobile start() stop() getOil(): int

To mechanik jest odpowiedzialny za wymianę opon i sprawdzanie stanu oleju w samochodzie. Nie istnieją

głupie pytania

P: W jaki sposób analiza SRP działa w przypadkach,

P: A co jeśli klasa CarWash wymagałaby przekazania

gdy metoda wymaga podania jakichś argumentów, jak na przykład metoda wash(Automobile) klasy CarWash?

obiektu Automobile jako argumentu wywołania konstruktora, natomiast sama metoda nie wymagałaby przekazywania żadnego argumentu? Czy w takim przypadku analiza SRP dałaby nieprawidłowe wyniki?

O: Dobre pytanie. Aby analiza SRP miała jakikolwiek sens, wraz

z nazwą metody musisz zapisać jej argumenty. A zatem powinieneś zapisać: „Klasa CarWash wash (myje) Automobile się”. Taka metoda ma sens (wraz z obiektem $XWRPRELOH jako argumentem), może więc zostać zdefiniowana w klasie &DU:DVK.

O: Owszem. Jeśli argument, który zapewniałby sensowność

metody, taki jak obiekt $XWRPRELOH przekazywany do metody ZDVK , będzie przekazywany w wywołaniu konstruktora, to analiza SRP może dać błędne lub mylące wyniki. Jednak w takich przypadkach oprócz analizy SRP zawsze musisz skorzystać ze zdrowego rozsądku i znajomości tworzonego systemu.

jesteś tutaj  415

Odszukaj zasadę jednej odpowiedzialności

6SRWNDQLD]653 SRP już kilka razy przewinęła się w naszej dotychczasowej pracy. Teraz, ponieważ powoli poznajesz tę zasadę coraz lepiej, nadszedł czas, byś określił, kiedy i w jaki sposób ją zastosowaliśmy. Twoim zadaniem jest przeanalizowanie przedstawionej poniżej strony i wyjaśnienie, w jaki sposób zastosowaliśmy na niej SRP oraz w jakim celu to zrobiliśmy.

7YMAGANIAULEGAJ’ZMIANOM FODVV 'RJ'RRU ^ RSHQ `

+U^_KVSdKMTK UVK]c N\daSMdOU Ÿ®- RuŸ˜p|‚l¥ou¬Ÿp|JŸ|J‚|ªl-J-oÔA¬Ÿ®-Ÿ-¥ |u- ¬A®yRŸ®-u¬p-ylRŸ J–®ªlA®RpŸ®Ÿpq-˜¬Ÿ5HPRWHŸlŸ‚–®RylR²u¬Ÿa|ŸJ|Ÿpq-˜¬Ÿ'RJ'RRUF

.YQ.YY\TK`K

 SXEOLFYRLGRSHQ ^ 6\VWHPRXWSULQWOQ ĵ'U]ZLF]NLRWZLHUDMÈ VLÚĵ  RSHQ WUXH 4OJESTDOKŽADNIETENSAMKOD

 IEJBYŽUšYWANY ILQDO 7LPHUWLPHU QHZ7LPHU   KTÎRYWCZEwN WKLASIE2EMOTEJAVA WLPHUVFKHGXOH QHZ7LPHU7DVN ^ SXEOLFYRLGUXQ ^      FORVH  4ERAZDRZWI ZAMYKAJ”SIÅ SAMEbNAWETJEwLIDODAMY WLPHUFDQFHO  NOWEURZ”DZENIA KTÎREBÅD” ` MOGŽYJEOTWIERAÁ3UPER `  `

.IEMOšESZ ZAPOMNIEÁ ODODANIU INSTRUKCJI IMPORTUJ”CYCH KLASYJAVAUTIL 4IMERORAZJAVA UTIL4IMER4ASK

SXEOLFYRLGFORVH  ^ 6\VWHPRXWSULQWOQ Ĵ'U]ZLF]NL]DP\NDMÈVLÚĵ  RSHQ IDOVH ` `

?Z\Y]dMdOXSO UYN_ YL]¸_Q_T¦MOQY ZSVY^K Ÿ5HPRWHGŸ

"R–-®Ÿu¥˜l˜®Ÿ¥˜¥yÔɟp|JŸ|J‚|ªl-J-oÔA¬Ÿ®-Ÿ-¥ |u- ¬A®yRŸ®-upylÖAlRŸ®Ÿpq-˜¬ aJ¬¸Ÿ RŸu|¸qlª|²AlŸ\¥ypAo|y-qyRŸ®|˜ -t¬Ÿ‚–®RylR˜l|yRŸJ|Ÿpq-˜¬Ÿ'RJ'RRU‡

SXEOLFYRLGSUHVV%XWWRQ ^ 6\VWHPRXWSULQWOQ ĵ1DFLĂQLÚWR SU]\FLVNQDSLORFLHĵ  LI GRRULV2SHQ ^ GRRUFORVH  `HOVH^ GRRURSHQ  ILQDO 7LPHUWLPHU QHZ7LPHU  WLPHUVFKHGXOH QHZ7LPHU7DVN ^ SXEOLFYRLGUXQ ^  GRRUFORVH   WLPHUFDQFHO  ` `  `







FODVV 5HPRWH^ SUHVV %XWWRQ `

0]

Strzałki reprezentują przejścia. Przenoszą one aplikację z jednego stanu do drugiego.

To jest nazwa przejścia, jednocześnie opisuje ona, co spowodowało zmianę stanu.

To jest tak zwany strażnik (ang. guard expression). Określa on warunek, jaki musi być spełniony, aby dane przejście mogło zostać wykonane.

Pozostałości

ość na tym Gra (czyli aktywn a się w tym yn cz za ie) diagram miejscu.

wybór scenariusza gry

Czasami przejście jest częścią jakiegoś procesu i nie ma żadnej szczególnej nazwy ani warunku można wykonania. W takim przypadku em” etap jnym „kole ać nazw by to stanu aplikacji.

Gotowość do rozpoczęcia gry

rozpoczęcie gry nać jedynie Ruch można wyko są jakieś y gd w przypadku, można jednostki, które takie, dla yli (cz ć ną su prze movement których atrybut szą od 0). ęk wi ść ma warto

Ruch gracza nr 1

przeprowadzenie bitwy [ilość jednostek zdolnych do ataku > 0]

zakończenie etapu

Atak gracza nr 1

zakończenie etapu [kolej ruchu przypada na gracza nr 1]

wykonaj ruch [ilość jednostek zdolna do przemieszczenia się > 0]

zakończenie etapu

wykonaj ruch [ilość jednostek zdolna do przemieszczenia się > 0]

Ruch gracza nr 2 To nie są żadne faktyczne klasy ani metody… to są konkretne stany, w których aplikacja może się znajdować w danej chwili.

zakończenie etapu [kolej ruchu przypada na gracza nr 2]

zakończenie etapu zakończenie etapu

Sprawdzenie warunku zwycięstwa To jest sprawdzenie warunku określającego, czy jeden z graczy nie wygrał; jeśli warunek ten zostanie spełniony, to gra się zakończy.

zwycięstwo

Atak gracza nr 2

przeprowadzenie bitwy [ilość jednostek zdolnych do ataku > 0] To jest stan końcowy tego diagramu. Realizacja procesu nie kończy się, póki nie dotrze ona do tego miejsca.

jesteś tutaj  583

Testowanie jednostkowe

Nr 8. Testowanie jednostkowe W każdym rozdziale, w którym tworzyliśmy jakąś aplikację, pisaliśmy także specjalny program służący do testowania naszego kodu. Przykładami takich programów są: 6XEZD\7HVWHU oraz 'RJ'RRU7HVWHU. Takie programy są jedną z form testowania jednostkowego. Każdą klasę testujemy, używając przy tym pewnego zestawu danych wejściowych lub wykonując odpowiednie sekwencje wywołań metod. Choć w przypadku wykorzystania przez klienta jest to doskonały sposób określenia, co aplikacja robi, to jednak wspomniane rozwiązanie ma także pewne wady. 1

Dla każdego sposobu zastosowania oprogramowania należy napisać kompletny program.

2

Konieczne jest wygenerowanie jakichś wyników — bądź to wyświetlanych na konsoli, bądź zapisywanych do pliku, które pozwolą sprawdzić, czy oprogramowanie faktycznie działa prawidłowo.

3

Po wykonaniu każdego testu konieczne jest samodzielne przejrzenie jego wyników i określenie, czy test zakończył się prawidłowo.

4

W pewnym momencie okaże się, że testy sprawdzają tak obszerne fragmenty funkcjonalności aplikacji, że nie koncentrują się już na jej mniejszych możliwościach.

Na szczęście są dostępne szkielety testowe, umożliwiające nie tylko sprawdzanie wszystkich, nawet najmniejszych fragmentów funkcjonalności aplikacji, lecz także ich automatyzację. Najpopularniejszym szkieletem tego typu, używanym podczas pisania programów w języku Java, jest JUnit (http://www.junit.org); obecnie jest on integrowany z wieloma środowiskami programistycznymi, takimi jak Eclipse.

Nie istnieją

głupie pytania

P: Skoro te wszystkie testy, które pisaliśmy w niniejszej książce, były w stanie zapewnić, że nasze oprogramowanie będzie działać na wysokim poziomie, to niby dlaczego potrzebujemy jeszcze więcej testów? Czy te, które pisaliśmy, nie wystarczą, by upewnić się, że nasze oprogramowanie działa?

O: Większość testów, jakie napisaliśmy w tej książce, w rzeczywistości

testowało jeden, konkretny scenariusz, taki jak: otworzenie drzwiczek, wypuszczenie psa na zewnątrz, otworzenie drzwiczek wyłącznie po rozpoznaniu szczekania psa właścicieli. Testy jednostkowe, a w szczególności testy, które przedstawiamy w tym dodatku, są znacznie bardziej szczegółowe — pozwalają na sprawdzanie kolejnych możliwości funkcjonalnych poszczególnych klas.

584

Dodatek A

Oba te rodzaje testów są potrzebne, ponieważ nigdy nie będziesz w stanie opracować scenariuszy testowych sprawdzających wszystkie możliwe kombinacje możliwości i funkcjonalności Twojego oprogramowania. W końcu jesteśmy tylko ludźmi i każdemu z nas, od czasu do czasu, zdarza się zapomnieć o takiej czy innej dziwnej sytuacji. Dzięki testom sprawdzającym poszczególne możliwości funkcjonalne klas możesz uzyskać pewność, że będą one działać we wszelkich scenariuszach, nawet jeśli któregoś z nich nie przetestujesz jawnie. Dadzą Ci one pewność, że każdy, nawet najmniejszy fragment oprogramowania działa, i znacząco zwiększą prawdopodobieństwo, że także po połączeniu fragmenty te będą funkcjonować prawidłowo.

Pozostałości

Jak wyglądają testy jednostkowe Test jednostkowy składa się z szeregu metod testujących, a każda z nich sprawdza jedną możliwość funkcjonalną klasy. A zatem dla klasy takiej jak 'RJ'RRU powinniśmy przetestować takie czynności jak otwieranie drzwiczek oraz zamykanie ich. Szkielet -8QLW wygenerowałby klasę testową przypominającą tę przedstawioną poniżej:

LPSRUWMXQLWIUDPHZRUN7HVW&DVH

dstawową klasą TestCase jest poużywaną podczas , nit JU u szkielet gramowania. testowania opro



  7DNODVDWHVWXMHG]LDïDQLHGU]ZLF]HNGODSVDXĝ\ZDMÈFGRWHJRFHOX  SU]\FLVNXQDSLORFLH   SXEOLFFODVV5HPRWH7HVWH[WHQGV7HVW&DVH ^ SXEOLFYRLGWHVW2SHQ'RRU   ^ Metoda 'RJ'RRUGRRU QHZ'RJ'RRU  assertTrue() 5HPRWHUHPRWH QHZ5HPRWH GRRU  sprawdza, czy wywołanie metodyUHPRWHSUHVV%XWWRQ  umieszczone w jejDVVHUW7UXH GRRULV2SHQ  wywołaniu zwróci`

 





 ej możliwości Dla każd funkcjonalnej klasy DogDoor zdefiniowaliśmy osobną metodę testującą.

wartość true. W tym przypadkuSXEOLFYRLGWHVW&ORVH'RRU ^ właśnie taki jest 'RJ'RRUGRRU QHZ'RJ'RRU  oczekiwany wynik. 5HPRWHUHPRWH QHZ5HPRWH GRRU  UHPRWHSUHVV%XWWRQ    WU\^ Metoda assertFalse() 7KUHDGFXUUHQW7KUHDG VOHHS   sprawdza, czy metoda przekazana `FDWFK ,QWHUUXSWHG([FHSWLRQH ^ w jej wywołaniu IDLO ķ3U]HUZDQRG]LDïDQLHZÈWNXĵ  NIE zwróciła przez ` przypadek wartości DVVHUW)DOVH GRRULV2SHQ  false. ` `



Ta meto  da spra  wdza, czy drzwiczki zamykają się automatycznie, bez konieczności jawnego wywoływania metody door.close(), która nie jest standardowym sposobem obsługi drzwiczek.

Testowanie kodu w kontekście Zauważ, że zamiast bezpośredniego testowania metod RSHQ i FORVH klasy 'RJ'RRU powyższy test wykorzystuje obiekt klasy 5HPRWH — a zatem stara się obsługiwać drzwiczki dokładnie w taki sposób, w jaki będą one używane w rzeczywistości. Dzięki temu test stara się symulować rzeczywiste sposoby używania drzwiczek, niezależnie od tego, że w danym momencie testuje on jeden, niewielki fragment funkcjonalności. Dokładnie w taki sam sposób postępujemy w klasie WHVW&ORVH'RRU . Zamiast wywoływać metodę FORVH , test otwiera drzwiczki, używając do tego celu pilota, a następnie czeka nieco dłużej, niż wynosi czas automatycznego zamykania drzwiczek; dopiero później test sprawdza, czy drzwiczki zostały zamknięte. Właśnie w taki sposób drzwiczki będą używane, a zatem dokładnie taki schemat działania należy przetestować.

jesteś tutaj  585

Standardy kodowania

Nr 9. Standardy kodowania i czytelny kod Czytanie kodu może w ogromnym stopniu przypominać czytanie książki. Powinieneś być w stanie powiedzieć, co się aktualnie dzieje, i nawet jeśli będziesz mieć parę pytań, to analiza kodu powinna pozwolić na stosunkowo łatwe znalezienie odpowiedzi. Dobrzy programiści i projektanci powinni mieć ochotę na to, by poświęcić nieco dodatkowego czasu na pisanie czytelnego kodu, gdyż poprawia on możliwości utrzymania i wielokrotnego stosowania fragmentów aplikacji. Poniżej przedstawiliśmy czytelną i opatrzoną komentarzami wersję klasy 'RJ'RRU, którą pisaliśmy w rozdziałach 2. i 3. 

 7DNODVDUHSUH]HQWXMHLQWHUIHMVGRSUDZG]LZ\FKGU]ZLF]HNGODSVD  Komentarze dokument ują  #DXWKRU*DU\3ROOLFH ułatwiają czytanie kod ce (JavaDoc)  #YHUVLRQ$XJ można ich użyć do autu, a oprócz tego wygenerowania dokum omatycznego   entacji przy uży SXEOLFFODVV'RJ'RRU^    ciu narzędzia javadoc.  LORĂÊUHDOL]RZDQ\FKSROHFHñRWZRU]HQLDGU]ZLF]HN SULYDWHLQWQXPEHU2I2SHQ&RPPDQGV  ERROHDQGRRU,V2SHQ IDOVH   

 #UHWXUQWUXHMHĂOLGU]ZLF]NLVÈRWZRU]RQH   SXEOLFLV2SHQ ^ WKLVRSHQ IDOVH `



 Nazwy metod i zmiennych są opisowe i łatwe do zrozumienia.

Ten kod jest napisany w sposób



przejrzysty, głównie  2WZLHUDGU]ZLF]NLDQDVWÚSQLHSRSLÚFLXVHNXQGDFK]DP\NDMH dzięki zastosowaniu odpowiednich wcięć.   SXEOLFYRLGRSHQ ^ NRGSU]HND]XMÈF\GRXU]ÈG]HQLDSROHFHQLHRWZRU]HQLDGU]ZLF]HN GRRU,V2SHQ WUXH QXPEHU2I2SHQ&RPPDQGV 7LPHU7DVNWDVW QHZ7LPHU7DVN ^ Nawet zmienneSXEOLFYRLGUXQ ^ używane jedynie LI QXPEHU2I2SHQ&RPPDQGV  ^ e w danej metodzi NRGSU]HND]XMÈF\GRVSU]ÚWXSROHFHQLH]DPNQLÚFLDGU]ZLF]HN noszą opisowe i zrozumiałe GRRU,V2SHQ IDOVH  ` nazwy. ` Wszelkie instrukcje, których ` znaczenie może nie być 7LPHUWLPHU QHZ7LPHU  oczywiste, są opatrywane WLPHUVFKHGXOH WDVN   komentarzem. ` `

586

Dodatek A

Pozostałości

Wspaniałe oprogramowanie to coś więcej niż jedynie działający kod Zapewne od wielu programistów mógłbyś usłyszeć, że standardy kodowania oraz odpowiednie formatowanie kodu to tylko niepotrzebny kłopot, jednak sam się możesz przekonać, co się stanie, jeśli wcale nie poświęcisz czasu na formatowanie swojego kodu: h nie ma żadnyc W tym kodzie zatem programista SXEOLFFODVV'RJ'RRU^    mentarzy…  usi się ko ki kod sam m analizujący ta myślać. do SULYDWHLQWQRF     wszystkiego ERROHDQGLR IDOVH   Żadnych informacji o tym , do czego służą te zm ienne… SXEOLFUHWXUQGLR ^WKLVRSHQ IDOVH` SXEOLFYRLGGRBP\BMRE ^   Także ich ...i te metody. nic nam nie mówią. GLR WUXH    nazwy QRF 7LPHU7DVNWW QHZ7LPHU7DVN ^ SXEOLFYRLGUXQ ^ LI QRF  GLR IDOVH Brak wcię ć  i odstępów dodatkowo ` utrudnia zrozumienie ` tego kodu. 7LPHUW QHZ7LPHU  WLPHUVFKHGXOH WW   ` `

Z czysto funkcjonalnego punktu widzenia obie wersje klasy 'RJ'RRU działają równie dobrze. Jednak obecnie powinieneś już wiedzieć, że wspaniałe oprogramowanie to coś więcej niż jedynie działający kod — to kod zapewniający łatwość utrzymania i możliwości wielokrotnego stosowania. A większość programistów nie miałaby najmniejszej ochoty ani utrzymywać, ani używać tej wersji klasy 'RJ'RRU. Stosunkowo trudno jest zrozumieć, co ten kod robi oraz w którym jego miejscu mogłyby wystąpić problemy — a teraz wyobraź sobie, że musiałbyś się zająć programem napisanym w taki sam sposób, który jednak nie ma 25, a 10 tysięcy wierszy!

Pisanie czytelnego kodu sprawia, że jego utrzymanie i wielokrotne stosowanie będzie łatwiejsze, i to zarówno dla Ciebie, jak i dla innych programistów. jesteś tutaj  587

Refaktoryzacja

Nr 10. Refaktoryzacja Refaktoryzacja jest procesem polegającym na modyfikowaniu struktury kodu, bez zmiany jego działania. Przeprowadza się go w celu poprawienia przejrzystości, elastyczności i możliwości rozbudowy kodu, a zazwyczaj dotyczy on bardzo konkretnych usprawnień projektu. Większość operacji związanych z refaktoryzacją jest stosunkowo prosta i koncentruje się na jednym, konkretnym aspekcie kodu. Na przykład: SXEOLFGRXEOHJHW'LVDELOLW\$PRXQW ^ VSUDZG]HQLHZDUXQNöZ LI VHQLRULW\ UHWXUQ LI PRQWKV'LVDEOHG!  UHWXUQ LI LV3DUW7LPH UHWXUQ Z\]QDF]HQLHL]ZUöFHQLHVWRSQLDQLHSHïQRVSUDZQRĂFL `

Refaktoryzacja modyfikuje wewnętrzną strukturę kodu BEZ zmieniania sposobu jego działania.

Choć w tym kodzie nie ma niczego szczególnie niewłaściwego, to jednak nie zapewnia tak dużej łatwości utrzymania, jaką mógłby gwarantować. W rzeczywistości metoda JHW'LVDELOLW\$PRXQW wykonuje dwie operacje: sprawdza warunki niepełnosprawności, a następnie wyznacza stopień niepełnosprawności. Obecnie powinieneś już wiedzieć, że taki kod narusza zasadę jednej odpowiedzialności. Tak naprawdę powinniśmy rozdzielić ten kod na dwie metody i w kodzie metody obliczającej wartość niepełnosprawności wywoływać metodę sprawdzającą warunki. A zatem możemy zmodyfikować nasz kod do następującej postaci: Rozdzieliliśmy

oba zad SXEOLFGRXEOHJHW'LVDELOLW\$PRXQW ^ i umieściliśmy każde ania z nich w osobnej metod VSUDZG]HQLHZDUXQNöZ zie, zgodnie z SRP. LI LV(OLJLEOH)RU'LVDELOLW\ ^ Z\]QDF]HQLHL]ZUöFHQLHVWRSQLDQLHSHïQRVSUDZQRĂFL `HOVH^ UHWXUQ ` `

Teraz, jeśli zmienią się warunki uznawania niepełnosprawności, konieczne będzie wprowadzenie zmian jedynie w kodzie metody LV(OLJLEOH)RU'LVDELOLW\ — żadnych modyfikacji nie trzeba będzie natomiast dokonywać w kodzie metody odpowiedzialnej za wyliczanie stopnia niepełnosprawności. Możesz wyobrazić sobie refaktoryzację jako swoistą kontrolę kodu. Powinien to być proces ciągły, gdyż kod pozostawiony sam sobie zazwyczaj staje się coraz trudniejszy do użycia. Spróbuj sięgnąć do jakiegoś swojego starego kodu i przeprowadź jego refaktoryzację, wykorzystując techniki projektowania obiektowego poznane w niniejszej książce. Programiści, którzy w przyszłości będą musieli używać Twojego kodu, na pewno Ci za to podziękują.

588

Dodatek A

'RGDWHN%:LWDP\Z2ELHNWRZLH

Stosowanie języka obiektowego

Tu napisałeś, że chcesz zmienić moją kompozycję na agregację, dodać nieco delegowania, a na końcu stwierdzasz, że nie jestem zbyt dobrze hermetyzowany. Zupełnie nie wiem, co mam o tym wszystkim myśleć, ale uważam, że mam prawo czuć się urażony!

Przygotuj się na zagraniczną wycieczkę. Czas odwiedzić Obiektów — miejsce, gdzie obiekty robią to, co powinny, aplikacje są dobrze hermetyzowane (już wkrótce dowiesz się, co to znaczy), a projekty oprogramowania pozwalają na ich wielokrotne stosowanie i rozbudowę. Musisz jeszcze poznać kilka dodatkowych zagadnień i poszerzyć swoje umiejętności językowe. Nie przejmuj się jednak, nie zajmie Ci to wiele czasu i zanim się obejrzysz, już będziesz rozmawiał w języku obiektowym, jakbyś mieszkał w Obiektowie od wielu lat.

to jest nowy dodatek  589

Twój pakiet socjalny

Witamy w Obiektowie Niezależnie od tego, czy to jest Twoja pierwsza wizyta w Obiektowie, czy też byłeś już tam kiedyś wcześniej, to jednak na pewno zgodzisz się ze stwierdzeniem, że trudno znaleźć drugie takie miejsce. Tutaj wszystko jest nieco inne, zatem chcemy Ci pomóc nieco uporządkować cały ten bałagan, zanim zajmiesz się lekturą podstawowej części tej książki.

Witam w Obiektowie! Wybrałam kilka rzeczy, które być może będą Ci potrzebne, byś od razu poczuł się tu dobrze. Dobrej zabawy!

Zaczniemy od bardzo prostego schematu UML, tak byśmy w treści książki mogli nieskrępowanie rozmawiać o obiektach . Następnie szybko prz yjr się dziedziczeniu — tylkzymy to, by upewnić się, że o po przygotowany do analizyjesteś bardziej złożonych prz ykł zamieszczonych w nin adów iejszej książce.

or fi

zm

HW\]DF

lim

]HQL

H

po

G]LF

+HUP

']LH

MD

UML

Kiedy już załatwimy sprawę dziedziczenia, również bardzo szybko przyjrzymy się polimorfizmowi.

W końcu, poświęcimy nieco uwagi hermetyzacji i upewnimy się, że wszyscy zgadzamy się co do znaczenia tego słowa.

590

Dodatek B

Witamy w Obiektowie

UML i diagramy klas W tej książce będzie często mowa o klasach i obiektach, jednak trudno jest przeglądać 200 wierszy kodu i jednocześnie wyobrażać sobie jego ogólną postać. Właśnie z tego względu będziemy używać języka UML, czyli Ujednoliconego Języka Modelowania (ang. Unified Modeling Language), pozwalającego przekazywać innym programistom oraz klientom te szczegóły dotyczące kodu i struktury aplikacji, które naprawdę są im potrzebne, a jednocześnie nie obciążającego ich informacjami niemającymi większego znaczenia.

y klasy Oto jak przedstawiam mie klas. gra dia m any zw na tak sób UML spo i tak w To właśnie ianie pozwala na przedstaw cji szczegółowych informa ikację. apl o klasach tworzących

To są zmienne składowe klasy. Każda ma pewną nazwę, a po dwukropku określony jest jej typ.

To jest nazwa klasy. Zawsze jest umieszczana na samej górze diagramu i zap isywana pogrubioną czcionką.

Airplane speed: int getSpeed(): int setSpeed(int)

To są metody klasy. Każda metoda ma naz wę, za nią w nawiasach podawane są parametr metody, a następnie, y po dwukropku, typ warto ści wynikowej.

Ta linia oddziela zmienne składowe klasy od jej metod.

wyobrazić sobie ogólną Diagram klasy pozwala na pierwszy rzut du, postać klasy: bez tru co dana klasa robi. ć, oka, można powiedzie gramie można nawet we W razie potrzeby w dia ierający zmienne składo pominąć fragment zaw oże w przekazaniu pom to li jeś lub metody, niezbędnych informacji.

Zaostrz ołówek Napisz szkielet kodu klasy Airplane. Przekonaj się, czy na podstawie przedstawionego powyżej diagramu klasy będziesz w stanie napisać szkielet kodu klasy Airplane. Czy przychodzi Ci na myśl coś, co nie zostało podane na tym diagramie? Jeśli tak, to wypisz te wszystkie elementy klasy w poniższych pustych liniach:

jesteś tutaj  591

Tworzenie kodu na podstawie diagramu klasy

Zaostrz ołówek

Rozwiązanie

Napisz szkielet kodu klasy Airplane. Na podstawie diagramu klasy przedstawionego na stronie 591 miałeś napisać prosty szkielet kodu klasy Airplane. Poniżej przedstawiliśmy nasze rozwiązanie tego ćwiczenia:

Diagram klas nie zawiera żadnych informacji o tym, czy zmienna speed powinna być publiczna, prywatna bądź chroniona.

Prawdę mówiąc, diagramy klas mogą dostarczać tych informacji, jednak w większości przypadków nie są one konieczne do zapewnienia przejrzystości przekazu.

SXEOLFFODVV$LUSODQH^  SULYDWHLQWVSHHG  SXEOLF$LUSODQH ^  `



Diagram klasy nie zaw informacji dotyczących ierał też żadnych zatem napisać konstr konstruktora. Mógłbyś ukt przekazania początkow or wymagający speed, i takie rozwią ej wartości zmiennej zanie także byłoby prawidłowe.

SXEOLFYRLGVHW6SHHG LQWVSHHG ^ WKLVVSHHG VSHHG  ` SXEOLFLQWJHW6SHHG ^ UHWXUQVSHHG ` `













i Diagram klasy oczywiście nie mów inna nam, co każda z tych metod pow ne robić… Pisząc je, przyjęliśmy pew założenia, jednak nie możemy mieć ła pewności, że kod faktycznie dzia i. niam ierze zam z zgodnie

Nie istnieją

głupie pytania

P: A zatem diagram klasy nie stanowi jej kompletnej reprezentacji, prawda?

O: Nie, jednak nie takie jest jego

przeznaczenie. Diagramy klas służą do przekazywania podstawowych informacji dotyczących zmiennych składowych oraz metod klas. Oprócz tego ułatwiają one prowadzenie dyskusji na temat kodu bez konieczności przeglądania setek wierszy kodu napisanego w Javie, C lub Perlu.

P: Mam swój własny sposób rysowania klas. Co w tym złego, że stosuję własne rozwiązania?

592

Dodatek B

O: Nie ma nic złego w tym, że stosujesz

swój własny sposób zapisu, jednak dla innych osób zrozumienie go może być znacznie trudniejsze. Dzięki stosowaniu standardów, takich jak właśnie UML, wszyscy możemy rozmawiać tym samym językiem i mieć pewność, że nasze diagramy przekazują te same informacje.

P: No a w ogóle kto wpadł na pomysł stworzenia tego UML-a?

O: Specyfikacja języka UML została

opracowana przez firmę Rational Software, pod kierownictwem Grady’ego Boocha, Ivara Jacobsona oraz Jimmy’ego Rumbaugha (trójki naprawdę mądrych gości). Obecnie specyfikacja ta jest zarządzana przez organizację OMG — Object Management Group.

P: To chyba strasznie dużo zamieszania jak na taki jeden mały diagram.

O: Język UML pozwala na znacznie

więcej niż rysowanie diagramów klas. Udostępnia on symbole i sposoby umożliwiające prezentowanie stanu obiektów, sekwencji zdarzeń zachodzących w aplikacji, a nawet wymagań użytkowników oraz ich interakcji z systemem. A poza tym nie dowiedziałeś się jeszcze wszystkiego odnośnie do możliwości rysowania diagramów klas. Obecnie jednak do narysowania diagramu takiego jak ten przedstawiony na stronie 591 wystarczy Ci garść podstawowych informacji. Jednak w treści książki zamieścimy więcej informacji dotyczących tego, co można pokazywać na diagramie klas, oraz przedstawimy inne rodzaje diagramów UML, gdy pojawi się taka potrzeba.

Witamy w Obiektowie

Dziedziczenie Jednym z podstawowych zagadnień programowania w Obiektowie jest dziedziczenie. Termin ten odnosi się do sytuacji, gdy jedna klasa dziedziczy zachowania od innej klasy i, w razie potrzeby, może je modyfikować. Zobaczmy, w jaki sposób można zastosować dziedziczenie w języku Java; w innych językach jest ono używane bardzo podobnie: Klasa Jet jest nazywana klasą pochodną klasy Airplane. Z kolei Airplane to klasa nadrzędna klasy Jet.

super jest słowem kluczowym. Odnosi się ono do klasy, od której dziedziczy dana klasa. A zatem, w tym przypadku wywoływany jest konstruktor klasy nadrzędnej, czyli Airplane.

Klasa pochodna może definiować swoje własne metody, które powiększą zbiór metod odziedziczonych od klasy nadrzędnej.

e. rza klasę Airplan Klasa Jet rozsze sa Jet dziedziczy kla Oznacza to, że ania klasy Airplane wszystkie zachow stywać na swój i może je wykorzy sposób.

SXEOLFFODVV-HWH[WHQGV$LUSODQH^ SULYDWHVWDWLFILQDOLQW08/7,3/,(5  SXEOLF-HW ^ VXSHU  `





Klasa pochodna mo swoje własne zm że definiować które powiększą ienne składowe, odziedziczonych zbiór zmiennych od klasy Airplan e.

SXEOLFYRLGVHW6SHHG LQWVSHHG ^ VXSHUVHW6SHHG VSHHG 08/7,3/,(5    ` Klasa pochodna może zmieniać zachowania SXEOLFYRLGDFFHOHUDWH ^ VXSHUVHW6SHHG JHW6SHHG    ` ` Klasa Jet dziedziczy także metodę getSpeed() klasy Airplane. Jednak ponieważ w obu klasach używana jest ta sama wersja tej metody, zatem w klasie Jet nie trzeba pisać żadnego kodu, który by coś w tej metodzie zmieniał. A zatem, choć nie zobaczysz metody getSpeed() w kodzie klasy Jet, to nic nie stoi na przeszkodzie, byś z niej korzystał.

odziedziczone od klasy nadrzędnej oraz wywoływać jej metody. Proces ten zachowań  nazywany jest przesłanianiem klasy nadrzędnej.

Możesz użyć wy wo getSpeed(), jedna łania o postaci super. zastosować wywo k równie dobrze możesz gdyby getSpeed() łanie getSpeed(), jak zdefiniowaną w była normalną metodą klasie Jet.

Dzięki dziedziczeniu możesz tworzyć klasy na podstawie innych, już istniejących klas, unikając przy tym powtarzania i powielania kodu.

jesteś tutaj  593

Zagadkowy basen

Zagadkowy basen Twoje zadanie polega na tym, by uzupełnić puste miejsca w kodzie, zamieszczonym w ramce obok, fragmentami kodu pływającymi w basenie u dołu. Każdy fragment kodu może być użyty więcej niż jeden raz i pamiętaj, że nie wszystkie fragmenty zostaną użyte. Twoim celem jest stworzenie klasy, którą będzie można skompilować, wykonać i która wygeneruje następujące wyniki:

Wyniki

[



SXEOLFFODVV)O\7HVW^ SXEOLFVWDWLFYRLGPDLQ 6WULQJ>@DUJV ^ $LUSODQHELSODQH QHZ$LUSODQH  ELSODQHVHW6SHHG BBBBBB  6\VWHPRXWSULQWOQ BBBBBBBBBBBBBBB  -HWERHLQJ QHZ-HW  ERHLQJVHW6SHHG BBBBBB  6\VWHPRXWSULQWOQ BBBBBBBBBBBBBB  BBBBBBBBBBB ZKLOH BBBBBB ^ BBBBBBBBBBBBBBBBBB  6\VWHPRXWSULQWOQ BBBBBBBBBBBBBBBBBBB  LI BBBBBBBBBBBBBBB! ^ BBBBBBBBBBBBBB BBBBBBBBBBBBBBBB   `HOVH^ BBBBBBBBBBBBBBBBBBBBBB ` BBBBBBB ` 6\VWHPRXWSULQWOQ BBBBBBBBBBBBBBBBBBBBBB  ` `

ERHLQJJHW6SHHG ELSODQHDFFHOHUDWH 

LQW[ 



[

[ 

ELSODQHVHW6SHHG 

[

[



ELSODQHJHW6SHHG 

[ 



ERHLQJVHW6SHHG ERHLQJDFFHOHUDWH 

[

5R]ZLÈ]DQLH]QDMG]LHV]QDVWURQLH

594

Dodatek B

Witamy w Obiektowie

Polimorfizm… Polimorfizm jest ściśle związany z dziedziczeniem. Kiedy jedna klasa dziedziczy od innej, to właśnie polimorfizm pozwala, by klasa pochodna odróżniała się od klasy nadrzędnej. , który y diagram Oto kolejn przedstawia tym razemy. dwie klas

klasa Jet Ta niewielka strzałka oznacza, że przykładaj dziedziczy od klasy Airplane. Nie w treści — szczególnej wagi do tej notacji dziedziczenia książki zajmiemy się zagadnieniem ziej bard znie znac klas ch na diagrama szczegółowo.

Airplane

Jet

speed: int

MULTIPLIER: int

getSpeed(): int setSpeed(int)

accelerate()

ną Airplane. Jet jest klasą pochod zie tam, Oznacza to, że wszęd u klasy ekt gdzie można użyć obi Airplane…

$LUSODQHSODQH $LUSODQH Zatem z lewej strony masz klasę nadrzędną…

$LUSODQHSODQH QHZ$LUSODQH

…można także użyć obiektu klasy Jet.

$LUSODQHSODQH QHZ-HW  nadrzędnej …a z prawej możesz użyć klasy ch. odny poch klas jej z lnej LUB dowo

$LUSODQHSODQH QHZ$LUSODQH  $LUSODQHSODQH QHZ-HW  $LUSODQHSODQH QHZ5RFNHW 

Załóżmy, że Rocket jest kolejną klasą dziedziczącą od Airplane.

Nie istnieją

głupie pytania

P: A co takiego przydatnego daje nam polimorfizm?

P: Wciąż nie rozumiem, w jaki sposób polimorfizm poprawia elastyczność mojego kodu.

O: Jeśli napiszesz kod operujący na klasie nadrzędnej, takiej

jak $LUSODQH, to równie dobrze będzie on mógł operować na dowolnych klasach pochodnych, takich jak -HW lub 5RFNHW. Dzięki temu Twój kod będzie znacznie bardziej elastyczny.

O: Cóż, jeśli okaże się, że potrzebujesz nowych możliwości

funkcjonalnych, będziesz mógł napisać nową klasę dziedziczącą od $LUSODQH. Ale ponieważ Twój kod używa klasy nadrzędnej, zatem Twoja nowa klasa pochodna będzie mogła być używana bez konieczności wprowadzania jakichkolwiek zmian w pozostałych częściach kodu! A to oznacza, że Twój kod jest elastyczny, można go łatwo modyfikować i rozbudowywać.

jesteś tutaj  595

Ukrywanie informacji

Hermetyzacja Hermetyzacja polega na ukrywaniu implementacji klasy w taki sposób, iż można je łatwo używać i zmieniać. Sprawia, że klasa działa jak swoista „czarna skrzynka” udostępniająca swoje usługi użytkownikom, a jednocześnie nie daje dostępu do kodu, uniemożliwiając przez to jego modyfikację lub nieprawidłowe użycie. Hermetyzacja jest kluczową techniką pozwalającą spełniać wymogi zasady otwarte-zamknięte. Załóżmy, że napisałbyś swoją klasę $LUSODQH w następujący sposób: SXEOLFFODVV$LUSODQH^  SXEOLFLQWVSHHG W tym przykładzie zmienną składową speed zdefiniowaliśmy jako publiczną, a nie prywatną; zatem obecnie dowolny kod aplikacji będzie w stanie odczytać jej wartość.

Hermetyzacja polega na ochranianiu informacji

SXEOLF$LUSODQH ^ `

dostępnych w kodzie przed

SXEOLFYRLGVHW6SHHG LQWVSHHG ^ WKLVVSHHG VSHHG `

nieprawidłowym

SXEOLFLQWJHW6SHHG ^ UHWXUQVSHHG ` `

użyciem.

Teraz każdy może jawnie określać szybkość samolotu Powyższa zamiana oznacza, że obecnie inne fragmenty kodu aplikacji nie muszą już określać szybkości samolotu, używając metody VHW6SHHG ; wartość zmiennej VSHHG można bowiem określić bezpośrednio. A zatem poniższy kod bez problemu można skompilować i wykonać: SXEOLFFODVV)O\7HVW^ SXEOLFVWDWLFYRLGPDLQ 6WULQJ>@DUJV ^ $LUSODQHELSODQH QHZ$LUSODQH  ELSODQHVSHHG    6\VWHPRXWSULQWOQ ELSODQHVSHHG  ` `     Wypróbuj ten kod… czy wyniki Cię nieco zaskoczyły?

596

Dodatek B

Nie musimy już dłużej używać metod setSpeed()  getSpeed  ()… możemy oraz bezpośrednio odczytywać i zapisywać wartość zmiennej speed.

Witamy w Obiektowie

Ale w czym problem? Nie wydaje Ci się, że to jakiś poważny problem, prawda? Ale co się stanie, kiedy zdefiniujesz klasę -HW i zaczniesz określać wartość zmiennej speed w następujący sposób:

Użycie obiektu klasy Jet bez hermetyzacji.

SXEOLFFODVV)O\7HVW^ SXEOLFVWDWLFYRLGPDLQ 6WULQJ>@DUJV ^ -HWMHW QHZ-HW  MHWVSHHG     6\VWHPRXWSULQWOQ MHWVSHHG 

Ponieważ klasa Jet dziedziczy od  lane, zate  m możesz używać Airp zmiennej składowej speed tak sam jak gdyby została ona zdefiniow o, ana bezpośrednio w klasie Jet.

-HWMHW QHZ-HW  MHWVHW6SHHG   Użycie obiektu 6\VWHPRXWSULQWOQ MHWJHW6SHHG  klasy Jet ` z zastosowaniem ` Oto w jaki sposób ustawialiśmy hermetyzacji.

y i odczytywaliśmy szybkość, kied zmienna speed nie była dostępna bezpośrednio.

Zaostrz ołówek Jakie jest znaczenie hermetyzacji danych? Wpisz, skompiluj i uruchom przedstawiony powyżej program FlyTest3.java. Jakie uzyskałeś wyniki? W dwóch poniższych pustych wierszach zapisz dwie liczby wyświetlone przez program: Szybkość samolotu jet1: Szybkość samolotu jet2: Jak sądzisz, co się stało? Poniżej zapisz swoje wyjaśnienia, dlaczego zostały wygenerowane właśnie takie wyniki:

A teraz napisz, na czym według Ciebie polega znaczenie hermetyzacji:

jesteś tutaj  597

Znaczenie hermetyzacji

Zaostrz ołówek

Rozwiązanie

Jakie jest znaczenie hermetyzacji danych? Wpisz, skompiluj i uruchom przedstawiony powyżej program FlyTest3.java. Jakie uzyskałeś wyniki? W dwóch poniższych pustych wierszach zapisz dwie liczby wyświetlone przez program: Szybkość samolotu jet1:

212

Szybkość samolotu jet2:

424

Jak sądzisz, co się stało? Poniżej zapisz swoje wyjaśnienia, dlaczego zostały wygenerowane właśnie takie wyniki:

W klasie Jet metoda setSpeed() mnoży przekazaną wartość razy dwa i dopiero potem zapisuje uzyskany wynik w zmiennej speed. Kiedy samodzielnie e Nie musiałeś konieczni o teg nie ład dok ć isa nap samego co my, jednak Twoje wyjaśnienia powinny być dosyć podobne.

określiliśmy wartość zmiennej speed, nie została ona pomnożona. A teraz napisz, na czym według Ciebie polega znaczenie hermetyzacji:

Hermetyzacja uniemożliwia określanie wartości danych w nieprawidłowy sposób. W przypadku gdy dane są hermetyzowane, wszelkie obliczenia i sprawdzenia wykonywane na danych będą dawały prawidłowe wyniki, gdyż bezpośredni dostęp do danych nie będzie możliwy.

Uczęszczasz na kurs programowan Poniżej podaliśmy oficjalną defin ia? icję hermetyzacji… Podczas egzaminu to właśnie ją powinieneś podać.

zapewnia coś A zatem hermetyzacja ycie informacji; ukr e yni jed więcej niż ć, że metody pozwala mieć pewnoś ania na danych row ope przeznaczone do ne! faktycznie będą używa

Kącik naukowy hermetyzacja — proces zamykania elementów programowych w większych i bardziej abstrakcyjnych jednostkach. Bywa także nazywana ukrywaniem informacji lub separacją zagadnień.

598

Dodatek B

Witamy w Obiektowie Nie istnieją

głupie pytania

P: A zatem hermetyzacja sprowadza się do definiowania większości zmiennych jako prywatne?

O: Nie. Hermetyzacja ma na celu

odseparowanie informacji od innych fragmentów kodu aplikacji, które nie powinny mieć do nich bezpośredniego dostępu. Jeśli dysponujesz jakimiś zmiennymi składowymi, to zapewne nie chcesz, by inne fragmenty aplikacji mogły na nich operować bez jakichkolwiek ograniczeń; właśnie dlatego separujesz takie dane, definiując je jako prywatne. Jeśli Twoje dane muszą być aktualizowane, to możesz udostępnić metody, które będą to robić w sposób odpowiedzialny; tak jak myśmy zrobili w klasie $LUSODQH, definiując metody JHW6SHHG oraz VHW6SHHG .

P

: A zatem, tak naprawdę, hermetyzacja polega na ochronie danych, tak?

O

: W rzeczywistości hermetyzacja to jeszcze coś więcej! Pozwala ona na separację nie tylko danych, lecz także zachowań. Na przykład mogłeś umieścić w jakiejś metodzie wiele kodu, a tę metodę umieścić w klasie; w ten sposób odseparowałeś pewne zachowanie od pozostałej części kodu, a żeby uzyskać dostęp do tego nowego zachowania, aplikacja musi użyć Twojej nowej klasy i metody. Takie rozwiązanie bazuje na tych samych zasadach co hermetyzacja danych, jednak w tym przypadku ochraniasz całe fragmenty aplikacji, zapewniając, że będą one używane prawidłowo.

P: Rany, sam nie wiem, czy to wszystko

P: Czy są zatem jakieś inne sposoby

rozumiem. Co zatem powinienem zrobić?

stosowania hermetyzacji oprócz ukrywania danych?

O: Po prostu czytaj. Upewnij się, że rozumiesz

O: Oczywiście. Na przykład w rozdziale 1.

zobaczysz, jak można hermetyzować całą grupę właściwości, usunąć je poza obiekt i zapewnić, że obiekt nie będzie ich używać w niewłaściwy sposób. Choć będziemy operować na całym zbiorze właściwości, to jednak i tak sprowadza się to do tego samego — separacji pewnej grupy danych od pozostałej części kodu aplikacji.

rozwiązanie ćwiczenia zamieszczone na stronie 598, a będziesz gotów, by rozpocząć lekturę rozdziału 1. Poświęcimy sporo miejsca rozważaniom na temat tych wszystkich zasad i pojęć obiektowych, zatem absolutnie nie musisz wszystkiego dogłębnie rozumieć już teraz.

Hermetyzacja oddziela dane od zachowania Twojej aplikacji. Dzięki temu możesz kontrolować, w jaki sposób dane są używane przez inne fragmenty aplikacji.

jesteś tutaj  599

Powtórzenie zagadnień podstawowych

Zrozumiałeś już wszystko? Zatem na pewno jesteś gotów do rozpoczęcia lektury całej reszty książki. No i oczywiście… ponownie witamy w prawidłowo zaprojektowanych dzielnicach Obiektowa. Uwielbiamy naszą doskonałą organizację i perfekcyjny projekt — jestem pewna, że Tobie też się one spodobają.

CELNE SPOSTRZEŻENIA „UML to skrót od angielskich słów

Unified Modeling Language (Ujednolicony Język Modelowania). „

UML pomaga w przekazywaniu informacji o strukturze aplikacji innym programistom, klientom i kadrze kierowniczej.

„

Klasa pochodna może przesłonić zachowania zdefiniowane w jej klasie nadrzędnej, by zmienić sposób działania metod.

„O polimorfizmie mówimy

w przypadku, gdy klasa pochodna różni się od klasy nadrzędnej.

Diagram kasy pokazuje jej ogólną postać, zawiera przy tym informacje o jej zmiennych i metodach.

„Polimorfizm sprawia, że aplikacja jest

O dziedziczeniu mówimy wtedy, gdy jedna klasa rozszerza inną, by skorzystać z niej lub zmodyfikować jej zachowania.

„Hermetyzacja polega na oddzielaniu

„

W dziedziczeniu klasa, od której dziedziczymy, jest nazywana klasą nadrzędną (lub bazową), natomiast klasa, która korzysta z dziedziczenia, jest nazywana klasą pochodną.

definiowanie zmiennych składowych klasy jako prywatnych i udostępnianie ich wyłącznie za pośrednictwem odpowiednich metod.

„

Klasa pochodna automatycznie otrzymuje wszystkie zachowania swojej klasy nadrzędnej.

„

„

600

Dodatek B

bardziej elastyczna i łatwiejsza do modyfikacji. i ukrywaniu pewnych fragmentów kodu przed pozostałymi częściami aplikacji. „Najprostszą formą hermetyzacji jest

„Możesz także hermetyzować całe

grupy danych, a nawet zachowania klas, uzyskując kontrolę nad sposobem, w jaki będą one używane.

Witamy w Obiektowie

Zagadkowy basen. Rozwiązanie Twoje zadanie polegało na wyłowieniu odpowiednich fragmentów kodu z basenu przedstawionego u dołu strony i umieszczeniu ich w pustych miejscach w kodzie. Mogłeś użyć tego samego fragmentu kodu więcej niż jeden raz, a niektóre fragmenty mogły się okazać całkowicie niepotrzebne. Twoim celem było stworzenie klasy, którą będzie można skompilować i uruchomić, i która wygeneruje poniższe wyniki.

Wyniki

SXEOLFFODVV)O\7HVW^ SXEOLFVWDWLFYRLGPDLQ 6WULQJ>@DUJV ^ $LUSODQHELSODQH QHZ$LUSODQH  ELSODQHVHW6SHHG BBBBBB  212 biplane.getSpeed() 6\VWHPRXWSULQWOQ BBBBBBBBBBBBBBBBBBBBB  -HWERHLQJ QHZ-HW  ERHLQJVHW6SHHG BBBBBB  422 boeing.getSpeed() 6\VWHPRXWSULQWOQ BBBBBBBBBBBBBBBBBB  int x = 0 BBBBBBBBBBB ZKLOH BBBBBB ^ x