Czym jest polimorfizm?
W programowaniu polimorfizm to zdolność jednego fragmentu kodu, takiego jak wywołanie funkcji albo nazwa metody, do pracy z wartościami różnych typów, z których każdy dostarcza własne zachowanie. Wywołanie shape.area() wykonuje inne obliczenie dla koła, a inne dla kwadratu.
Aktualizacja: 24 września 2026
W Pythonie len("hello") zwraca 5, len([4, 8, 15]) zwraca 3, a len({"a": 1}) zwraca 1. To jedna nazwa funkcji użyta na trzech typach, a każdy typ liczy na swój sposób: napis liczy znaki, lista liczy elementy, słownik liczy klucze. Słowo pochodzi z greki: poly, wiele, i morph, forma. Jedno wywołanie, wiele form zachowania.
Jak działa polimorfizm
Kod wywołuje metodę po nazwie, na przykład shape.area(). Gdy ta linia się wykonuje, język patrzy na faktyczny obiekt i szuka area w klasie tego obiektu. To wyszukiwanie nazywa się dynamicznym wiązaniem (dynamic dispatch) i właśnie ono pozwala jednej linii wykonywać różną pracę dla różnych obiektów:
Circle 3.14
Square 9
Circle 12.57
Python znajduje metodę, przeszukując najpierw klasę obiektu, a potem jej klasy nadrzędne w ustalonej kolejności, nazywanej kolejnością rozwiązywania metod (method resolution order). Gdyby Square nie miał własnego area(), wyszukiwanie doszłoby do Shape.area() i zgłosiło NotImplementedError, co jest sygnałem, że każda podklasa musi dostarczyć własną wersję.
Pętla nigdy nie sprawdza, jaką figurę trzyma. Żeby dodać trójkąt, piszesz klasę Triangle z własnym area(), a pętla działa bez zmian. Bez polimorfizmu pętla potrzebowałaby gałęzi if isinstance(...) dla każdej figury, edytowanej za każdym razem, gdy pojawi się nowa.
Rodzaje polimorfizmu
| Rodzaj | Inna nazwa | Jak wygląda | Kiedy wybierana jest wersja |
|---|---|---|---|
| Podtypowy | Polimorfizm dynamiczny, nadpisywanie metod | Podklasa zastępuje metodę rodzica | W trakcie działania programu |
| Ad hoc | Przeciążanie | Jedna nazwa, kilka wersji dla różnych typów parametrów; także przeciążanie operatorów | W czasie kompilacji w Javie i C++ |
| Parametryczny | Typy generyczne, szablony | Jeden fragment kodu, który działa dla dowolnego typu: List<T> w Javie, vector<T> w C++ | W czasie kompilacji |
Gdy ktoś w programowaniu obiektowym mówi po prostu "polimorfizm", prawie zawsze ma na myśli pierwszy wiersz: polimorfizm podtypowy przez nadpisywanie.
Python ma wszystkie trzy na swój sposób. Nadpisywanie działa jak w przykładzie z figurami. Operatory takie jak + to polimorfizm ad hoc. Funkcje takie jak max() i sorted() są parametryczne w duchu: przyjmują listę liczb, napisów albo dat, o ile elementy da się porównać, a podpowiedzi typów takie jak list[int] albo TypeVar mogą to jawnie zapisać.
Polimorfizm statyczny i dynamiczny w Javie
Java ma oba rodzaje, a pytania na rozmowach kwalifikacyjnych często dotyczą różnicy między nimi:
class Animal { String sound() { return "..."; } }
class Dog extends Animal {
@Override String sound() { return "Woof"; }
}
Animal pet = new Dog();
pet.sound(); // "Woof": chosen at run time from the object
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
add(2, 3); // int version, chosen by the compiler
add(2.5, 1.0); // double version
Zmienna pet ma typ Animal, a mimo to wykonuje się wersja z Dog, bo wybór zależy od obiektu, a nie od zmiennej. Dwie metody add to przeciążanie: kompilator wybiera jedną na podstawie typów argumentów, zanim program się uruchomi. Interfejsy dają to samo zachowanie w czasie działania bez współdzielenia kodu: każdą klasę implementującą Comparable można posortować przez Collections.sort. C++ działa tak samo z jedną różnicą: metoda dostaje wiązanie dynamiczne tylko wtedy, gdy jest zadeklarowana jako virtual. Zobacz polimorfizm w Javie, przeciążanie metod i funkcje wirtualne w C++.
Polimorfizm w Pythonie: duck typing i operatory
Python nie wymaga wspólnej klasy nadrzędnej. Jeśli obiekt ma metodę, którą wywołuje kod, to działa: "jeśli chodzi jak kaczka i kwacze jak kaczka, to jest kaczką". Operatory też są polimorficzne. Napisanie a + b wywołuje a.__add__(b), więc twoje własne klasy mogą obsługiwać +, len() i print():
3 abcd [1, 2]
Vector(11, 22)
5 3 2
Dziedziczenie to jeden ze sposobów na polimorfizm, a nie wymóg. Vector nie dziedziczy po niczym szczególnym, a mimo to len() i + traktują go jak typ wbudowany. Dokumentacja dziedziczenia w Pythonie pokazuje drogę przez podklasy.
Częste błędy
Oczekiwanie, że Python przeciąża według sygnatury. Drugie def o tej samej nazwie po cichu zastępuje pierwsze:
Hi Ana Silva
Traceback (most recent call last):
...
TypeError: greet() missing 1 required positional argument: 'last'
Zamiast tego użyj argumentu domyślnego: def greet(first, last="").
Zapominanie o virtual w C++. Bez niego wywołanie metody przez wskaźnik do klasy bazowej uruchamia wersję bazową, nawet gdy obiekt jest podklasą.
Nadpisania, które zmieniają znaczenie. Jeśli area() w jednej podklasie zwraca obwód, psuje się każdy kod, który ufa area(). Nadpisanie musi dotrzymać obietnicy złożonej przez rodzica; ta reguła nazywa się zasadą podstawienia Liskov.
Sprawdzanie typów wszędzie. Łańcuch gałęzi if type(x) == ... zwykle oznacza, że każdy typ powinien zamiast tego mieć własną metodę.
Co dalej
Polimorfizm działa razem z enkapsulacją, która chroni dane każdego obiektu, i abstrakcją, która definiuje wspólny interfejs implementowany przez typy. Poćwicz nadpisywanie w dokumentacji klas w Pythonie albo wypróbuj kurs Java, gdzie przeciążanie i nadpisywanie pojawiają się obok siebie.
Najczęściej zadawane pytania
Jaki jest przykład polimorfizmu?
+ w Pythonie: 1 + 2 dodaje liczby, "ab" + "cd" łączy napisy, a [1] + [2] łączy listy. Ten sam symbol działa na trzech typach, a każdy typ decyduje, co on oznacza. W kodzie obiektowym klasycznym przykładem jest lista figur, w której każda odpowiada na area() własnym wzorem.Czym różni się polimorfizm od dziedziczenia?
Czym jest polimorfizm w biologii?
Czy Python obsługuje przeciążanie metod?
def o tej samej nazwie zastępuje pierwsze. Kod w Pythonie osiąga ten sam efekt za pomocą argumentów domyślnych, *args albo functools.singledispatch, który wybiera funkcję na podstawie typu pierwszego argumentu.Dlaczego polimorfizm jest przydatny?
shape.draw() na każdej figurze, nie wymaga żadnych zmian, gdy dodasz klasę trójkąta; pisze się tylko nową klasę. Bez polimorfizmu ta pętla dostawałaby nową gałąź if dla każdego typu.