Polimorfizm — to samo polecenie, różne wykonania
Organizator uruchamia wszystkie atrakcje, nie sprawdzając przy każdej, jakiego jest rodzaju. Konkretna atrakcja zna własne zachowanie.
Czego się dziś nauczysz
Po lekcji wyjaśnisz poznany mechanizm, prześledzisz działanie programu i zastosujesz go w samodzielnym zadaniu. Pracuj po kolei według spisu OOP. Potrzebujesz podstaw zmiennych, warunków, metod i wcześniejszych tematów tego modułu.
Zaplanuj 1–2 godziny lekcyjne, a zadania rozszerzające wykonaj w osobnym laboratorium. Używamy .NET 8 lub nowszego. Nie łącz kilku kompletnych przykładów w jednym Program.cs.
Wyjaśnienie krok po kroku
Najpierw dziedziczenie, potem wybór zachowania
Dziedziczenie pozwala użyć obiektu klasy pochodnej tam, gdzie oczekujemy typu bazowego. Polimorfizm korzysta z tego przy wywołaniu metod: typ zmiennej mówi, jakie operacje są dostępne, a rzeczywisty typ obiektu wskazuje, która implementacja wirtualnej metody zostanie wykonana.
Tak wygląda to w kodzie — Fragment wnętrza Main; Roboty dziedziczy po Atrakcja. To fragment ilustracyjny, nie cały Program.cs.
Atrakcja a = new Roboty();
Console.WriteLine(a.Uruchom()); // Roboty ruszaja
Zmienna jest typu Atrakcja, a obiekt typu Roboty. Uruchom jest wirtualna i Roboty ma jej override. WriteLine wypisuje zwrócony napis i kończy wiersz.
Virtual i override
Virtual w klasie bazowej udostępnia domyślne zachowanie, które można nadpisać. Override w pochodnej deklaruje jego zamianę. Sama zgodność nazw nie wystarcza. Nadpisana metoda pozostaje wirtualna dla dalszego dziedziczenia, chyba że dodamy sealed override. Nie dopisujemy jednocześnie virtual i override.
Tak wygląda to w kodzie — Definicje typów — poza Main. To fragment ilustracyjny, nie cały Program.cs.
class Atrakcja
{
public virtual string Uruchom() { return "Start"; }
}
class Quiz : Atrakcja
{
public override string Uruchom() { return "Pytanie"; }
}
Nazwy i parametry pasują do wspólnej operacji. Nadpisanie nie tworzy przeciążenia z nową listą parametrów.
Przeciążanie to inne zjawisko
Przeciążone metody mają różne listy parametrów; dla zwykłego statycznie typowanego wywołania kompilator wybiera sygnaturę na podstawie argumentów. Przy override sygnatura jest wspólna, a odpowiednie zachowanie wirtualne wybierane jest podczas wykonania na podstawie obiektu.
New nie zastępuje override
Modyfikator new przed metodą ukrywa odziedziczony element. To nie to samo co operator new tworzący obiekt. Przy ukrywaniu wybór ukrytego elementu zależy od typu referencji użytej w kodzie, więc wynik może zaskoczyć. Na początku stosuj override, gdy zamierzasz zmienić zachowanie wirtualne.
Co zyskujemy?
Pętla organizatora nie zawiera if dla każdego rodzaju atrakcji. Dodając nową klasę z poprawnym override, nie musimy zmieniać tej pętli. Nadal musimy utworzyć i przekazać nowy obiekt — polimorfizm nie usuwa całej konfiguracji programu. Zysk dotyczy kodu korzystającego ze wspólnego kontraktu.
Tak wygląda to w kodzie — Fragment wnętrza Main; atrakcje to tablica Atrakcja[]. To fragment ilustracyjny, nie cały Program.cs.
foreach (Atrakcja a in atrakcje)
{
Console.WriteLine(a.Uruchom());
}
Pętla nie pyta o rodzaj atrakcji. Nowa klasa z override może dołączyć do tablicy bez dopisywania kolejnej gałęzi w tej pętli.
Nie każda gałąź if wymaga hierarchii
Dla jednego prostego wyboru switch może być bardziej czytelny. Polimorfizm pomaga, gdy kilka rodzajów obiektów ma wspólne operacje, a odmienne zachowanie powtarza się w wielu miejscach. Nie twórz kilkunastu pustych klas wyłącznie po to, by zastąpić jedną tabelę danych.
Przewidź → uruchom → zmień
Kompletny, osobny plik Program.cs. Przed uruchomieniem przewidź wynik. Każdy program
uruchamiaj w oddzielnym projekcie konsolowym.
using System;
class Atrakcja
{
public virtual string Uruchom() { return "Otwarcie stanowiska"; }
}
class Roboty : Atrakcja
{
public override string Uruchom() { return "Roboty ruszaja"; }
}
class Quiz : Atrakcja
{
public override string Uruchom() { return "Pytanie na ekranie"; }
}
class Program
{
static void Main()
{
Atrakcja[] atrakcje = { new Roboty(), new Quiz(), new Atrakcja() };
foreach (Atrakcja atrakcja in atrakcje)
Console.WriteLine(atrakcja.Uruchom());
}
}
Roboty ruszaja
Pytanie na ekranie
Otwarcie stanowiska
Co dokładnie robi kod?
W każdej iteracji zmienna ma typ Atrakcja, ale wskazuje kolejno Roboty, Quiz i Atrakcja. Wywołanie Uruchom trafia do override dwóch pierwszych obiektów oraz wersji bazowej trzeciego. Tablica przechowuje referencje, nie zamienia obiektów w nowo utworzone obiekty bazowe. foreach pobiera kolejne elementy. Uruchom jest naszą metodą zwracającą napis; Console.WriteLine(string) wypisuje ten napis i zwraca void.
Zmień jedną daną i zapisz przewidywany wynik przed uruchomieniem. Następnie porównaj obserwację z przewidywaniem i wskaż instrukcję odpowiedzialną za różnicę.
Zadania
Nie ma tu gotowych rozwiązań. Oddaj kod, własne przypadki testowe oraz krótkie wyjaśnienie decyzji. Pierwsze zadanie jest podstawowe; ostatnie pozwalają pogłębić temat.
Dodaj atrakcję Karaoke i jej zachowanie. Zaznacz dokładnie, które części programu musiałeś zmienić.
Zaprojektuj wspólną metodę przyjmującą jedną Atrakcje i wywołującą ją dwukrotnie. Sprawdź dwie klasy pochodne.
Eksperyment: zastąp jedno override przez new. Wywołaj metodę przez zmienną typu bazowego i pochodnego. Porównaj i wyjaśnij wyniki.
Dla opłat za wypożyczenie dwóch rodzajów sprzętu napisz najpierw wersję z if, a potem z metodą wirtualną. Nie mieszaj liczenia z wypisywaniem. Oceń liczbę miejsc zmienianych przy dodaniu trzeciego rodzaju.
Sprawdź, czy rozumiesz
- Co określa dostępne metody, a co wykonanie override?
- Czy nadpisanie metody jest przeciążeniem?
- Czy każda instrukcja switch jest błędem projektowym?
Dokumentacja: Microsoft Learn — Polimorfizm.