Programowanie II prowadzący: Adam Dudek Lista nr 8 Dziedziczenie : Dziedziczenie to nic innego jak definiowanie nowych klas w oparciu o już istniejące. Jest to najważniejsza cecha świadcząca o sile programowania obiektowo zorientowanego. Dzięki tej technice możliwe jest najbardziej naturalne odzwierciedlenie związków jakie zachodzą pomiędzy obiektami modelowanej w programie rzeczywistości. Przykład najprostszy: class Tpunkt void odbij_wzgledem_poczatku() //... void obroc(int kat) //... Zawartość metod składowych została już dawno wymyślona podobnie jak i sama definicja punktu. Okazuje się jednak, już w przypadku ekranu komputerowego taka definicja już nie wystarcza. Klasę Tpunkt należało by bowiem rozszerzyć o wartości RGB, tak aby mógł on zostać poprawnie wyświetlony. class TPunkt_monitor Oczywiście w przypadku programowania strukturalnego musielibyśmy ponownie sprecyzować metody odbij_wzgledem_poczatku() i obroc(int kat), czyli musielibyśmy wykonać tę samą pracę ponownie. Dzięki jednak technice dziedziczenia możemy zaproponować nową klasę, wykorzystując możliwości starej: A następnie w programie głównym użyć konstrukcji: Tpunkt_monitor punkt; punkt.x=10; Punkt.obroc(90); Programowanie II studia niestacjonarne - Lista 8 1
Fakty bardzo istotne: - dziedziczenie nie polega na przekopiowaniu zawartości jednej klasy do drugiej. Składniki dziedziczone nadal mają zakres klasy podstawowej, na który to zostaje nałożony zakres klasy pochodnej - w przypadku ponownego definiowania składników w klasie pochodnej o takich samych nazwach jak w klasie podstawowej tracimy bezpośredni dostęp do tych ostatnich - aby odwołać się do pierwotnego znaczenia przesłoniętego składnika należy użyć kwalifikatora zakresu, np.: Tpunkt_monitor obiekt; obiekt.wypisz(); //metoda składowa klasy Tpunkt_monitor Obiekt.Tpunkt::wypisz(); //metoda składowa klasy Tpunkt - należy pamiętać, iż dziedziczenie dotyczy klas (czyli typów danych), a nie obiektów będących ich reprezentantami. Dziedziczenie kilkupokoleniowe : class A //--- ciało klasy A class B: public A //--- ciało klasy B class C: public B //--- ciało klasy C Wywoływanie konstruktorów : class Tpunkt Tpunkt() cout<<"konstruktor punktu"<<"\n"; Tpunkt_monitor() cout<<"konstruktor punktu ekranowego"<<"\n"; class TPunkt_3D:public Tpunkt_monitor TPunkt_3D() cout<<"konstruktor punktu 3D"<<"\n"; Programowanie II studia niestacjonarne - Lista 8 2
Dziedziczenie, a dostęp do składników Mechanizm dziedziczenia pozwala określać w jaki sposób mają być dziedziczone składniki klasy podstawowej. Prywatne składniki klasy podstawowej są dziedziczone, ale nie ma do nich dostępu. Nie prywatne składniki klasy podstawowej są dostępne w zakresie klasy pochodnej bezpośrednio. Dziedziczenie, a składniki chronione Składniki chronione (PROTECTED) traktowaliśmy do tej pory jak prywatne. W praktyce klauzula PROTECTED oznacza, że składnik jest zastrzeżony tylko dla klas pochodnych. W praktyce oznacza to, że składnik typu PROTECTED: dla całego świata powinien być traktowany jak prywatny czyli niedostępny dla wszystkich klas pochodnych od danej klasy powinien być dostępny, tak jakby był składnikiem publicznym. Można zatem powiedzieć, iż to klasa podstawowa poprzez odpowiednią specyfikację dostępu decyduje, które składniki zamierza udostępnić otoczeniu, a które tylko klasom pochodnym. Sposoby dziedziczenia Również klasa pochodna ma możliwość określania sposobu w jaki chce dziedziczyć składniki klasy podstawowej. Wybór taki dokonywany jest w momencie definicji klasy pochodnej: Słowo public może zostać zastąpione protected lub private. Dzięki temu mamy możliwość określenia sposobu dziedziczenia składników nie prywatnych (!!!). Zasada definiowania sposobu dziedziczenia jest prosta: PUBLIC powoduje, że wszystkie składniki publiczne dziedziczone są w sposób publiczny, chronione jako chronione, a prywatne jako prywatne PROTECTED powoduje, że wszystkie składniki publiczne i chronione dziedziczone są jako chronione PRIVATE powoduje, że wszystkie składniki dziedziczone są jako prywatne. Funkcje wirtualne Funkcje wirtualne sprawiają, że program jest obiektowo zorientowany inaczej mówiąc orientuje się względem obiektów. Przykład najprostszy: class instrument char nazwa[30]; cout<<"intrument wydaje dzwiek... \n"; class trabka:public instrument Programowanie II studia niestacjonarne - Lista 8 3
cout<<"teraz gra trabka... \n"; class fortepian:public instrument cout<<"teraz gra fortepian...\n"; int main(int argc, char* argv[]) instrument jakis_instrument; trabka jakas_trabka; fortepian jakis_fortepian; jakis_instrument.graj(); jakas_trabka.graj(); jakis_fortepian.graj(); Return 0; } A teraz dla odmiany inna wersja funkcji głównej: instrument *wsk_na_instrument; instrument jakis_instrument; trabka jakas_trabka; fortepian jakis_fortepian; wsk_na_instrument = &jakis_instrument; wsk_na_instrument = &jakas_trabka; wsk_na_instrument = &jakis_fortepian; W praktyce wirtualny oznacza tyle co możliwy, czy też mogący zaistnieć, gdyż funkcja oznaczona jako wirtualna może, ale nie musi być zrealizowana w klasach pochodnych jeszcze raz. Virtual mówi, że od tej pory po wszystkie dalsze pokolenia kompilator ma użyć swojej inteligencji, gdy chodzi o wywołania tejże funkcji przez wskaźnik. Polimorfizm W zależności od wywołania funkcji wirtualnej, wywoływany jest w praktyce inny kod. Zatem np. fragment kodu: referencja.graj(); wywoływany jest praktycznie w postaci: referencja.instrument::graj(); referencja.trąbka::graj(); lub lub Programowanie II studia niestacjonarne - Lista 8 4
referencja.fortepian::graj(); Mówimy tutaj o wielości formy programu, czyli polimorfizmie. Fragment kodu zmienia się tak, że raz odpowiada wywołaniu w wersji pierwszej raz w innej. Zadanie 1 (20 pkt) Proszę zoptymalizować strukturę klas swojej wersji gry Monopolly. W każdym miejscu, w którym dokonana zostanie modyfikacja w stosunku do poprzedniej wersji, proszę w formie komentarza opisać możliwie szczegółowo na czym zmiany polegały i na jakiej podstawie podjęto taką, a nie inną decyzję. Oczywiście modyfikacja ma dotyczyć wykorzystania mechanizmów dziedziczenia. Zadanie 1 (30 pkt) Zaproponuj rozwiązanie, które umożliwiło by przechowywanie obiektów różnych klas (w grze monopoly.) w jednej liście dwukierunkowej. Oczywiście poza listą należałoby zaimplementować możliwość wypełnienia istotnymi danymi wszystkich pól w grze, możliwość ustalenia właściciela danego pola, postawienia na polu domku czy też hotelu, oraz pobrania opłaty (na różnych typach pól). Ważna jest implementacja funkcjonalności "wyświetlania" planszy gry. "Wyświetlanie" realizowane ma być w formie zapisu do pliku HTML (aplikacja generuje plik HTML). W pliku należy w przejrzystej formie przedstawić wygląd stanu planszy w grze. Dla ułatwienia można wykorzystać dużą tabelę (okienko w przeglądarce może się przewijać). Przy użyciu okienka konsolowego proszę zrealizować sterowanie grą, przy użyciu przeglądarki - wyświetlanie. Rozwiązanie powinno umożliwiać przeprowadzenie rozgrywki. Programowanie II studia niestacjonarne - Lista 8 5