Polimorfizm (z greki: „wiele form”) to cecha programowania obiektowego, dzięki której to samo wywołanie metody zachowuje się inaczej w zależności od tego, na jakim obiekcie je wywołasz. Mówisz obiektowi co ma zrobić, a on sam decyduje jak — bez sterty if-ów sprawdzających, z jakim typem masz do czynienia. To jeden z czterech filarów OOP, obok enkapsulacji, dziedziczenia i abstrakcji.
Jak to działa
Najczęściej spotkasz polimorfizm w wersji „runtime”, opartej o dziedziczenie i nadpisywanie metod. Klasa bazowa definiuje metodę (np. area()), a klasy potomne dostarczają własne implementacje. Kiedy trzymasz obiekty w kolekcji typu bazowego i wołasz tę samą metodę, program w czasie działania wybiera właściwą wersję — to tzw. dynamic dispatch. Drugi rodzaj to polimorfizm „compile-time” (statyczny): przeciążanie metod (overloading) i generyki/szablony, gdzie kompilator dobiera implementację po sygnaturze albo typie.
Po co? Żeby pisać kod, który nie musi znać konkretnych typów. Dodajesz nową klasę i reszta systemu działa bez zmian, bo operuje na wspólnym interfejsie. Mniej rozgałęzień, łatwiejsze testowanie, czytelniejsza architektura.
Przykład z praktyki
Masz interfejs PaymentProvider z metodą charge(amount) i trzy implementacje: StripeProvider, PayPalProvider, BlikProvider. W kodzie checkoutu piszesz po prostu:
provider.charge(99.00)
i nie obchodzi cię, który to provider — każdy gada ze swoim API po swojemu. Dorzucasz jutro ApplePayProvider i nie ruszasz checkoutu. Klasyk z podręczników to lista figur: for (Shape s : shapes) total += s.area(); — koło liczy pole inaczej niż prostokąt, ale wołasz jedną metodę.
Częste błędy i mity
Polimorfizm to nie to samo co dziedziczenie. Dziedziczenie to mechanizm, polimorfizm to efekt — możesz mieć polimorfizm bez klasycznego dziedziczenia (np. przez interfejsy albo „duck typing” w Pythonie: jeśli obiekt ma metodę quack(), traktujesz go jak kaczkę).
Druga pułapka: w C++ metoda musi być oznaczona jako virtual, inaczej dostaniesz static binding i wywoła się wersja z klasy bazowej — cichy, wkurzający bug. W Javie metody są wirtualne domyślnie. I uważaj na łamanie zasady podstawienia Liskov: jeśli podklasa zmienia kontrakt metody tak, że niespodziewanie wybucha, polimorfizm działa ci na szkodę, nie pomoc.
Pojęcia powiązane
Dziedziczenie, enkapsulacja, abstrakcja, interfejs, klasa abstrakcyjna, nadpisywanie metod (overriding), przeciążanie (overloading), zasada podstawienia Liskov, dynamic dispatch.