Polimorfizm w C

1. Wprowadzenie

  • Polimorfizm (z greckiego „wiele form") to zdolność obiektów różnych klas do reagowania na te same komunikaty (metody) w różny sposób.
  • Jest to jeden z trzech głównych filarów programowania obiektowego (obok enkapsulacji i dziedziczenia).
  • W C# występują dwa typy polimorfizmu:
    • Polimorfizm w czasie kompilacji (compile-time) – przeciążanie metod i operatorów,
    • Polimorfizm w czasie wykonania (runtime) – przesłanianie metod (override).

2. Polimorfizm w czasie kompilacji (statyczny)

2.1. Przeciążanie metod (Method Overloading)

  • Przeciążanie metod to definiowanie kilku metod o tej samej nazwie, ale z różnymi parametrami.
  • Kompilator wybiera właściwą metodę na podstawie liczby i typów argumentów.

2.2. Przeciążanie operatorów (Operator Overloading)

  • C# pozwala na przeciążanie operatorów dla własnych typów.

3. Polimorfizm w czasie wykonania (dynamiczny)

  • Polimorfizm dynamiczny polega na przesłanianiu metod z klasy bazowej w klasach pochodnych.
  • Wymaga użycia słów kluczowych virtual i override.
  • Dzięki temu można traktować obiekty klas pochodnych jako obiekty klasy bazowej i wywoływać odpowiednie metody.

3.1. Podstawowy przykład

Wynik:

Hau hau!
Miau!
Zwierzę wydaje dźwięk
  • Mimo że wszystkie obiekty są typu Zwierze, każdy wywołuje swoją własną implementację metody DajGlos().

3.2. Zalety polimorfizmu dynamicznego

  • Elastyczność – ten sam kod działa dla różnych typów obiektów.
  • Rozszerzalność – łatwo dodać nowe klasy bez zmiany istniejącego kodu.
  • Czytelność – uproszczenie kodu przez unikanie wielokrotnych instrukcji warunkowych.

4. Różnica między virtual/override a new

4.1. virtual i override – polimorfizm


4.2. new – ukrywanie metody (brak polimorfizmu)

  • new nie tworzy polimorfizmu – wywoływana jest metoda z klasy bazowej.

5. Polimorfizm z klasami abstrakcyjnymi

  • Klasy abstrakcyjne idealnie nadają się do tworzenia polimorficznych hierarchii.
  • Wymuszają implementację metod abstrakcyjnych w klasach pochodnych.

Wynik:

Pole: 78.54
Pole: 24.00

6. Polimorfizm z interfejsami

  • Interfejsy również umożliwiają polimorfizm.
  • Klasy implementujące ten sam interfejs mogą być traktowane jako obiekty tego interfejsu.

7. Rzutowanie i sprawdzanie typów

7.1. Operator is

  • Sprawdza, czy obiekt jest danego typu.

7.2. Operator as

  • Próbuje rzutować obiekt na dany typ. Jeśli się nie uda, zwraca null.

7.3. Pattern matching (C# 7+)


8. Przykładowy program

Wynik:

Jan Kowalski - Pensja: 5 000,00 zł
Anna Nowak - Pensja: 8 000,00 zł

9. Najlepsze praktyki

  • Używaj virtual/override zamiast new dla polimorfizmu.
  • Stosuj klasy abstrakcyjne lub interfejsy do tworzenia wspólnych kontraktów.
  • Unikaj nadużywania polimorfizmu – zbyt złożone hierarchie mogą być trudne do zrozumienia.
  • Używaj pattern matching dla czytelniejszego kodu przy sprawdzaniu typów.
  • Zawsze rozważ kompozycję jako alternatywę dla dziedziczenia.

10. Podsumowanie

  • Polimorfizm statyczny – przeciążanie metod i operatorów (compile-time).
  • Polimorfizm dynamiczny – przesłanianie metod virtual/override (runtime).
  • Polimorfizm umożliwia pisanie elastycznego, rozszerzalnego kodu.
  • Klasy abstrakcyjne i interfejsy wspierają polimorfizm.