Metoda wirtualna (funkcja wirtualna) to metoda, która może zostać nadpisana w klasach dziedziczących. Metody wirtualne umożliwiają polimorfizm.
Właściwości C++ Są deklarowane przy użyciu słowa kluczowego virtual. 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. 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). Jeśli w klasie jest zadeklarowana jakakolwiek metoda wirtualna, zaleca się aby destruktor w tej klasie również określić jako wirtualny.
Java W Javie domyślnie wszystkie metody są wirtualne.
C#
W C# domyślnie metody nie są wirtualne. Metody wirtualne muszą zostać zadeklarowane przy użyciu słowa kluczowego virtual.
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.
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ą, a więc nie jest możliwe stworzenie obiektu tej klasy. Klasa taka służy zdefiniowaniu pewnego interfejsu i jest przeznaczona jedynie do tego, by od niej dziedziczyć.
W przykładzie poniżej, o ile mogą istnieć figury będące kwadratami, kołami itp. to 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 jakiegokolwiek obiektu klasy Figura np.: Figura mojObiekt;.
Przykład w C++ #include <iostream>
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() << std::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() << std::endl; // wynik: -1 std::cout << jakisKwadrat.pole() << std::endl; // wynik: 25 std::cout << jakiesKolo.pole() << std::endl; // wynik: 28.274...
// wskazniki ----------------------------- wskJakasFigura = &jakasFigura; std::cout << wskJakasFigura->pole() << std::endl; // wynik: -1 wskJakasFigura = &jakisKwadrat; std::cout << wskJakasFigura->pole() << std::endl; // wynik: 25 wskJakasFigura = &jakiesKolo; std::cout << wskJakasFigura->pole() << std::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 ma 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 przypisywało mu 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.
Zobacz też klasa abstrakcyjna interfejs
Bibliografia
Przypisy
Kategoria:Programowanie obiektowe