Wydawnictwo Helion
ul. Koœciuszki 1c
44-100 Gliwice
tel. 032 230 98 63
UML. In¿ynieria
oprogramowania.
Wydanie II
Praktyczny podrêcznik do nauki jêzyka UML
• Jak zaprojektowaæ dobry system?
• Jak poprawnie tworzyæ i odczytywaæ modele?
• Jak w praktyce stosowaæ UML i poprawiæ jakoœæ projektowanych produktów?
W œwiecie informatyki dobry projekt to czêsto wiêcej ni¿ po³owa sukcesu, a wraz ze
wzrostem popularnoœci obiektowych jêzyków programowania UML — ujednolicony jêzyk
modelowania przeznaczony do reprezentacji elementów w analizie obiektowej i programowaniu
obiektowym — sta³ siê podstawowym narzêdziem do tworzenia modeli. Dlatego te¿
trudno wyobraziæ sobie dobrego informatyka, który nie potrafi przygotowaæ poprawnego
projektu w tym jêzyku lub odczytaæ modelu utworzonego przez kogoœ innego.
„UML. In¿ynieria oprogramowania. Wydanie II” to przystêpny podrêcznik dla studentów
i informatyków pragn¹cych nie tylko poznaæ ujednolicony jêzyk modelowania, ale przede
wszystkim nauczyæ siê korzystaæ z niego w kontekœcie in¿ynierii oprogramowania.
Czytaj¹c go, dowiesz siê, jak powinien wygl¹daæ dobry system, poznasz sk³adniê
i funkcje jêzyka UML, a przedstawione studia przypadku pozwol¹ Ci zobaczyæ, jak
u¿ywaæ go do projektowania praktycznych rozwi¹zañ.
• Projektowanie systemów bazuj¹cych na komponentach
• Wprowadzenie do obiektowoœci
• Efektywny proces projektowania
• Opracowywanie modeli klas
• Przygotowywanie modeli przypadków u¿ycia
• Tworzenie diagramów interakcji, stanów i aktywnoœci
• Przygotowywanie diagramów struktury i wdro¿eñ
• Stosowanie komponentów i wzorców
• Dbanie o jakoœæ procesu i produktu
• Praktyczne przyk³ady projektowania systemów
Jeœli chcesz tworzyæ oprogramowanie najwy¿szej jakoœci, zacznij od dobrego projektu
Autor: Perdita Stevens
T³umaczenie: Ireneusz Jakóbik
ISBN: 978-83-246-0805-8
Tytu³ orygina³u:
Using UML: Software Engineering
with Objects and Components (2nd Edition)
Format: B5, stron: 304
X
Wstęp ...................................................................................................... 13
Część I Tło koncepcyjne .................................................................. 19
Rozdział I Inżynieria oprogramowania z użyciem komponentów ......................... 21
1.1. Co to jest dobry system? ......................................................................................... 21
1.2. Czy istnieją dobre systemy? ..................................................................................... 22
1.2.1. Problemy… ............................................................................................... 22
1.2.2. …a nawet katastrofalne niepowodzenia .................................................... 24
1.2.3. Obietnice, obietnice .................................................................................. 25
1.3. Jakie są dobre systemy? ............................................................................................ 26
1.3.1. Hermetyzacja: słabe powiązania ................................................................ 27
1.3.2. Abstrakcja: wysoki stopień spójności ......................................................... 30
1.3.3. Architektura i komponenty ........................................................................ 31
1.3.4. Projektowanie oparte na komponentach: modularność ............................ 33
1.4. Jak są zbudowane dobre systemy? ........................................................................... 33
Rozdział 2 Koncepcje obiektów ............................................................................... 35
2.1. Czym jest obiekt? ..................................................................................................... 35
2.1.1. Przykład ..................................................................................................... 37
2.1.2. Komunikaty ................................................................................................ 37
2.1.3. Interfejsy .................................................................................................... 38
2.1.4. Klasy .......................................................................................................... 39
2.2. Jaki to ma związek z celami poprzedniego rozdziału? .............................................. 42
2.2.1. Co wspólnego z komponentami mają obiekty? ......................................... 43
2.3. Dziedziczenie ........................................................................................................... 44
2.4. Polimorfizm i wiązanie dynamiczne ......................................................................... 46
Rozdział 3 Wstępne studium przypadku ................................................................. 49
3.1. Problem ................................................................................................................... 49
3.1.1. Sprecyzowanie wymagań ........................................................................... 49
3.1.2. Model przypadków użycia ......................................................................... 51
3.2. Zakres oraz iteracje ................................................................................................. 53
3.3. Identyfikowanie klas ................................................................................................. 55
3.4. Relacje między klasami ............................................................................................. 57
3.5. System w akcji ......................................................................................................... 60
3.5.1. Zmiany w systemie: diagramy stanów ....................................................... 64
3.5.2. Dalsze prace .............................................................................................. 64
4
UML. Inżynieria oprogramowania
Rozdział 4 Proces projektowania ............................................................................. 67
4.1. Definicje terminów .................................................................................................. 67
4.1.1. Modele i języki modelowania .................................................................... 68
4.1.2. Proces i jakość ........................................................................................... 70
4.2. Proces projektowania .............................................................................................. 70
4.2.1. Zunifikowana metodologia? ....................................................................... 72
4.2.2. Procesy stosowane w UML ....................................................................... 73
4.3. System, projekt, model, diagram ............................................................................. 75
4.3.1. Wykorzystanie modeli ............................................................................... 76
Część II UML ..................................................................................... 79
Rozdział 5 Podstawy modeli klas ............................................................................. 81
5.1. Identyfikowanie obiektów i klas ............................................................................... 81
5.1.1. Co sprawia, że model klasy jest dobry? ..................................................... 81
5.1.2. Jak zbudować dobry model klasy ............................................................... 82
5.1.3. Jakim rodzajem rzeczy są klasy? ................................................................. 85
5.1.4. Obiekty ze świata rzeczywistego a ich reprezentacje w systemie ............ 85
5.2. Powiązania ............................................................................................................... 86
5.2.1. Wielokrotności .......................................................................................... 88
5.3. Atrybuty i operacje .................................................................................................. 89
5.3.1. Operacje .................................................................................................... 89
5.3.2. Atrybuty ..................................................................................................... 89
5.4. Generalizacja ............................................................................................................ 90
5.4.1. Korzystanie z języka polskiego w celu sprawdzenia, czy zachodzi
generalizacja ...................................................................................................... 93
5.4.2. Implementacja generalizacji: dziedziczenie ................................................ 93
5.5. Model klasy podczas opracowywania ...................................................................... 94
5.6. Karty CRC ............................................................................................................... 95
5.6.1. Tworzenie kart CRC ................................................................................. 95
5.6.2. Używanie kart CRC podczas opracowywania projektu ............................ 96
5.6.3. Przykład karty CRC ................................................................................... 97
5.6.4. Refaktoring ................................................................................................ 98
Rozdział 6 Więcej na temat modeli klas ................................................................ 101
6.1. Więcej na temat powiązań ..................................................................................... 101
6.1.1. Agregacja i kompozycja ........................................................................... 101
6.1.2. Role ......................................................................................................... 103
6.1.3. Możliwości nawigacji ............................................................................... 104
6.1.4. Powiązania kwalifikowane ....................................................................... 105
6.1.5. Powiązania pochodne .............................................................................. 107
6.1.6. Ograniczenia ............................................................................................ 108
6.1.7. Klasy powiązań ........................................................................................ 110
6.2. Więcej na temat klas .............................................................................................. 112
6.2.1. Interfejsy .................................................................................................. 113
6.2.2. Klasy abstrakcyjne .................................................................................... 115
6.3. Klasy sparametryzowane ....................................................................................... 117
6.4. Zależność ............................................................................................................... 118
6.5. Komponenty i pakiety ............................................................................................ 119
6.6. Widoczność i ochrona ........................................................................................... 119
Spis treści
5
Rozdział 7 Najważniejsze informacje na temat modeli przypadków użycia ........ 121
7.1. Szczegóły dotyczące aktorów ................................................................................ 123
7.2. Przypadki użycia w szczegółach ............................................................................. 125
7.3. Granice systemu .................................................................................................... 126
7.4. Używanie przypadków użycia ............................................................................... 127
7.4.1. Przypadki użycia podczas gromadzenia wymagań ................................... 127
7.4.2. Przypadki użycia podczas projektowania ................................................ 128
7.5. Możliwe problemy z przypadkami użycia .............................................................. 130
Rozdział 8 Więcej informacji na temat modeli przypadków użycia ..................... 133
8.1. Relacje między przypadkami użycia ....................................................................... 133
8.1.1. Przypadki użycia do wielokrotnego użycia: <<include>> .................... 134
8.1.2. Komponenty i przypadki użycia ............................................................... 136
8.1.3. Rozdzielanie wariantów zachowania: <<extend>> ............................. 138
8.2. Generalizacje ......................................................................................................... 139
8.3. Aktorzy i klasy ........................................................................................................ 140
8.3.1. Notacja: aktorzy jako klasy ...................................................................... 141
Rozdział 9 Najważniejsze informacje na temat diagramów interakcji .................. 143
9.1. Współprace ............................................................................................................ 144
9.2. Diagramy komunikacji ........................................................................................... 145
9.3. Diagramy sekwencji ............................................................................................... 147
9.4. Więcej zaawansowanych funkcji ............................................................................ 150
9.4.1. Komunikaty z obiektu do samego siebie ................................................. 150
9.4.2. Zwracane wartości .................................................................................. 151
9.4.3. Tworzenie i usuwanie obiektów ............................................................. 152
9.5. Diagramy interakcji służące innym celom .............................................................. 154
9.5.1. Pokazywanie, jak klasa udostępnia operację ............................................ 154
9.5.2. Opisywanie, jak działa wzorzec projektowy ........................................... 154
9.5.3. Opisywanie, jak można użyć komponentu .............................................. 154
Rozdział 10 Więcej informacji na temat diagramów interakcji ............................... 155
10.1. Więcej niż tylko proste sekwencje komunikatów ................................................ 155
10.1.1. Zachowania warunkowe ....................................................................... 155
10.1.2. Iteracja ................................................................................................... 157
10.2. Współbieżność ..................................................................................................... 158
10.2.1. Modelowanie ścieżek kontroli ............................................................... 159
Rozdział 11 Najważniejsze informacje na temat diagramów stanów i aktywności ......165
11.1. Diagramy stanów ................................................................................................. 166
11.1.1. Niespodziewane komunikaty ................................................................ 167
11.1.2. Poziom abstrakcji ................................................................................... 168
11.1.3. Stany, zmiany stanu i zdarzenia .............................................................. 168
11.1.4. Akcje ...................................................................................................... 169
11.1.5. Dozór .................................................................................................... 171
11.2. Diagramy aktywności ........................................................................................... 174
Rozdział 12 Więcej informacji na temat diagramów stanów ................................... 179
12.1. Inne rodzaje zdarzeń ........................................................................................... 179
12.2. Inne rodzaje akcji ................................................................................................. 180
12.3. Zaglądanie do wnętrza stanów ............................................................................ 181
12.4. Współbieżność w obrębie stanów ....................................................................... 183
6
UML. Inżynieria oprogramowania
Rozdział 13 Diagramy architektury i wdrożeń ....................................................... 185
13.1. Diagramy struktury komponentów ..................................................................... 185
13.2. Model wdrożeń ................................................................................................... 187
13.2.1. Warstwa fizyczna ................................................................................... 187
13.2.2. Wdrażanie oprogramowania na sprzęcie ............................................... 187
Rozdział 14 Pakiety i modele ................................................................................... 191
14.1. Pakiety ................................................................................................................. 191
14.1.1. Kontrolowanie przestrzeni nazw ........................................................... 192
14.2. Modele ................................................................................................................. 194
Część III Studia przypadków ........................................................... 197
Rozdział 15 Administrowanie I4 .............................................................................. 199
15.1. Studium przypadku .............................................................................................. 199
15.1.1. Model klas .............................................................................................. 203
15.1.2. Dynamika ............................................................................................... 204
15.1.3. Diagramy stanów ................................................................................... 204
15.1.4. Diagramy aktywności ............................................................................. 204
15.2. Dyskusja ............................................................................................................... 205
Rozdział 16 Gry planszowe ...................................................................................... 207
16.1. Zakres i wstępna analiza ...................................................................................... 208
16.1.1. „Kółko i krzyżyk” ................................................................................... 208
16.1.2. Szachy .................................................................................................... 209
16.2. Interakcja ............................................................................................................. 213
16.3. Z powrotem do szkieletu aplikacji ....................................................................... 215
16.4. Stany .................................................................................................................... 217
Rozdział 17 Symulacja metodą kolejnych zdarzeń ................................................. 219
17.1. Wymagania ........................................................................................................... 219
17.1.1. Bardziej szczegółowy opis ..................................................................... 220
17.2. Zarys modelu klasy .............................................................................................. 222
17.3. Przypadki użycia .................................................................................................. 224
17.3.1. Podsumowanie przypadku użycia tworzenie modelu ............................ 224
17.3.2. Podsumowanie przypadku użycia obserwowanie zachowania .............. 225
17.3.3. Podsumowanie przypadku użycia zbieranie danych statystycznych ...... 225
17.3.4. Podsumowanie przypadku użycia uruchomienie modelu ...................... 225
17.4. Standardowy mechanizm symulacji opartej na procesie ...................................... 226
17.5. Powiązania i możliwości nawigacji ....................................................................... 227
17.6. Klasy w szczegółach ............................................................................................. 230
17.6.1. Klasa Zarzadca ....................................................................................... 230
17.6.2. Klasa JednostkaAktywna ........................................................................ 231
17.6.3. Klasa JednostkaPasywna ........................................................................ 233
17.6.4. Klasa Zasob ............................................................................................ 233
17.7. Klasa Raport ......................................................................................................... 236
17.8. Klasa DaneStatystyczne ....................................................................................... 236
17.8.1. Klasa Srednia .......................................................................................... 236
17.9. Budowanie kompletnego modelu symulacji ......................................................... 237
17.10. Ucztujący filozofowie ......................................................................................... 238
Spis treści
7
Część IV W stronę praktyki ............................................................. 241
Rozdział 18 Wielokrotne używanie: komponenty i wzorce .................................... 243
18.1. Praktyczne informacje na temat wielokrotnego używania ................................... 243
18.1.1. Co może być użyte wielokrotnie i w jaki sposób? ................................. 244
18.1.2. Dlaczego używać powtórnie? ................................................................ 246
18.1.3. Dlaczego używanie wielokrotne jest trudne? ........................................ 247
18.1.4. Które komponenty w naturalny sposób nadają się
do powtórnego użycia? ........................................................................ 248
18.1.5. A co z budowaniem własnych komponentów? ...................................... 249
18.1.6. Jaką różnicę sprawia zorientowanie obiektowe? ................................... 250
18.2. Wzorce projektowe ............................................................................................. 251
18.2.1. Przykład: Fasada .................................................................................... 254
18.2.2. Język UML i wzorce .............................................................................. 254
18.3. Szkielety ............................................................................................................... 256
Rozdział 19 Jakość produktu: weryfikowanie, walidacja i testowanie .................... 257
19.1. Omówienie jakości .............................................................................................. 257
19.2. W jaki sposób można osiągnąć wysoką jakość? .................................................... 258
19.2.1. Nastawienie na produkt ........................................................................ 258
19.2.2. Nastawienie na proces .......................................................................... 258
19.2.3. Dalsza lektura ........................................................................................ 259
19.3. Weryfikacja .......................................................................................................... 259
19.4. Walidacja .............................................................................................................. 260
19.4.1. Użyteczność .......................................................................................... 261
19.5. Testowanie .......................................................................................................... 262
19.5.1. Wybieranie i przeprowadzanie testów .................................................. 263
19.5.2. Dodatkowe problemy związane ze zorientowaniem obiektowym ....... 265
19.5.3. Dlaczego testowanie jest często przeprowadzane źle? ......................... 267
19.6. Przeglądy i inspekcje ............................................................................................ 268
19.6.1. Problemy związane z przeglądami FTR ................................................. 269
Rozdział 20 Jakość procesu: zarządzanie, zespoły i kontrola jakości ...................... 271
20.1. Zarządzanie ......................................................................................................... 271
20.1.1. Zarządzanie projektem .......................................................................... 272
20.1.2. Szacowanie projektu iteracyjnego ......................................................... 273
20.1.3. Zarządzanie projektowaniem opartym na komponentach .................... 274
20.1.4. Zarządzanie ludźmi ................................................................................ 275
20.2. Zespoły ................................................................................................................ 276
20.3. Przywództwo ...................................................................................................... 277
20.3.1. Zmiana procesu projektowania ............................................................. 278
20.4. Kontrola jakości ................................................................................................... 279
20.4.1. Kontrola jakości w procesach iteracyjnych ............................................ 281
20.4.2. Kompleksowe zarządzanie jakością ....................................................... 281
20.5. Dalsza lektura ...................................................................................................... 283
Skorowidz ............................................................................................ 289
W tym rozdziale będziemy kontynuować udzielanie odpowiedzi na pytanie „jakie są dobre
systemy?”, opisując zorientowany obiektowo paradygmat, który zawdzięcza swoją popular-
ność częściowo temu, że wspiera on projektowanie dobrych systemów. Korzystanie jednak
ze zorientowania obiektowego nie jest ani niezbędne, ani wystarczające do budowania do-
brych systemów. Udzielimy odpowiedzi na następujące pytania:
· Czym jest obiekt?
· W jaki sposób porozumiewają się obiekty?
· Jak jest zdefiniowany interfejs obiektu?
· Co obiekty mają wspólnego z komponentami?
Pod koniec omówimy dziedziczenie, polimorfizm i wiązanie dynamiczne.
Treści zawarte w tym rozdziale odnoszą się do standardowych, nowoczesnych, opartych
na klasach, obiektowo zorientowanych języków, takich jak Java, C# i C++. Większość roz-
działu można odnieść też do języków Smalltalk, Eiffel, CLOS, Perl5 i innych obiektowo zo-
rientowanych języków, z którymi można się spotkać, jednak dokładne ich porównywanie
wykracza poza zakres tej książki.
W niniejszym rozdziale są zawarte pytania na temat sposobów, w jaki języki programo-
wania używane przez
Czytelnika udostępniają omawiane obiektowo zorientowane funkcje.
Jeśli Czytelnik zna już jakiś język programowania, powinien być w stanie udzielić odpowie-
dzi na te pytania. Jeśli nie, warto aby przeczytał ten rozdział do końca, poznał podstawy wy-
branego języka, a następnie powrócił do pytań w celu sprawdzenia swojej wiedzy na temat
sposobów realizowania przez język zorientowania obiektowego. W pytaniach język progra-
mowania używany przez Czytelnika będziemy w skrócie nazywać „Twoim językiem”.
2.1. Czym jest obiekt?
Pojęciowo obiekt jest elementem, z którym można wchodzić w interakcje: można wysyłać
do niego różne komunikaty, a on będzie reagował. Zachowania obiektu zależą od jego bie-
żącego stanu wewnętrznego, który może ulegać zmianie, na przykład na skutek reakcji na
36
Część I ¨ Tło koncepcyjne
otrzymany komunikat. Ważne jest, z którym obiektem zachodzi interakcja, i dlatego odnie-
sienie do obiektu następuje za pośrednictwem jego nazwy. Obiekt ma zatem swoją tożsa-
mość, która odróżnia go od wszystkich innych obiektów. Obiekt jest więc elementem posia-
dającym swoje zachowanie, stan i tożsamość; w taki sposób charakteryzuje go Grady Booch
[7]. Rozważymy te aspekty bardziej szczegółowo.
Przedmiot
Pojęcie przedmiotu jest bardziej interesujące, niż może się to początkowo wydawać. Obiekt
nie jest tylko elementem systemu; reprezentuje on w systemie koncepcję przedmiotu. Obiekt
może być na przykład reprezentacją prawdziwego przedmiotu, takiego jak zegar, tablica
rozdzielcza albo klient. W rzeczywistości obiekty pojawiły się jawnie po raz pierwszy w ję-
zyku Simula, w którym reprezentowały obiekty z realnego świata — te, które były symulo-
wane. Oczywiście, aby systemy programistyczne ogólnego przeznaczenia mogły być two-
rzone w oparciu o obiekty, obiekty nie muszą reprezentować fizycznie istniejących rzeczy.
Do kwestii sposobu rozpoznawania obiektów powrócimy w rozdziale 5.
P: Które z elementów następującej listy mogą być obiektami: młotek, kula, proces chemiczny,
miłość, mgła, rzeka, złość, kot, szarość, szary kot?
Stan
Stan obiektu to wszystkie dane aktualnie w nim zapisane. Zwykle obiekt ma wiele nazwa-
nych atrybutów (zmiennych obiektu, danych składowych), z których każdy ma określoną
wartość. Niektóre z atrybutów mogą podlegać zmianom, to znaczy ich wartości mogą się zmie-
niać. Obiekt reprezentujący na przykład klienta może mieć atrybut o nazwie adres, którego
wartość ulegnie modyfikacji po przeprowadzce klienta. Inne atrybuty mogą być niezmienne,
czyli stałe. Obiekt reprezentujący klienta może mieć atrybut, którego wartością będzie numer
identyfikacyjny klienta, pozostający bez zmian przez cały czas trwania życia obiektu. W większo-
ści współczesnych obiektowo zorientowanych języków programowania zestaw atrybutów
przypisanych obiektowi nie może ulec zmianie podczas trwania życia tego obiektu, chociaż
wartości atrybutów mogą się zmieniać.
Zachowanie
Zachowanie oznacza sposób, w jaki obiekt działa i reaguje w sensie zmian swojego stanu
i przekazywania komunikatów. Obiekt rozumie niektóre komunikaty, co znaczy, że może je
odbierać i zgodnie z nimi postępować. Zestaw komunikatów rozumianych przez obiekt, po-
dobnie jak i zestaw atrybutów, jest zwykle stały. Sposób, w jaki obiekt reaguje na komunikat,
może zależeć od aktualnych wartości jego atrybutów. Dzięki temu, nawet jeśli świat zewnętrzny
nie ma zagwarantowanego bezpośredniego dostępu do atrybutów (a zazwyczaj właśnie tak
jest), może pośrednio wpływać na ich wartości. W taki sposób wartości atrybutów decydują
o stanie obiektu.
Rozdział 2. ¨ Koncepcje obiektów
37
Tożsamość
To zagadnienie może być trochę trudne. Idea jest taka, że obiekty są definiowane nie tylko
przez bieżące wartości swoich atrybutów. Istnienie obiektu jest ciągłe. Wartości atrybutów
mogą na przykład ulec zmianie, zwykle w odpowiedzi na komunikat, ale to będzie ciągle ten
sam obiekt. Odniesienie do obiektu następuje zazwyczaj przez jego nazwę (wartość zmien-
nej w programie-kliencie, na przykład
Klient
), ale nazwa obiektu to nie to samo, co sam
obiekt, ponieważ obiektowi można nadać kilka różnych nazw (co może być zaskakujące dla
programistów funkcyjnych, a niektórzy z nich twierdzą, że zorientowanie obiektowe można
zrealizować w czysto funkcyjny sposób; jednak tego zagadnienia nie będziemy poruszać
w niniejszej książce).
2.1.1. Przykład
Rozważmy obiekt o nazwie
mojZegar
, który rozumie komunikaty
podajCzas
i
ustawCzasNa(07:43)
,
i w ogólności
ustawCzasNa(nowyCzas)
dla sensownych wartości parametru
nowyCzas
. Można to
zrealizować, tworząc interfejs, który ogłosi, że przyjmuje komunikaty w postaci
podajCzas
i
ustawCzasNa(nowyCzas: Czas)
, gdzie
Czas
jest typem
1
, którego elementy są sensownymi
wartościami wspomnianego parametru
nowyCzas
.
W jaki sposób zaimplementować taką funkcję? Świat zewnętrzny nie musi tego wiedzieć
(informacja powinna być ukryta), ale być może obiekt ma atrybut
Czas
, którego wartość
zwraca w odpowiedzi na komunikat
podajCzas
i który jest ustawiany na wartość
nowyCzas
po
otrzymaniu przez obiekt komunikatu
ustawCzasNa(nowyCzas)
. Jest też możliwe, że komuni-
kat jest przekazywany do innego obiektu, znanego obiektowi
mójZegar
, i że to właśnie ten
inny obiekt pracuje z komunikatami.
2.1.2. Komunikaty
Z poprzedniego przykładu można wysnuć kilka wniosków na temat komunikatów. W skład
komunikatu wchodzi słowo kluczowe nazywane selektorem. W przykładzie selektorami są
podajCzas
i
ustawCzasNa
. Komunikat może, ale nie musi, zawierać jeden lub więcej argu-
mentów, które są wartościami przekazywanymi obiektowi, podobnie jak są przekazywane
wartości w przypadku wywoływania funkcji. W powyższym przykładzie
07:43
jest argu-
mentem, stanowiącym część komunikatu
ustawCzasNa(07:43)
. Zazwyczaj dla danego selektora
istnieje pojedyncza „prawidłowa” liczba argumentów, które powinny znajdować się w ko-
munikacie rozpoczynającym się tym od tego selektora. Nie można oczekiwać, że obiekt
mojZegar
zrozumie komunikat
ustawCzasNa
bez żadnego argumentu lub w postaci
ustawCzasNa(4, 12:30)
i tak dalej. Akceptowalne wartości argumentów również są ustalane w interfejsie obiektu
2
.
1
Albo klasą; klasy omówimy w podrozdziale 2.1.4.
2
Podając klasę (patrz podrozdział 2.1.4) lub niekiedy tylko interfejs, do których powinien należeć obiekt
przekazywany jako argument, lub jego typ, jeśli należy do typu podstawowego, takiego jak
int
albo
bool
— szczegóły zależą od konkretnego języka.
38
Część I ¨ Tło koncepcyjne
P: Jaka jest składnia przesyłania komunikatów w Twoim języku?
Warto zauważyć, że wartości, na przykład wartości atrybutów obiektu albo wartości ar-
gumentów przesyłane w komunikatach, nie muszą należeć do typu podstawowego (takiego
jak znaki, liczby całkowite i tak dalej. Rodzaje typów podstawowych zależą od konkretnego
języka). Mogą to być również obiekty.
P: Jakie są podstawowe typy w Twoim języku?
Jedną z czynności, którą może wykonać obiekt w odpowiedzi na komunikat, jest wysła-
nie komunikatu do innego obiektu. Jeśli tak ma się stać, to powinien on w jakiś sposób po-
znać nazwę tego obiektu. Prawdopodobnie wartością jednego z jego atrybutów mógłby być
na przykład obiekt, którego nazwa byłaby wykorzystywana podczas wysyłania komunikatu.
P: Niech
o
będzie obiektem. Do jakich innych obiektów może wysyłać komunikaty obiekt
o
, oprócz
obiektów, dla których może być wartością ich atrybutów?
Warto zwrócić uwagę na to, że podczas wysyłania komunikatu do obiektu nie wiadomo,
jaki kod zostanie w wyniku wykonany, gdyż informacja o tym jest hermetyzowana. Będzie to
istotne pod koniec niniejszego rozdziału, kiedy omówimy wiązanie dynamiczne.
2.1.3. Interfejsy
Publiczny interfejs definiuje komunikaty, które będą przyjmowanie przez obiekt niezależnie
od miejsca ich pochodzenia. Zazwyczaj interfejs zapisuje selektory tych komunikatów, łącznie
z niektórymi informacjami na temat wymaganych argumentów oraz wartości, która ewentu-
alnie mogłaby być zwrócona. Jak wspomnieliśmy w rozdziale 1., korzystne byłoby, gdyby
interfejs zawierał jakiś rodzaj specyfikacji dotyczącej skutków wysłania komunikatu do obiektu.
Informacje tego typu są jednak podawane jedynie w komentarzach i dokumentacji. W więk-
szości języków programowania atrybuty mogą być zawarte również w publicznym interfejsie
obiektu. Umieszczenie atrybutu
x
w interfejsie jest równoważne z zadeklarowaniem, że
fragment danych tego obiektu,
x
, może być widziany i zmieniany z zewnątrz.
Zagadnienie do przedyskutowania 9
Jeśli korzystasz z języka, który nie zezwala na umieszczanie atrybutów w interfejsie publicz-
nym, to w jaki sposób możesz osiągnąć taki sam efekt, używając komunikatów w celu uzyska-
nia dostępu do danych? Czy są jakieś zalety stosowania takiego rozwiązania nawet w językach,
które dopuszczają używanie atrybutów w interfejsie? A wady?
Często jest tak, że nie każdy element systemu powinien mieć możliwość wysyłania do
obiektu wszystkich komunikatów, które dany obiekt mógłby zrozumieć. Dlatego też obiekt
może zwykle zrozumieć pewne komunikaty, które nie są częścią jego publicznego interfejsu.
Jak już na przykład wyjaśniliśmy w rozdziale 1., struktura danych używana przez moduł po-
winna być hermetyzowana, co jest zgodne z założeniem, że interfejs publiczny obiektu nie
powinien zazwyczaj zawierać atrybutów.
Obiekt zawsze może wysłać sam do siebie komunikat, który będzie w stanie zrozumieć.
Wysyłanie komunikatów samemu sobie może wydawać się dziwne lub zbyt skomplikowane,
Rozdział 2. ¨ Koncepcje obiektów
39
jednak powód takiego postępowanie wyjaśnimy w dalszej części niniejszego rozdziału przy
okazji omawiania wiązania dynamicznego.
Zazwyczaj obiekt ma przynajmniej dwa interfejsy: publiczny, z którego może korzystać
dowolny element systemu, oraz obszerniejszy interfejs prywatny, z którego może korzystać
zarówno sam obiekt, jak i inne uprzywilejowane elementy systemu. Czasami może być jesz-
cze przydatny interfejs pośredni, udostępniający więcej możliwości niż interfejs publiczny,
ale mniej niż interfejs prywatny. Czasami w danym kontekście jest potrzebna tylko część
publicznego interfejsu obiektu, co powoduje wzrost liczby obiektów, które potencjalnie mo-
głyby być używane zastępczo. Może być zatem przydatne zapisywanie naprawdę potrzeb-
nych funkcji w mniejszym interfejsie, zamiast umieszczania ich w całym interfejsie publicz-
nym obiektu. Z tego względu obiekt może mieć (lub realizować) więcej niż tylko jeden
interfejs. I odwrotnie — wiele różnych obiektów może realizować taki sam interfejs.
W tej książce określenie „interfejs” zawsze będzie oznaczać interfejs publiczny. Jeśli bę-
dzie inaczej, zostanie to wyraźnie określone.
2.1.4. Klasy
Do tej pory omawialiśmy obiekty, jak gdyby każdy z nich był osobno definiowany, każdy miał
swój własny interfejs i sposób sprawdzania, który inny obiekt może wysyłać do niego komu-
nikaty. Oczywiście taki sposób tworzenia typowego systemu nie jest rozsądny, ponieważ
obiekty mają ze sobą wiele wspólnego. Jeśli przedsiębiorstwo ma 10 000 klientów i potrzeb-
ne jest zbudowanie systemu z obiektami reprezentującymi każdą z tych osób, to wszystkie
obiekty reprezentujące klientów będą posiadały wiele wspólnych cech. Ich zachowania po-
winny być spójne, aby programiści i osoby opiekujące się systemem mogły je zrozumieć.
Potrzebna będzie klasa obiektów reprezentująca klientów. Klasa opisuje zestaw obiektów
pełniących w systemie takie same funkcje.
W opartych na klasach, zorientowanych obiektowo językach programowania każdy obiekt
należy do klasy, a klasa obiektu determinuje jego interfejs
3
. Obiekt
mojZegar
może należeć do
klasy
Zegar
, której interfejs publiczny określa, że każdy obiekt klasy
Zegar
będzie udostępniał
operację
ustawCzasNa(nowyCzas: Czas)
. Znaczy to, że będzie rozumiał komunikaty z selekto-
rem
ustawCzasNa
i pojedynczym argumentem, który jest obiektem klasy (typu)
Czas
.
W rzeczywistości nawet sposób, w jaki obiekt reaguje na komunikaty, jest zdeterminowany
przez jego klasę, łącznie z wartościami atrybutów obiektu. Metoda jest określonym frag-
mentem kodu, w którym są zaimplementowane operacje. W interfejsie obiektu widoczne
jest jedynie to, że obiekt wykonuje operację; metoda, w której jest zaimplementowana funk-
cja, jest ukryta.
Podobnie zestaw atrybutów posiadanych przez obiekt jest zdeterminowany przez klasę,
chociaż oczywiście wartości przyjmowane przez atrybuty obiektu nie są określone w klasie
i mogą ulegać zmianom. Być może na przykład obiekty należące do klasy
Zegar
mają pry-
watny atrybut o nazwie
czas
. Jeśli taki sam komunikat zostanie wysłany do dwóch obiektów
tej samej klasy, w obu przypadkach zostanie wykonana ta sama metoda, chociaż efekt jej
wykonania może być znacząco różny, gdy atrybuty obiektów będą mieć inne wartości.
3
W rzeczywistości obiekt może należeć do więcej niż jednej klasy, z których klasa najbardziej szczególna
decyduje o jego interfejsie.
40
Część I ¨ Tło koncepcyjne
Podsumowując, obiekty tej samej klasy mają takie same interfejsy. Publiczne i prywatne
interfejsy klasy
Zegar
mogłyby być opisane następująco:
- czas : Czas
+ podajCzas() : Czas
+ ustawCzasNa (nowyCzas: Czas)
W skład interfejsu publicznego wchodzą wiersze oznaczone znakiem
+
, a w skład inter-
fejsu prywatnego także wiersz ze znakiem
–
.
P: W jaki sposób definiuje się klasę, taką jak
Zegar
w Twoim języku? Czy interfejs i implementacja
są definiowane oddzielnie? Jak jest nazywany interfejs publiczny, a jak prywatny (może właśnie: pu-
bliczny i prywatny)? Czy są może jeszcze jakieś inne możliwości? Co one oznaczają?
Proces tworzenia nowego obiektu należącego do klasy
Zegar
jest nazywany tworzeniem
egzemplarza klasy
Zegar
, a powstały w wyniku obiekt egzemplarzem klasy
Zegar
. To właśnie
dlatego zmienne, których wartości należą do obiektu i które mogą ulegać zmianie w czasie
życia obiektu, są nazywane zmiennymi egzemplarza. Utworzenie obiektu oznacza utworze-
nie nowego egzemplarza klasy posiadającego własny stan i tożsamość, które będą pozosta-
wać spójne w obrębie wszystkich obiektów klasy. Proces tworzenia zazwyczaj obejmuje także
nadanie wartości atrybutom. Wartości mogą zostać określone w zleceniu utworzenia lub na-
dane przez klasę obiektu jako domyślne wartości początkowe. W językach takich jak C++
i Java klasa odgrywa bezpośrednią rolę podczas tworzenia nowych obiektów — w rzeczy-
wistości wykonuje całą pracę, nie ograniczając się jedynie do roli szablonu. Dlatego klasa
może być często uważana za fabrykę obiektów.
P: W jaki sposób jest tworzony w Twoim języku nowy obiekt danej klasy?
W wielu językach klasy mogą zachowywać się, jak gdyby same były obiektami. Klasa
Klient
mogłaby mieć atrybut
liczbaEgzemplarzy
, który byłby liczbą całkowitą zwiększaną za
każdym razem, gdy tworzony jest nowy obiekt typu
Klient
. Takie rozwiązanie konsekwentnie
przyjęto w języku Smalltalk, a w językach C++ i Java można odnaleźć jego ślady. W Smalltalk
na przykład nowy obiekt typu
Klient
zostałby utworzony przez wysłanie komunikatu (przy-
kładowo
new
) do klasy
Klient
. Reakcją klasy byłoby utworzenie nowego obiektu i zwrócenie
go do metody wywołującej.
Zagadnienie do przedyskutowania 10
Zastanów się, co by było, gdyby konsekwentnie stosować takie rozwiązanie. Stwierdziliśmy
już, że każdy obiekt ma swoją klasę. Jeśli więc klasa
k
zostanie uznana za obiekt, to co będzie
klasą dla
k
? I co będzie klasą tej klasy?
P: Czy Twój język posiada takie funkcje? Jakie dokładnie?
Dygresja: po co istnieją klasy?
Dlaczego nie wystarczą obiekty, które posiadają wymagany stan, zachowanie i tożsamość?
Klasy w zorientowanych obiektowo językach programowania służą dwóm celom. Po pierw-
sze, są wygodnym sposobem na opisanie zbioru (klasy) obiektów posiadających takie same
właściwości. W językach zorientowanych obiektowo klasy są używane przede wszystkim dla
Rozdział 2. ¨ Koncepcje obiektów
41
wygody. Nie ma na przykład potrzeby przechowywania kopii kodu opisującego zachowania
obiektu w każdym obiekcie, chociaż można wyobrażać sobie, że w każdym obiekcie znajduje
się jego kod. Zamiast tego programista może jednorazowo napisać definicję klasy, a kompi-
lator utworzy pojedynczą reprezentację klasy i pozwoli obiektom na uzyskiwanie dostępu do
reprezentacji klasy w celu pobierania potrzebnego kodu.
Należy pamiętać, że taka wygoda wcale nie jest czymś błahym. Zasadniczym celem jest
przecież uczynienie systemów łatwiejszymi do zrozumienia, aby ich projektowanie i pielę-
gnacja były jak najbardziej tanie, łatwe i niezawodne. Sprawienie, że podobne obiekty dzielą
ten sam projekt oraz implementację, jest ważnym krokiem w tym kierunku.
W taki sposób przejawia się zasada
4
, która jest tak ważna w inżynierii oprogramowania
— nie tylko w odniesieniu do kodu — że ją wykrzyczymy:
PISZ TYLKO RAZ!
Oznacza to, że jeśli dwie kopie jakiegoś produktu inżynierii oprogramowania, na przy-
kład fragment kodu, fragment diagramu czy fragment tekstu pochodzący z dokumentu, po-
winny być spójne, to nie powinny istnieć w dwóch egzemplarzach. Za każdym razem, gdy ta
sama informacja jest zapisywana w więcej niż jednym miejscu, nie tylko marnuje się wysiłek
na powtórne robienie tego samego, zamiast zrobienia czegoś nowego, ale też naraża się na
kłopoty związane z pielęgnacją, ponieważ dwie wersje nigdy nie będą zsynchronizowane
bez żadnych starań. Sztuka stosowania tej zasady polega na stwierdzeniu, czy dwie kopie infor-
macji są rzeczywiście takie same, czy też zdarzyło się, że potencjalnie różne informacje są
akurat w danym momencie identyczne. W tym drugim przypadku skopiowanie i wklejenie
odpowiedniego fragmentu jest jak najbardziej prawidłowe. W pierwszym przypadku niechęć
do powielania informacji w więcej niż jednym miejscu jest zaletą. Należy tak postąpić wy-
łącznie wtedy, gdy istnieją ku temu dobre powody.
Zagadnienie do przedyskutowania 11
Czy ta zasada oznacza, że powielanie danych w rozproszonych bazach danych jest złym pomy-
słem? Dlaczego?
Zauważmy, że istnieją inne sposoby na tworzenie wielu podobnych obiektów, pisząc ich
kod tylko raz. Można na przykład zdefiniować prototyp obiektu, a następnie definiować ko-
lejne obiekty jako będące „takie jak tamten, ale z następującymi różnicami”. W takim przy-
padku również wystarczyłoby przechowywanie tylko jednej kopii kodu. Mniej więcej w taki
sposób postępuje się, programując w eleganckim, opartym na prototypach, języku Self. Ję-
zyki programowania oparte na klasach zdominowały jednak obecnie zorientowanie obiekto-
we i zapewne tak już pozostanie.
Po drugie, w większości zorientowanych obiektowo językach programowania klasy są
używane w taki sam sposób, jak w wielu innych językach są wykorzystywane typy — w celu
określenia, jakie wartości są dopuszczalne w określonych kontekstach, umożliwiając tym
samym kompilatorowi (oraz, co jest równie ważne, osobie czytającej kod) zrozumienie in-
tencji programisty i sprawdzenie, czy pewne rodzaje błędów nie wystąpią w programie.
4
Na którą kładzie nacisk zwłaszcza Kent Beck.
42
Część I ¨ Tło koncepcyjne
Wiele osób uważa, że klasy i typy oznaczają tę samą rzecz. Rzeczywiście, jest to wygod-
ne i często nie prowadzi do żadnych nieporozumień, niemniej taki sposób myślenia jest
błędny. Należy pamiętać, że klasa definiuje nie tylko to, jakie komunikaty zrozumie obiekt,
co tak naprawdę wystarcza, aby sprawdzać, czy jest on akceptowalny w danym kontekście.
Określa również czynności obiektu wykonywane w odpowiedzi na komunikaty.
2.2. Jaki to ma związek z celami poprzedniego rozdziału?
Szukamy modułów, najlepiej nadających się do wielokrotnego używania wymiennych mo-
dułów, które mogłyby być dobrymi komponentami. Gdzie można je znaleźć w systemie
obiektowo zorientowanym?
Poszczególne obiekty mogłyby być uważane za moduły, ale nie byłyby one dobre. W syste-
mie znajduje się bowiem zazwyczaj wiele różnych modułów, które są blisko spokrewnione
pojęciowo. Gdyby każdy obiekt był uważany za odrębny moduł, to albo istniałyby wprowa-
dzające w błąd nieścisłości między pojęciowo powiązanymi modułami, albo potrzeba za-
chowania spójności powodowałaby tworzenie silnych skojarzeń.
Klasy powinny być słabo skojarzonymi, wysoce spójnymi modułami. Można zatem mieć
nadzieję (być może na próżno, co przedyskutujemy w następnym podrozdziale), że będzie
realne uzyskanie korzyści przedstawionych w rozdziale 1., a mianowicie niskiego kosztu
i krótkiego czasu opracowywania, łatwości pielęgnacji i wysokiej niezawodności. Zalety te
nie są cechą charakterystyczną zorientowania obiektowego, chociaż jest ono obecnie najle-
piej poznaną ścieżką do nich prowadzącą.
Kolejna uznana zaleta zorientowania obiektowego należy do innej kategorii. Naturalne
jest postrzeganie otaczającego świata w kategoriach obiektów. Warto przypomnieć sobie, że
głównym wymogiem stawianym przed dobrym systemem jest spełnianie oczekiwań użyt-
kowników i że prawidłowe uchwycenie tych oczekiwań oraz śledzenie ich zmian należą do
najtrudniejszych zadań związanych z tworzeniem systemów. Ważne jest, aby model systemu
(model domeny problemu i zachodzących procesów) był zgodny z modelem użytkownika.
Wydaje się, że obiekty z domeny problemu rzadziej ulegają zmianom, a same zmiany są
mniej dramatyczne, niż jest to w przypadku działania systemu wymaganego przez użytkow-
nika. Jeśli zatem system jest zbudowany w oparciu o te obiekty, to zmiany w systemie praw-
dopodobnie będą mniej rewolucyjne, niż gdyby system powstał w oparciu o bardziej ulotne
aspekty funkcjonalne. Podsumowując, należy mieć nadzieję, że system będzie zgodny z mo-
delem świata użytkownika, dzięki czemu będzie możliwe:
· łatwiejsze i dokładniejsze uchwycenie wymagań;
· lepsze nadążanie za zmianami wymagań użytkownika;
· tworzenie systemów, które prowadzą interakcje w bardziej naturalny sposób. Łatwiejsze
jest zaimplementowanie na przykład interfejsu użytkownika, który pozwala użytkowni-
kowi na interakcję z plikiem, zegarem albo drukarką jako całością (co ma sens z punktu
widzenia użytkownika, ponieważ te przedmioty są podobne do obiektów z rzeczywistego
świata), jeśli są one spójnymi obiektami także z punktu widzenia systemu. Z tego powo-
du interfejsy użytkownika były jednymi z pierwszych głównych obszarów, w których zo-
rientowanie obiektowe stało się popularne.
Rozdział 2. ¨ Koncepcje obiektów
43
Należy jednak zaznaczyć, że te argumenty nie są bezdyskusyjne. W niektórych przypad-
kach, takich jak na przykład systemy obsługi zamówień, podejście zorientowane obiektowo
wydaje się bardzo naturalne, w innych (na przykład kompilatory) taka argumentacja jest
mniej przekonująca.
W każdym jednak przypadku gdy zorientowanie obiektowe osiąga swój cel, zwykle nie
jest tak z powodu charakterystycznych cech zorientowania obiektowego. Jest tak, ponieważ:
· podstawami zorientowania obiektowego są modularność, hermetyzacja i abstrakcja,
· zorientowane obiektowo języki programowania sprawiają, że osiąganie celów staje się
względnie proste, ponieważ istnieje uzasadnione prawdopodobieństwo, że oczywisty sposób
zrobienia czegoś jest również dobrym sposobem na zrobienie tego.
Zorientowanie obiektowe jest religią: to ludzie zostają zorientowani w stronę obiektów.
Zagadnienie do przedyskutowania 12
Istnienie jakich konsekwencji, dobrych i złych, może sugerować stwierdzenie, że „zorientowanie
obiektowe jest religią”?
2.2.1. Co wspólnego z komponentami mają obiekty?
Euforia towarzysząca zorientowaniu obiektowemu mogłaby czasami sugerować, że każda
klasa jest automatycznie komponentem, nadającym się do wielokrotnego używania. Oczywi-
ście nie jest to prawdą. W rozdziale 1. omówiliśmy powody, dla których możliwość wielo-
krotnego używania komponentu nie jest po prostu faktem o komponencie jako takim, ale
zależy od kontekstu projektu i proponowanego sposobu jego powtórnego użycia. Innym ważnym
czynnikiem jest to, że struktura klasy może być zbyt szczegółowa, aby mogła być efektywnie
wielokrotnie wykorzystywana. Aby na przykład można było wielokrotnie korzystać z poje-
dynczej klasy w sposób praktyczny i łatwy, należałoby pisać programy w tym samym języku
i sięgać do zgodnej architektury. Nie zawsze jest to niewykonalne. Istnieją powszechnie używa-
ne biblioteki klas, które odniosły sukces, i ich biblioteki mogą być uważane za komponenty
do wielokrotnego używania. Często jednak określenie komponentu odnosi się do grupy po-
wiązanych klas. Im więcej wysiłku włoży się w dostosowanie komponentu do kontekstu, tym
więcej korzyści powinien przynieść dany komponent, jeśli ten wysiłek ma być opłacalny.
Celowo rozmyliśmy kwestię tego, czy komponenty są zbudowane z obiektów, czy z klas;
nie dokonaliśmy jeszcze wyraźnego rozróżnienia między typem a egzemplarzem. Powód jest
taki, że wziąwszy pod uwagę szeroką definicję komponentu, oba te przypadki mogą być
prawdziwe. Klasa albo zbiór klas może być komponentem na poziomie kodu źródłowego.
Kontekst, w którym jest używany komponent, korzysta z udogodnień danego języka w celu
tworzenia obiektów klasy. W innych przypadkach komponent może realizować dostęp do
określonego, gotowego obiektu jakiejś klasy, realizując również dostęp do funkcji związa-
nych z tworzeniem kolejnych obiektów tej klasy. Należy pamiętać, że sam komponent ma
dobrze zdefiniowany interfejs. Jeśli komponent składa się z obiektów i (lub) klas, to interfejs
komponentu zwykle nie pozwala na dostęp do wszystkiego, co znajduje się w interfejsach
obiektu i wewnątrz klas. Komponent sam stara się być hermetyzowaną abstrakcją i prawdo-
podobnie jest właściwe, że ukrywa część lub całość swojej wewnętrznej struktury — na
przykład czy i jak składa się z obiektów i klas.
44
Część I ¨ Tło koncepcyjne
Zagadnienie do przedyskutowania 13
Skorzystaj z sieci Web i innych dostępnych zasobów w celu odszukania popularnych kompo-
nentów architektury, takich jak JavaBeans, CORBA albo .NET. Co to jest architektura? Jaką
wewnętrzną strukturę może mieć komponent i jak jest zdefiniowany interfejs komponentu?
2.3. Dziedziczenie
Do tej pory omówiliśmy jedynie pojęciowe podstawy tego, co czasami jest nazywane pro-
jektem opartym na obiektach. W tym podrozdziale weźmiemy pod uwagę dziedziczenie,
które będąc pojęciowym elementem leżącym u podstaw obiektowo zorientowanego pro-
jektu, jest niczym lukier dla pączka. Jak wskazuje metafora, pojęcie to jest uważane za atrak-
cyjne i ważne, ale tak naprawdę jest mniej treściwe niż to, co do tej pory opisaliśmy w książce.
W niniejszym podrozdziale rozpatrzymy dziedziczenie jako techniczną właściwość języ-
ków zorientowanych obiektowo, użyteczną (w ograniczonym zakresie) podczas niskopozio-
mowego wielokrotnego wykorzystywania. W rozdziale 5. omówimy korzystanie z dziedzi-
czenia w modelowaniu.
Poniższy przykład został zaczerpnięty ze studium przypadku z rozdziału 15. W rozważa-
nym systemie istnieje klasa, której obiekty reprezentują wykładowców, oraz klasa, której
obiekty reprezentują opiekunów studiów. Opiekun studiów jest kimś w rodzaju wykładowcy,
a dokładniej jest wykładowcą, który oprócz pełnienia zwykłych obowiązków jest odpowiedzialny
także za nadzorowanie postępów poszczególnych studentów. W systemie
OpiekunStudiow
powinien rozumieć takie same komunikaty jak
Wykladowca
i dodatkowo powinien być w sta-
nie odpowiedzieć na komunikat
podopieczni
, zwracając zbiór obiektów typu
Student
.
Załóżmy, że klasa
Wykladowca
została już zaimplementowana i teraz należy zaprojektować
klasę
OpiekunStudiow
. Załóżmy też (wyłącznie dla skonkretyzowania przykładu), że używany
jest język podobny do C++, w którym interfejs klasy i jego implementacja są przechowywane
w oddzielnych plikach. Co jest potrzebne do zaimplementowania nowej klasy tak szybko jak
to tylko możliwe? Można skopiować i wkleić kod definiujący interfejs klasy
Wykladowca
do
nowego pliku z interfejsem i dodać u dołu opis dodatkowego komunikatu. Podobnie można
skopiować do pliku z implementacją nowej klasy kod, w którym zaimplementowano reakcję
obiektów klasy
Wykladowca
na komunikat, a następnie dokonać niezbędnych zmian. Jak już
jednak wspomnieliśmy, wielokrotne używanie kodu przez jego kopiowanie ma wady. Naj-
ważniejsza z nich jest taka, że w przypadku konieczności dokonania zmian w zduplikowa-
nym kodzie (na przykład wykryto błąd w programie) należy wprowadzić zmiany w dwóch
miejscach zamiast w jednym. Jest to uciążliwe i prawdopodobnie doprowadzi do błędów.
Zamiast tego obiektowo zorientowane języki programowania dopuszczają utworzenie
nowej klasy
OpiekunStudiow
na podstawie istniejącej klasy
Wykladowca
. Wystarczy zadekla-
rować, że klasa
OpiekunStudiow
jest podklasą klasy
Wykladowca
, a następnie dopisać jedynie
to, co ma związek z dodatkowymi atrybutami lub operacjami klasy
OpiekunStudiow
5
.
Co więcej, ponieważ w zorientowaniu obiektowym wiedza na temat kodu, jaki zostanie
wykonany przez obiekt po tym, gdy otrzyma on komunikat, jest hermetyzowana, istnieje
5
W niektórych językach, a zwłaszcza w języku Eiffel, można w podklasach usuwać atrybuty i operacje
nadklas, jednak takie postępowanie nie jest zazwyczaj uważane za dobry zwyczaj.
Rozdział 2. ¨ Koncepcje obiektów
45
możliwość, aby w klasie
OpiekunStudiow
zaimplementować w odpowiedzi na komunikat inne
zachowania niż te, które są zaimplementowane w klasie
Wykladowca
. Oznacza to, że w klasie
OpiekunStudiow
można przesłonić niektóre z metod klasy
Wykladowca
. Oprócz dziedziczenia
metod po klasie
Wykladowca
, klasa
OpiekunStudiow
może zawierać nową metodę implemen-
tującą taką samą operację, dla której klasa
Wykladowca
miała już metodę. Kiedy obiekt klasy
OpiekunStudiow
otrzyma komunikat z żądaniem wykonania operacji, wywołana metoda bę-
dzie wyspecjalizowaną wersją metody udostępnianej przez klasę
OpiekunStudiow
.
Podklasa jest rozszerzoną, wyspecjalizowaną wersją swojej nadklasy. Zawiera operacje oraz
atrybuty nadklasy i możliwie jeszcze inne, dodatkowe elementy.
P: W jaki sposób jest definiowana podklasa w Twoim języku? Czy klasa może być bezpośrednią
podklasą kilku klas (dziedziczenie wielokrotne), czy nie?
Terminologia
Mówi się, że:
· klasa
OpiekunStudiow
dziedziczy po klasie
Wykladowca
,
· klasa
OpiekunStudiow
jest podklasą (lub klasą pochodną) klasy
Wykladowca
,
· klasa
OpiekunStudiow
jest specjalizacją klasy
Wykladowca
,
· klasa
OpiekunStudiow
jest bardziej wyspecjalizowana niż klasa
Wykladowca
,
· klasa
Wykladowca
jest nadklasą (lub klasą bazową) klasy
OpiekunStudiow
,
· klasa
Wykladowca
jest generalizacją klasy
OpiekunStudiow
.
Wszystkie powyższe określenia mają niemal to samo znaczenie. Często pojęcia podklasy i nad-
klasy są używane przy opisie konkretnych relacji pomiędzy pojęciami reprezentowanymi
w klasach. Zazwyczaj zachodzi między nimi związek. Związki pojęciowe jednak niekoniecz-
nie muszą być widoczne w strukturze klasy. Przeciwnie, niektórzy programiści używają
dziedziczenia w strukturze klasy w przypadkach, w których nie ma pojęciowego związku
dziedziczenia pomiędzy klasami (nie zawsze można polecać takie rozwiązanie. Może być
ono wygodne w krótszym okresie czasu, jednak niemal zawsze spowoduje nieporozumienia
po upływie dłuższego czasu).
Czy można powiedzieć, że klasa
OpiekunStudiow
należy do klasy
Wykladowca
? Można, jeśli
myśli się o klasach jak o zbiorach obiektów. Gdy mówi się o obiektach klasy
Wykladowca
,
z pewnością chce się uwzględnić także obiekty klasy
OpiekunStudiow
— po prostu ciągłe pi-
sanie o „klasie
Wykladowca
i dowolnych podklasach” jest zbyt niewygodne. Co więcej, zasa-
da, że obiekt podklasy powinien być zastępowalny obiektem nadklasy, jest fundamentalna.
Za każdym razem więc, gdy będzie mowa o obiektach należących do klasy, będzie to ozna-
czać także obiekty należące do podklas. Z drugiej strony bardzo wygodna byłaby możliwość
mówienia o „klasie tego obiektu”, jak gdyby należał on wyłącznie do jednej klasy. Przez
określenie „klasa” obiektu będziemy więc rozumieć najbardziej wyspecjalizowaną klasę, do
której należy dany obiekt. Oznacza to, że twierdzenie, iż zachowanie obiektu jest określone
przez jego klasę, pozostaje prawdziwe.
46
Część I ¨ Tło koncepcyjne
2.4. Polimorfizm i wiązanie dynamiczne
Te dwa określenia są często w środowisku zorientowanym obiektowo używane zamiennie.
W rzeczywistości oznaczają one całkiem różne koncepcje. Ogólnie w językach programowa-
nia jedno pojęcie może istnieć bez drugiego, chociaż w językach zorientowanych obiektowo
są one spokrewnione i mają związek z dziedziczeniem.
Przede wszystkim należy zauważyć, że sam fakt, iż jedną klasę można definiować na
podstawie innej klasy, korzystając ponownie z jej kodu, nie musi mieć żadnego wpływu na
to, w jaki sposób kod-klient będzie traktował obiekty tych klas. Możliwy byłby język, w którym
dziedziczenie byłoby tylko sposobem na wielokrotne wykorzystywanie kodu. Można by zdefi-
niować klasę
OpiekunStudiow
na podstawie klasy
Wykladowca
, lecz cały kod klienta (a także
kompilator) musiałby dokładnie znać klasę każdego obiektu, z którym by wchodził w inte-
rakcję. Technicznie rzecz biorąc oznacza to, że można by utworzyć język z takim rodzajem
dziedziczenia, w którym występowałyby wyłącznie typy monomorficzne. Języki zoriento-
wane obiektowo radzą sobie jednak lepiej.
Polimorfizm
Określenie to, wywodzące się z języka greckiego, oznacza mający wiele kształtów. W języ-
kach obiektowo zorientowanych odnosi się do sytuacji, w której obiekt może należeć do jednego
z kilku typów. W obiektowo zorientowanej sytuacji zmienna polimorficzna mogłaby odnosić
się (w różnych przypadkach) do obiektów kilku różnych klas. Funkcja polimorficzna może
pobierać argumenty różnych typów. Powiedzieliśmy już, że obiekt podklasy powinien być
stosowany w każdej sytuacji, w której mógłby zostać użyty obiekt nadklasy. Powinno to oznaczać
w szczególności, że obiekt podklasy powinien być akceptowany jako wartość zmiennej, jeśli
jest akceptowany jako obiekt klasy bazowej. Innymi słowy, w obiektowo zorientowanym ję-
zyku każda zmienna jest polimorficzna — przynajmniej w tym ograniczonym znaczeniu.
Podobnie, jeśli jakaś funkcja (na przykład metoda) może pobierać argument klasy B, powin-
na być w stanie pobrać argument dowolnej klasy C, która jest podklasą klasy B; tak więc
również każda funkcja jest polimorficzna w tym ograniczonym sensie. Funkcja, która na
przykład oczekuje jako argumentu obiektu
Wykladowca
, nie powinna zgłosić błędu, jeśli jako
argument otrzyma obiekt
OpiekunStudiow
.
Zagadnienie do przedyskutowania 14
Czy w Twoim języku (w C albo w typologicznie funkcjonalnym języku programowania) jest re-
alizowany polimorfizm? Czy na polimorfizm są nałożone większe, czy mniejsze ograniczenia niż
w polimorfizmie obiektowo zorientowanym, który został tu opisany? Czy jest on przydatny?
W jaki sposób?
Polimorfizm pozwala na znaczne ograniczenie duplikacji kodu. Swoje prawdziwe możli-
wości pokazuje jednak w powiązaniu z związaniem dynamicznym.
Rozdział 2. ¨ Koncepcje obiektów
47
Wiązanie dynamiczne
Słowo „wiązanie” w określeniu wiązanie dynamiczne (albo też późne wiązanie — oba te
określenia są używane zamiennie) odnosi się do procesu identyfikowania kodu, który powi-
nien zostać wykonany na skutek otrzymanego komunikatu. Zrozumienie tego będzie łatwiej-
sze po rozważeniu kilku przykładów.
Załóżmy, że interfejs obiektu
Wykladowca
zawiera funkcję
mozeWykonac (obowiazek: Obowia-
zek)
, która jako argument pobiera obiekt reprezentujący obowiązek administracyjny, a w wyniku
zwraca wartość logiczną, której wartością jest
prawda
, jeśli wykładowca może wypełnić dany
obowiązek. Przyjmijmy też założenie (trochę nierealne), że
OpiekunStudiow
jest w stanie zrobić
wszystko, a tym samym że zawsze powinien zwracać wartość
prawda
. Znaczy to, że podklasa
OpiekunStudiow
przeimplementowała (przesłoniła) funkcję
mozeWykonac
, prawdopodobnie za-
stępując skomplikowany kod sprawdzający informacje na temat zainteresowań lub doświad-
czenia wykładowcy prostym kodem, który zawsze zwraca wartość
prawda
.
Załóżmy teraz, że
wykladowcy
są zmienną odnoszącą się do zbioru obiektów klasy
Wykladowca
.
W tym zbiorze mogą być oczywiście zawarte także obiekty typu
OpiekunStudiow
, ponieważ
jeśli do zmiennej
wykladowcy
można dodawać obiekty klasy
Wykladowca
, to można do niej do-
dawać również obiekty klasy
OpiekunStudiow
. Przypuśćmy, że klient-zadanie próbujące znaleźć
kogoś do zorganizowania seminarium wykonuje kod w rodzaju poniższego pseudokodu:
dla kazdy o w wykladowca
o.mozeWykonac(organizacjaSeminarium)
Warto zwrócić uwagę na zapis z kropką w wierszu z wysyłanym komunikatem, który jest
wspólny dla wielu języków programowania. Wyrażenie
o.mozeWykonac(organizacjaSeminarium)
oznacza wysłanie komunikatu
mozeWykonac(organizacjaSeminarium)
do obiektu
o
. Może ono
być również używane w celu reprezentowania wartości, którą obiekt
o
zwróci klientowi po
przetworzeniu komunikatu.
Przyglądając się kodowi można przypuszczać, że obiekt
o
należy do klasy
Wykladowca
. Warto
przypomnieć ideę, że w obiekcie hermetyzowane jest jego zachowanie, a klasy wprowadzo-
no jedynie z potrzeby zwięzłości zapisu. Jeśli obiekt
o
znajduje się wyłącznie w klasie
Wykla-
dowca
, to powinien zostać wykonany kod obiektu
Wykladowca
dla komunikatu
mozeWykonac
,
czyli jego zachowanie w reakcji na komunikat. Co powinno się stać, gdy pętla
dla
osiągnie
obiekt
o
, który w rzeczywistości należy do klasy
OpiekunStudiow
? Oczywiście w takim przy-
padku powinien wykonać się kod klasy
OpiekunStudiow
(gdyby obiekt
o
rzeczywiście zabierał
ze sobą swój kod, działoby się tak automatycznie; fakt, że tak nie jest, wynika po prostu
z optymalizacji). Znaczy to, że ten sam fragment składni powinien spowodować wykonanie
dwóch różnych fragmentów kodu w różnych sytuacjach. Wysyłany komunikat jest dyna-
micznie wiązany z odpowiednim kodem
6
.
Na koniec rozważmy inny bardzo prosty przykład wiązania dynamicznego, który ilu-
struje także przypadek wysyłania przez obiekt komunikatu do samego siebie.
6
W najprostszy sposób można to zrobić, sprawdzając typ obiektu podczas wykonywania się programu.
W nowoczesnych językach jednak większość pracy bierze na siebie kompilator, co pozwala na większą
wydajność.
48
Część I ¨ Tło koncepcyjne
Przypuśćmy, że interfejs klasy
Wykladowca
zawiera komunikat
mozliwosci
; w odpowiedzi
na niego obiekt klasy
Wykladowca
powinien zwrócić zbiór czynności, które wykładowca może
wykonać. Być może kod klasy
Wykladowca
jest zaimplementowany w podobny sposób jak
w poniższym pseudokodzie (w którym instrukcja
sam.mozeWykonac(obowiazek)
oznacza wy-
nik zwracany po wysłaniu do tego obiektu komunikatu z selektorem
mozeWykonac
i argu-
mentem
obowiazek
):
coMogeWykonac := pustyZbior
dla kazdy obowiazek w listaWszystkichObowiazkow
jesli (sam.mozeWykonac(obowiazek)) wtedy dodaj obowiazek do coMogeWykonac
inaczej zrob nic
zwroc coMogeWykonac
Oczywiście tę operację można przesłonić w nowej klasie
OpiekunStudiow
, tylko po co tak
robić (wydajność raczej nie będzie ważna)? Załóżmy, że kod pozostanie na swoim miejscu,
dzięki czemu zostanie bez zmian odziedziczony przez obiekty klasy
OpiekunStudiow
. Przy-
pomnijmy jednak, że w klasie
OpiekunStudiow
została przesłonięta operacja
mozeWykonac
. Co
się stanie, gdy obiekt klasy
OpiekunStudiow
otrzyma komunikat
mozliwosci
? Zostanie wyko-
nany powyższy kod — w czym nie ma nic szczególnego — co spowoduje wysłanie komuni-
katu
mozeWykonac
do siebie samego. Jest to obiekt klasy
OpiekunStudiow
, tak więc po otrzy-
maniu komunikatu
mozeWykonac
zostanie wykonana wyspecjalizowana wersja kodu zdefiniowana
w klasie
OpiekunStudiow
.
W przypadku używania czystego języka obiektowo zorientowanego takie zachowanie szybko
powinno stać się drugą naturą. Ostrożność należy zachować w przypadku języka C++: takie
zachowanie wykazują tylko metody wirtualne. Niektóre osoby, łącznie ze mną
7
, są głęboko
przekonane, że zaczynając programowanie obiektowo zorientowane w języku C++ powin-
no się początkowo tworzyć wyłącznie metody wirtualne
8
, a dopiero potem nauczyć się two-
rzenia metod niewirtualnych.
PODSUMOWANIE
Ten rozdział był krótkim wprowadzeniem do zorientowania obiektowego. Opisaliśmy, czym
jest obiekt i jak między obiektami są przesyłane komunikaty. Pokazaliśmy, że obiekty w systemie
można z pożytkiem podzielić na klasy, zamiast rozpatrywać je indywidualnie, oraz rozpo-
częliśmy rozpatrywanie powiązań między obiektami i komponentami. Omówiliśmy, jak za
pomocą dziedziczenia klasa może zostać zdefiniowana z wykorzystaniem definicji innej kla-
sy. Na koniec przedstawiliśmy inną cechę dziedziczenia w językach zorientowanych obiek-
towo — możliwość udostępniania polimorfizmu i wiązania dynamicznego.
W następnym rozdziale dokonamy wprowadzenia do głównego tematu tej książki — Ujed-
noliconego Języka Modelowania, który może być używany do określania specyfikacji i pro-
jektowania systemów korzystających z przewagi, jaką zapewnia zorientowanie obiektowe.
7
Stevens.
8
Łącznie (zwłaszcza) z destruktorami, ale nigdy z konstruktorami. Dlaczego?