Języki i techniki programowania Ćwiczenia 3 Dziedziczenie
Klasa abstrakcyjna Autor: Marcin Orchel Klasa abstrakcyjna to taka, że nie możemy tworzyć obiektów tej klasy, możemy jednak dziedziczyć po tej klasie. Jeśli klasa ma co najmniej jedna metodę abstrakcyjną, a więc nie zdefiniowaną w tej klasie, to klasa jest abstrakcyjna. W Javie dodaje się wtedy słowo kluczowe abstract przed nazwą klasy. W C++ nie jest konieczne dodawanie tego słowa. W C++ aby danej metody nie definiować należy na końcu deklaracji metody dodać = 0;. Metodami niedefiniowanymi mogą być tylko i wyłącznie metody wirtualne. Co to są za metody będzie przedstawione w późniejszej częsci. Metoda wirtualna, niedefiniowana w klasie nazywa się metodą czysto wirtualną (pure virtual). Dziedziczenie W Javie po jednej klasie może dziedziczyć tylko jedna klasa jednokrotne dziedziczenie. Klasa może implementować wiele interfejsów, interfejsy mogą być dziedziczone między sobą wielokrotnie. Przykład: interace interface1 interace interface2 interace interface3 extends interface1, interface2 class class1 implements interface1, interface2 Dziedziczenie z trzema poziomami Klasa Car -> Fiat -> Punto W przypadku uruchomienia Punto punto; punto.startengine(); Jeśli metoda startengine jest zdefiniowana w klasie Punto to ona zostanie uruchomiona. Jeśli nie jest zdefiniowana w klasie Punto, a jest zdefiniowana w klasie Car i Fiat, to zostanie uruchomiona metoda startengine() z klasy Fiat. W przypadku uruchomienia:
void f(car & car) car.startengine(); Punto punto; f(punto); zostanie uruchomiona metoda startengine() z klasy Car. Jeśli metoda startengine będzie wirtualna w klasie Car to zostanie wywołana metoda startengine() z klasy Punto. Jeśli nie jest zdefiniowana w klasie Punto to z klasy Fiat. Metody wirtualne W Javie wszystkie metody są wirtualne. Metoda wirtualna to taka metoda, że mając daną zmienną typu bazowego, pod którą znajduje się obiekt typu pochodnego, wywołanie metody wirtualnej na tej zmiennej spowoduje wywołanie metody z klasy pochodnej, a nie klasy bazowej. W C++ aby metoda była wirtualna musi mieć słowo kluczowe virtual przed nazwą. Przykład: Vehicle public: virtual startengine() ; Tram : public Vehicle public: startengine() ; movevehicles(vehicle & vehicle) vehicle.startengine(); void main() Tram tram = new Tram() movevehicles(tram);
Wywołanie movevehicles(tram) spowoduje uruchomienie metody startengine() z klasy Tram, a nie z klasy Vehicle. Wielokrotne dziedziczenie W Javie jest możliwe tylko jednokrotne dziedziczenie pomiędzy klasami. W C++ jest możliwe wielokrotne dziedziczenie. Przykład Mamy różne rodzaje pojazdów i różne rodzaje silników. Chcemy mieć możliwość definiowania zachowania dla poszczególnych rodzajów pojazdów z poszczególnymi rodzajami silników. Struktura klas: Car -> Fiat Car -> CarWithGasoline Fiat, CarWithGasoline -> FiatWithGasoline Klasy tworzą tzw. strukturę diamentową. Metoda startengine() jest zdefiniowana dla klasy Car, Fiat oraz CarWithGasoline. foo(car & car) FiatWithGasoline fiatwithgasoline; foo(fiatwithgasoline); Powyższe wywołanie funkcji foo jest nieprawidłowe, ponieważ klasa Car jest przekazywana dwoma drogami poprzez klasę Fiat oraz CarWithGasoline. FiatWithGasoline fiatwithgasoline; (1) fiatwithgasoline.startengine(); Przy tym uruchomieniu pojawi się błąd kompilacji, ponieważ nie można stwierdzić, która metoda ma się uruchomić czy z klasy Fiat, czy z klasy CarWithGasoline. Gdy nie ma zdefiniowanej metody startengine() zarówno dla klasy Fiat jak i CarWithGasoline, to również będzie błąd kompilatora, ponieważ metoda startengine() z klasy Car jest przekazywana dwoma drogami i nie wiadomo, którą wybrać. Podobne problemy do powyższych dotyczą również parametrów klas. Można zapobiec przekazywaniu metod dwoma drogami dziedziczenia używając wirtualnego dziedziczenia specyfikowanego następująco: Fiat : public virtual Car.
Jeśli metoda startengine() nie jest zdefiniowana ani dla klasy Fiat, ani dla klasy CarWithEngine, oraz klasy Fiat i CarWithEngine wirtualnie dziedziczą po klasie Car, uruchomienie (1) jest prawidłowe, ponieważ wszystkie metody klasy Car przekazywane są tylko jedną drogą.
Zadania Zadanie podstawowe - stworzyć klasę Figure, po której dziedziczy klasa Triangle - stworzyć klasę Circle dziedziczącą po klasie Figure. W klasie Figure zadeklarować metodę computeperimeter, zdefiniować tą metodę dla klasy Triangle oraz dla klasy Circle. Utworzyć klasę Figures, która w konstruktorze dostaje tablicę figur. W klasie Figures zdefiniować metodę znajdującą figurę o największym obwodzie. Napisać test poprawności programu. Zadanie dodatkowe 1 - stworzyć dodatkową figurę Rhombus dziedziczącą po Figure, - stworzyć dodatkową figurę Square dziedziczącą po Rhombus Znaleźć figurę o największym obwodzie dla przykładowych figur zapisanych w obiekcie klasy Figures. Napisać test poprawności programu. Zadanie dodatkowe 2 Dla wszystkich figur zdefiniować metody liczące pole, zdefiniować w klasie Figures metodę znajdującą największe pole. Zdefiniować klasy: - FigureWithCircleInside Klasa te posiada konstruktor z parametrami koła znajdującego się wewnątrz figury. Klasa ta posiada metodę liczącą pole koła. - RhombusWithCircleInside Klasa ta posiada konstruktor z parametrami rombu oraz koła wpisanego do rombu. Klasa ta posiada metodę liczącą pole figury. Pole figury z kołem wewnątrz jest równe polu figury bez pola koła. Klasa ta posiada metodę sprawdzającą czy dane koło da się wpisać w dany romb. Zdefiniować dodatkowo następujące dziedziczenia: Figure -> FigureWithCircleInside Rhombus, FigureWithCircleInside -> RhombusWithCircleInside W RhombusWithCircleInside napisać metodę sprawdzającą czy można wpisać dane koło w dany romb.
Stworzyć obiekt typu Figures z przykładowymi figurami zdefiniowanymi powyżej. Znaleźć figurę o największym polu. Napisać test poprawności programu.