Metoda wirtualna
Z Wikipedii
Metoda wirtualna (funkcja wirtualna) to metoda która jest polimorficzna.
Spis treści |
[edytuj] Przykład w C++
#include <iostream.h> const float pi = 3.14159; //---------------------------------------- class Figura { public: virtual float pole() const { return -1.0; } }; //---------------------------------------- class Kwadrat : public Figura { public: Kwadrat( const float bok ) : a( bok ) {} float pole() const { return a * a; } private: float a; // bok kwadratu }; //---------------------------------------- class Kolo : public Figura { public: Kolo( const float promien ) : r( promien ) {} float pole() const { return pi * r * r; } private: float r; // promien kola }; //---------------------------------------- void wyswietlPole( Figura& figura ) { std::cout << figura.pole() << endl; return; } //---------------------------------------- int main() { // deklaracje obiektow: Figura jakasFigura; Kwadrat jakisKwadrat( 5 ); Kolo jakiesKolo( 3 ); Figura* wskJakasFigura = 0; // deklaracja wskaźnika // obiekty ------------------------------- std::cout << jakasFigura.pole() << endl; // wynik: -1 std::cout << jakisKwadrat.pole() << endl; // wynik: 25 std::cout << jakiesKolo.pole() << endl; // wynik: 28.274... // wskazniki ----------------------------- wskJakasFigura = &jakasFigura; std::cout << wskJakasFigura->pole() << endl; // wynik: -1 wskJakasFigura = &jakisKwadrat; std::cout << wskJakasFigura->pole() << endl; // wynik: 25 wskJakasFigura = &jakiesKolo; std::cout << wskJakasFigura->pole() << endl; // wynik: 28.274... // referencje ----------------------------- wyswietlPole( jakasFigura ); // wynik: -1 wyswietlPole( jakisKwadrat ); // wynik: 25 wyswietlPole( jakiesKolo ); // wynik: 28.274... return 0; }
W przykładzie znajdują się deklaracje 3 klas: Figura
, Kwadrat
i Kolo
. W klasie Figura
została zadeklarowana metoda wirtualna (słowo kluczowe virtual) virtual float pole()
. Każda z klas pochodnych od klasy Figura
mają zaimplementowane swoje metody float pole()
. Następnie (w funkcji main) znajdują się deklaracje obiektów każdej z klas i wskaźnika mogącego pokazywać na obiekty klasy bazowej Figura
.
Wywołanie metod składowych dla każdego z obiektów powoduje wykonanie metody odpowiedniej dla klasy danego obiektu. Następnie wskaźnikowi wskJakasFigura
zostaje przypisany adres obiektu jakasFigura
i zostaje wywołana metoda float pole()
. Wynikiem jest "-1" zgodnie z treścią metody float pole()
w klasie Figura
. Następnie przypisujemy wskaźnikowi adres obiektu klasy Kwadrat - możemy tak zrobić ponieważ klasa Kwadrat
jest klasą pochodną od klasy Figura
- jest to tzw. rzutowanie w górę. Wywołanie teraz metody float pole()
dla wskaznika nie spowoduje wykonania metody zgodnej z typem wskaźnika - który jest typu Figura*
lecz zgodnie z aktualnie wskazywanym obiektem, a więc wykonana zostanie metoda float pole()
z klasy Kwadrat
(gdyż ostatnie przypisanie wskaźnikowi wartości był adres obiektu klasy Kwadrat
). Analogiczna sytuacja dzieje się gdy przypiszemy wskaźnikowi adres obiektu klasy Kolo
. Następnie zostaje wykonana funkcja void wyswietlPole(Figura&)
która przyjmuje jako parametr obiekt klasy Figura
przez referencję. Tutaj również zostały wykonane odpowiednie metody dla obiektów klas pochodnych a nie metoda zgodna z obiektem jaki jest zadeklarowany jako parametr funkcji czyli float Figura::pole()
. Takie działanie jest spowodowane przez przyjmowanie obiektu klasy Figura
przez referencję. Gdyby obiekty były przyjmowane przez wartość (parametr bez &) zostałaby wykonana 3 krotnie metoda float Figura::pole()
i 3 krotnie wyświetlona wartość -1.
Wyżej opisane działanie zostało spowodowane przez określenie metody w klasie bazowej jako wirtualnej. Gdyby zostało usunięte słowo kluczowe virtual w deklaracji metody w klasie bazowej, zostałyby wykonane metody zgodne z typem wskaźnika lub referencji, a więc za każdym razem zostałaby wykonana metoda float pole()
z klasy Figura
.
[edytuj] "Czysta wirtualność"
Określa to, że metoda z klasy bazowej deklarująca metodę wirtualną nigdy nie powinna się wykonać. W efekcie klasa taka staje się klasą abstrakcyjną. Oznacza to tyle, iż nie jest możliwe stworzenie obiektu tej klasy. Klasa taka służy jedynie temu, by zdefiniować pewnego rodzaju interfejs i jest przeznaczona jedynie po to, by od niej dziedziczyć.
W przykładzie wyżej, o ile mogą istnieć figury będące kwadratami, kołami itp to "raczej" nie powinien istnieć żaden obiekt klasy Figura
. Figura
jest tutaj pewnym abstrakcyjnym określeniem, natomiast dziedziczenie po tej klasie i rozszerzeniu jej o inne elementy (dane i metody) powoduje, że mamy do czynienia już z konkretną figurą geometryczną. Metodę czysto wirtualną w języku C++ deklaruje się tak:
class Figura { public: virtual float pole() = 0; };
Taka deklaracja metody wirtualnej zmusza jednocześnie do określenia metody float pole()
na jednym z poziomów dziedziczenia. Nie jest możliwe pominięcie takiej implementacji. Jednocześnie taka deklaracja uniemożliwia stworzenie jakiegokolwike obiektu klasy Figura
np.: Figura mojObiekt;
.
[edytuj] Właściwości
- Metoda wirtualna nie może być zadeklarowana jako statyczna (static).
- Jeśli metoda wirtualna została zaimplementowana w jakimkolwiek "wyższym" poziomie dziedziczenia (w szczególności w klasie bazowej całej struktury dziedziczenia), nie jest konieczne podawanie implementacji w klasie pochodnej.
- Jeśli w klasie jest zadeklarowana jakakolwiek metoda wirtualna, zaleca się aby destruktor w tej klasie również określić jako wirtualny (zobacz: destruktor).
C++
- Słowo kluczowe virtual można pominąć w deklaracjach w klasach pochodnych.
- Słowo kluczowe virtual określa się tylko w deklaracji metody, nie określa się tego w definicji (chyba że deklaracja jest jednocześnie definicją, co ma miejsce gdy implementacja metody zostanie określona w ciele klasy).
Java
- W Javie domyślnie wszystkie metody są wirtualne. Aby jednak określić jakąś metodę jako niewirtualną należy zadeklarować metodę jako final. Słowo kluczowe final użyte w tym kontekście zabrania implementowania klasom pochodnym swoich wersji danej metody.
[edytuj] Zastosowania
- Rozszerzalność kodu. Polimorfizm umożliwia rozszerzanie nawet skompilowanych fragmentów kodu.
- Pozwala na rozszerzalność kodu również wtedy, gdy dostępna jest jedynie skompilowana wersja klasy bazowej.
- Zwalnia programistę od niepotrzebnego wysiłku.
- Programista nie musi przejmować się tym, którą z klas pochodnych aktualnie obsługuje, a jedynie tym, jakie operacje chce na tej klasie wykonać.
- Programista myśli co ma wykonać a nie jak to coś wykonać - nie musi się przejmować szczegółami implementacyjnymi.