Podstawy Programowania – semestr drugi Wyk ad czternasty ł 1

Transkrypt

Podstawy Programowania – semestr drugi Wyk ad czternasty ł 1
Podstawy Programowania – semestr drugi
Wykład czternasty
1.
Polimorfizm
Ostatni wykład zakończyliśmy stwierdzeniem, że możemy obiektowi dowolnej klasy przypisać obiekt klasy dziedziczącej po tej klasie. Przypisanie takie obejmuje
jednak jedynie wartości pól wspólnych dla klas obu obiektów. Może być ono dokonywane zarówno poprzez jawne użycie instrukcji przypisania, jak i za pośrednictwem
przekazania przez wartość. Technika obiektowa pozwala na bardziej wyrafinowane korzystanie z obiektów, ale wymaga użycia wskaźników i zapoznania się z dwoma
nowymi pojęciami: metodami wirtualnymi i konstruktorami. Własność instrukcji przypisania, która dotyczy obiektów klas należących do jednej hierarchii dziedziczenia
jest zachowana również w przypadku wskaźników, co oznacza, że wskaźnik klasy bazowej może przechowywać adres obiektu dowolnej klasy pochodnej. Ta własność
okazuje się być bardzo przydatna w połączeniu z późnym wiązaniem adresów metod. Dotychczas adres metody, która ma być wywołana był ustalany na etapie
kompilacji. Język Object Pascal pozwala na odroczenie określenia tego adresu do czasu wykonania programu, co nazywane jest późnym lub dynamicznym wiązaniem
i ma ścisły związek z tzw. polimorficznym zachowaniem metod. Aby adres metody był ustalany na etapie wykonania programu za jej deklaracją należy umieścić słowo
kluczowe virtual. Tak zadeklarowana metoda jest nazywana metodą wirtualną lub metodą polimorficzną. Polimorficzne zachowanie metody polega na tym, że kiedy na
obiekt klasy pochodnej wskazuje wskaźnik klasy bazowej i przy pomocy tego wskaźnika wywoływana jest metoda, to zostanie wywołana metoda właściwa dla klasy
obiektu, a nie dla klasy wskaźnika. By ten mechanizm zadziałał konieczne jest, aby ta metoda była zadeklarowana w klasie bazowej i to jako metoda wirtualna. Jeśli
metoda została zadeklarowana w klasie bazowej jako wirtualna, to w klasach pochodnych nie można jej deklaracji zmienić z powrotem na statyczną (pominąć słowo
kluczowe virtual), natomiast odwrotna sytuacja jest możliwa. Nie jest bezpośrednio możliwe wywołanie, przy pomocy wskaźnika klasy bazowej metod, które w tej klasie
nie zostały zadeklarowane, a są dodane do klas pochodnych. Ten problem można jednak rozwiązać i rozwiązanie to zostanie przedstawione w dalszej części wykładu.
Adresy metod wirtualnych są ustalane na podstawie tablicy metod wirtualnych (ang. VMT = Virtual Method Table). Jest to specjalna tablica umieszczana w segmencie
danych po uruchomieniu programu. Warunkiem stworzenia tej tablicy jest zadeklarowanie w klasie specjalnej metody, która nazywana jest konstruktorem. Deklaracja
tej metody określona jest następującym schematem:
constructor nazwa;
Konstruktor w języku Object Pascal może się nazywać dowolnie, zazwyczaj jednak nazywa się go „init” lub „inicjuj”. Może on również być metodą pustą, ale zazwyczaj
są w nim umieszczane instrukcje związane z inicjalizacją pól obiektu. Konstruktor jest jedyną metodą, która nie może być wirtualna, gdyż jest on odpowiedzialny za
1
powiązanie VMT z obiektem . Wirtualna tablica metod jest tworzona dla każdej klasy, która zawiera konstruktor. W przypadku, kiedy taka klasa nie posiada metod
wirtualnych, to rozmiar tej tablicy wynosi 8 bajtów i przechowuje ona między innymi rozmiar obiektu klasy oraz zanegowany rozmiar obiektu klasy. Każda metoda
wirtualna powoduje zwiększenie rozmiaru tej tablicy o 4 bajty, w których jest zapisywany adres tej metody. Kiedy tworzony jest obiekt konstruktor dokonuje
inicjalizacji dwubajtowego pola tego obiektu, które przechowuje adres (dokładniej: tylko offset) VMT. Pole to jest tworzone niejawnie przez kompilator. Możemy ustalić
jakiej faktycznie klasy jest wskazywany przez wskaźnik obiekt posługując się funkcją TypeOf, która zwraca adres wirtualnej tablicy metod właściwej klasy obiektu.
Dzięki temu możemy obiekt rzutować na wskaźnik odpowiedniej klasy i wywołać metody, które są obecne w tej klasie, ale nie są obecne w klasie bazowej. Do tego
zagadnienia wrócimy na przyszłych wykładach. Oto przykład ilustrujący działanie mechanizmu polimorfizmu:
W programie zdefiniowano cztery klasy: „CialoNiebieskie”
(CiałoNiebieskie), „Planeta”, „Gwiazda” i „Wszechswiat”
(Wszechświat). Przyjrzyjmy się najpierw klasom w module.
Tworzą one drzewo dziedziczenia, którego korzeniem jest
klasa „CialoNiebieskie” - dwie pozostałe dziedziczą po niej.
Każda z tych klas posiada konstruktor. W klasie
„CialoNiebieskie” jest on metodą pustą, w pozostałych
wywołuje inne metody odpowiedzialne za nadawanie wartości
poszczególnym polom odpowiednich klas. Każda klasa
pochodna oprócz pól i metod dziedziczonych po klasie bazowej
posiada również własne pola oraz metody je obsługujące.
Jedną z metod wspólnych dla wszystkich klas jest metoda
„drukuj”. To co ją odróżnia od pozostałych jest to, że jest
metodą wirtualną. Ta metoda jest nadpisywana w kolejnych
klasach, tak aby obsługiwała pola charakterystyczne dla tych
klas. Jeśli jej nie nadpisalibyśmy, to będzie się zachowywała
tak jak metoda z klasy bazowej, gdyż też podlega
dziedziczeniu. W pliku zawierającym blok główny programu
stworzono jeszcze jedną klasę. Ta klasa zawiera tylko jedną
metodę, która przez parametr pobiera wskaźnik do obiektu
klasy „CialoNiebieskie”. W bloku głównym programu tworzony
jest obiekt tej klasy i wywoływana jest metoda „drukuj” tego
obiektu. W wierszu 18 jest jej przekazany przez parametr
adres obiektu klasy „Gwiazda”, a w wierszu 19 adres obiektu
klasy „Planeta”. Okazuje się, że w metodzie „drukuj” obiektu
klasy „Wszechswiat” zostały wywołane metody „drukuj”
właściwe dla klas obiektów, których adresy zostały przekazane
tej metodzie. Jeśli zmienilibyśmy tę metodę w ten sposób, że
będzie korzystała nie ze wskaźnika, ale z parametru klasy
„CialoNiebieskie” przekazującego przez wartość, to program
będzie zachowywał się podobnie do programu z poprzedniego
wykładu, który nie korzystał z mechanizmu polimorfizmu.
1 program Uniwersum;
2 uses cn;
3 type Wszechswiat=object
4
procedure drukuj(ob:PCialoNiebieskie);
5
end;
6 var
7
ws:Wszechswiat;
8
gw:Gwiazda;
9
pl:Planeta;
10
11 procedure Wszechswiat.drukuj(ob:PCialoNiebieskie);
12 begin
13 ob^.drukuj;
14 end;
15
16 begin
17 pl.inicjuj;
18 gw.inicjuj;
19 ws.drukuj(@gw);
20 ws.drukuj(@pl);
21 end.
1
Robi to w sposób niejawny, czyli od strony programisty nie wymaga to dodatkowych zabiegów, poza stworzeniem takiej metody, która, jak to już wcześniej napisano,
może być metodą pustą.
1
Podstawy Programowania – semestr drugi
1 unit cn;
2 interface
3 uses crt;
4 type
5
6 PCialoNiebieskie = ^CialoNiebieskie;
7
8 CialoNiebieskie = object
9
private
10
nazwa:string;
11
masa:integer;
12
x,y,z:real;
13
function podajMase:integer;
14
function podajNazwe:string;
15
function podajX:real;
16
function podajY:real;
17
function podajZ:real;
18
public
19
constructor inicjuj;
20
procedure ustawNazwe(const n:string);
21
procedure ustawWspolrzedne(x1,y1,z1:real);
22
procedure ustawMase(m:integer);
23
procedure drukuj; virtual;
24 end;
25
26 Planeta = object(CialoNiebieskie)
27
private
28
atmosfera:boolean;
29
public
30
constructor inicjuj;
31
function podajAtmosfere:boolean;
32
procedure ustawAtmosfere(a:boolean);
33
procedure drukuj; virtual;
34 end;
35
36 Gwiazda = object(CialoNiebieskie)
37
private
38
temperaturaPowierzchni:integer;
39
public
40
constructor inicjuj;
41
function podajTemperaturePowierzchni:integer;
42
procedure ustawTemperaturePowierzchni(t:integer);
43
procedure drukuj; virtual;
44 end;
45
2
Podstawy Programowania – semestr drugi
46 implementation
47
48
function CialoNiebieskie.podajMase:integer;
49
begin
50
51
podajMase:=masa;
end;
52
53
function CialoNiebieskie.podajNazwe:string;
54
begin
55
56
podajNazwe:=nazwa;
end;
57
58
function CialoNiebieskie.podajZ:real;
59
begin
60
61
podajZ:=z;
end;
62
63
function CialoNiebieskie.podajY:real;
64
begin
65
66
podajY:=y;
end;
67
68
function CialoNiebieskie.podajX:real;
69
begin
70
71
podajX:=x;
end;
72
73
procedure CialoNiebieskie.ustawNazwe(const n:string);
74
begin
75
76
nazwa:=n;
end;
77
78
procedure CialoNiebieskie.ustawWspolrzedne(x1,y1,z1:real);
79
begin
80
x:=x1;
81
y:=y1;
82
z:=z1;
83
end;
84
85
procedure CialoNiebieskie.ustawMase(m:integer);
86
begin
87
88
Masa:=m;
end;
89
90
constructor CialoNiebieskie.inicjuj;
3
Podstawy Programowania – semestr drugi
91
begin
92
end;
93
94
procedure CialoNiebieskie.drukuj;
95
begin
96
clrscr;
97
writeln('Nazwa: ',podajNazwe);
98
writeln('Masa: ',podajMase);
99
writeln('Współrzędne: ',podajX, ',', podajY, ',', podajZ);
100
end;
101
102
constructor Planeta.inicjuj;
103
begin
104
ustawMase(2000);
105
ustawWspolrzedne(1,0,0);
106
ustawNazwe('Ziemia');
107
ustawAtmosfere(True);
108
end;
109
110
procedure Planeta.ustawAtmosfere(a:boolean);
111
begin
112
113
atmosfera:=a;
end;
114
115
function Planeta.podajAtmosfere:boolean;
116
begin
117
118
podajAtmosfere:=atmosfera;
end;
119
120
procedure Planeta.drukuj;
121
begin
122
inherited drukuj;
123
if podajAtmosfere=True then writeln('Atmosfera: tak') else
124
writeln('Atmosfera: nie');
125
readln;
126
end;
127
128
constructor Gwiazda.inicjuj;
129
begin
130
ustawMase(20000);
131
ustawWspolrzedne(0,0,0);
132
ustawNazwe('Słońce');
133
ustawTemperaturePowierzchni(5000);
134
end;
135
4
Podstawy Programowania – semestr drugi
136
procedure Gwiazda.ustawTemperaturePowierzchni(t:integer);
137
begin
138
temperaturaPowierzchni:=t;
139
end;
140
141
function Gwiazda.podajTemperaturePowierzchni:integer;
142
begin
143
podajTemperaturePowierzchni:=temperaturaPowierzchni;
144
end;
145
146
procedure Gwiazda.drukuj;
147
begin
148
clrscr;
149
inherited drukuj;
150
writeln('Temperatura powierzchni: ', podajTemperaturePowierzchni);
151
readln;
152
end;
153 end.
Moduł „cn” jest również wykorzystywany w krótkim programie, który wypisuje część zawartości tablicy metod wirtualnych na ekran:
W programie tym stworzony został typ rekordowy
opisujący elementy tablicy metod wirtualnych.
Typ ten jest używany przez procedurę
„display_vmt”, która dokonuje rzutowania danych
wskazywanych przez wskaźnik bez określonego
typu na ten typ rekordowy i wyświetlenia na
ekran zawartości pól tego rekordu (chodzi tu
o nadanie określonej formy danym, które zostały
pobrane wprost z pamięci operacyjnej). Adresy
VMT poszczególnych klas są uzyskiwane dzięki
wcześniej wspomnianej funkcji TypeOf. Program
napisano
na
podstawie
skryptu
Zofii
Kruczkiewicz
pt.:
„Metody
programowania
obiektowego”.
1 program tablica_vmt;
2 uses crt,cn;
3 type Rek = record
4
SizePoz,SizeNeg,Pom1,Pom2:Word;
5
Adr_Metody_1:Pointer;
6
end;
7 var
8
pl:Planeta;
9
gw:Gwiazda;
10
11
wsk:pointer;
12
13 procedure display_vmt(adr:pointer);
14 begin
15 with rek(adr^) do
16 begin
17
writeln('Rozmiar obiektu: ',SizePoz);
18
writeln('Rozmiar zanegowany: ',SizeNeg);
19
writeln('Pom1: ',Pom1, ' Pom2: ',Pom2);
20
writeln('Adres drukuj:',Seg(Adr_Metody_1^),':',Ofs(Adr_Metody_1^));
21 end;
22 end;
23
24 begin
5
Podstawy Programowania – semestr drugi
25 clrscr;
26 pl.inicjuj;
27 gw.inicjuj;
28 wsk:=TypeOf(pl);
29 display_vmt(wsk);
30 wsk:=TypeOf(gw);
31 display_vmt(wsk);
32 readln;
33 end.
2.
Podsumowanie
Polimorfizm jest bardzo pożytecznym mechanizmem w programowaniu obiektowym. Pełnię jego możliwości będziemy mogli wykorzystać po zapoznaniu się z obiektami
tworzonymi dynamicznie. Czasem, aby ułatwić wywoływanie metod z klas pochodnych, do klas bazowych dodaje się wszystkie metody jakie mogą wystąpić w ich
klasach pochodnych i umieszcza się w nich instrukcję abstract, która powoduje błąd czasu wykonania, jeśli metoda ją zawierająca zostanie wywołana. Takie metody
nazywa się metodami abstrakcyjnymi, a klasy które je zawierają klasami abstrakcyjnymi. Dosyć często używa się takich klas w innym celu – aby uczynić model
obiektowy bardziej odpowiadającym rzeczywistemu. Klasami abstrakcyjnymi mogą być klasy reprezentujące takie pojęcia jak: figura geometryczna, czy ssak. Problem
metod i klas abstrakcyjnych zostanie szerzej omówiony na następnym wykładzie. W wyżej zaprezentowanym programie należy zwrócić uwagę na użycie operatora @.
Potencjalnie ten operator jest niebezpieczny, ponieważ kompilator nie sprawdza typu wskaźnika, jaki on zwraca, co oznacza, że za jego pomocą do metody drukuj klasy
Wszechświat możemy przekazać wskaźnik na dowolną zmienną, nawet taką, która nie jest obiektem (np.: wskaźnik na zmienną typu integer). Aby pozbyć się tego
operatora możemy zastąpić przekazanie wskaźnika do metody przekazaniem przez zmienną, tzn. przekształcić nagłówek metody drukuj na procedure drukuj(var
ob:CialoNiebieskie); i zmienić w ciele tej metody ob^.drukuj na ob.drukuj. Przekazanie przez zmienną jest przekazaniem wskaźnika, ale w sposób bezpieczny (z
kontrolą typów). Przekazanie przez zmienną nazywane jest również przekazaniem przez referencję. Rolę referencji, czyli bezpiecznego wskaźnika pełni parametr
poprzedzony słowem kluczowym var.
6

Podobne dokumenty