Lekcja 14. Polimorfizm i metody wirtualne
Trudny / egzaminacyjnyPo co się tego uczymy?
Polimorfizm to trzecia z czterech głównych zasad OOP (po hermetyzacji i dziedziczeniu) i jeden z tematów, w których uczniowie najczęściej czują się niepewnie na egzaminie — a jednocześnie jedna z najbardziej praktycznych technik, eliminująca długie łańcuchy warunków sprawdzających typ obiektu.
Teoria
Polimorfizm (z greckiego "wiele form") pozwala, żeby TO SAMO wywołanie metody zachowywało się RÓŻNIE, w zależności od tego, na jakim konkretnie obiekcie zostało wywołane — nawet jeśli w kodzie operujesz na nich przez wspólny typ bazowy.
Aby to działało, klasa bazowa musi oznaczyć metodę jako virtual ("może zostać nadpisana"), a klasa pochodna nadpisuje ją słowem override:
class Pracownik { public virtual double ObliczPremie() { return Pensja * 0.05; } }
class Kierownik : Pracownik { public override double ObliczPremie() { return Pensja * 0.20; } }
Dzięki temu możesz mieć tablicę czy listę typu bazowego (Pracownik[]), przechowującą obiekty RÓŻNYCH klas pochodnych, i wywołać na każdym tę samą metodę — C# sam "wie", którą wersję uruchomić, sprawdzając RZECZYWISTY typ obiektu, nie typ zmiennej.
Bez polimorfizmu musiałbyś sprawdzać typ każdego obiektu ręcznie (if (p is Kierownik) { ... } else if (p is Programista) { ... }) — z polimorfizmem ten cały łańcuch warunków znika, bo każdy obiekt "sam wie", jak się zachować.
To jedna z najważniejszych korzyści programowania obiektowego: możesz dodać NOWĄ klasę pochodną (np. Stazysta) w przyszłości, bez zmiany ani jednej linii kodu, który już korzysta z polimorficznego wywołania ObliczPremie().
Schemat
Pracownik[] zespol = { programista, kierownik, stażysta };
foreach (Pracownik p in zespol)
p.ObliczPremie(); <- WYWOŁANIE JEST TAKIE SAMO dla każdego,
ale KAŻDY typ wykonuje SWOJĄ WŁASNĄ wersję:
Programista.ObliczPremie() -> premia = pensja * 0.10
Kierownik.ObliczPremie() -> premia = pensja * 0.20
Stazysta.ObliczPremie() -> premia = 0
Przykład z życia
Naciśnięcie "drukuj" działa tak samo niezależnie od tego, jaka konkretnie drukarka jest podłączona (laserowa, atramentowa, etykiet) — każda "wie", jak zinterpretować to samo polecenie na swój własny sposób. To właśnie polimorfizm.
Kod (C#)
using System;
class Pracownik
{
public string Imie;
public double Pensja;
public Pracownik(string imie, double pensja)
{
Imie = imie;
Pensja = pensja;
}
public virtual double ObliczPremie() // "virtual" = może być nadpisana
{
return Pensja * 0.05;
}
}
class Kierownik : Pracownik
{
public Kierownik(string imie, double pensja) : base(imie, pensja) { }
public override double ObliczPremie() // "override" = własna wersja
{
return Pensja * 0.20;
}
}
class Stazysta : Pracownik
{
public Stazysta(string imie, double pensja) : base(imie, pensja) { }
public override double ObliczPremie()
{
return ; // stażyści na razie bez premii
}
}
class Program
{
static void Main()
{
Pracownik[] zespol = {
new Pracownik("Ala", 5000),
new Kierownik("Bartek", 9000),
new Stazysta("Celina", 3000)
};
foreach (Pracownik p in zespol)
{
// to samo wywołanie - RÓŻNY wynik dla każdego typu!
Console.WriteLine(p.Imie + ": premia = " + p.ObliczPremie() + " zł");
}
}
}
Komentarz i wyjaśnienie kodu
Tablica Pracownik[] zespol przechowuje TRZY RÓŻNE typy obiektów (Pracownik, Kierownik, Stazysta) — to możliwe właśnie dzięki dziedziczeniu (Lekcja 13), bo każdy z nich JEST RODZAJEM Pracownik. Pętla foreach wywołuje p.ObliczPremie() identycznie dla każdego elementu, ale C# w czasie DZIAŁANIA programu sprawdza rzeczywisty typ obiektu i uruchamia WŁAŚCIWĄ, nadpisaną wersję metody — to się nazywa wiązanie dynamiczne.
Ćwiczenie samodzielne
Dodaj do przykładu z lekcji nową klasę pochodną Dyrektor : Pracownik z premią 30%, bez zmiany kodu w Main() poza dodaniem nowego obiektu do tablicy.
Plac zabaw — wypróbuj online
Poniższy kod wykonuje się od razu w Twojej przeglądarce — nic nie trzeba instalować. Zmień kod i kliknij „Uruchom”.
Zadania do pracy własnej
Zdefiniuj klasę bazową
Zwierzez metodą wirtualnąWydajDzwiek()zwracającą tekst "...", oraz dwie klasy pochodnePies(nadpisującą na "Hau!") iKot(na "Miau!") — wywołaj metodę na tablicy obiektów obu typów.Rozbuduj hierarchię
Pojazdz wcześniejszej lekcji o metodę wirtualnąOpisPojazdu()zwracającą różny tekst dla każdej klasy pochodnej (np.Motocykl,Samochod), i wypisz opisy wszystkich pojazdów z listy przez pętlę.Zbuduj system naliczania opłat za wypożyczenie w wypożyczalni: klasa bazowa
Produktz metodą wirtualnąObliczOplateZaDzien(), oraz klasy pochodneFilm(2 zł/dzień),Konsola(15 zł/dzień) iRower(5 zł/dzień). Utwórz listę różnych wypożyczonych produktów i oblicz łączną opłatę za wypożyczenie wszystkich na 3 dni.
Typowe błędy
Najczęstszy błąd to zapomnienie słowa virtual w metodzie klasy bazowej — bez niego override w klasie pochodnej zgłosi błąd kompilacji, bo metoda nie jest oznaczona jako "nadpisywalna". Drugi błąd: użycie new zamiast override przy nadpisywaniu metody — to UKRYWA metodę bazową zamiast ją nadpisać, co psuje polimorfizm (przy wywołaniu przez typ bazowy uruchomi się stara wersja). Trzeci: ręczne sprawdzanie typu obiektu (if (p is Kierownik)) tam, gdzie polimorfizm załatwiłby sprawę prościej i bezpieczniej.
Nawiązanie do egzaminu zawodowego
To realizacja efektu INF.04.4.5 z podstawy programowej ("stosuje zasady programowania obiektowego") w zakresie polimorfizmu, oraz efektu INF.04.4.7 ("definiuje klasy pochodne") w części dotyczącej metod wirtualnych.