8.1 Polimorfizm
Polimorfizm to jedna z głównych koncepcji programowania obiektowego (OOP), która pozwala obiektom różnych klas używać tego samego interfejsu.
W Pythonie polimorfizm osiąga się przez dynamiczne typowanie i dziedziczenie. Ważnym aspektem polimorfizmu jest przeciążanie metod i zastępowanie metod klasy bazowej metodami klasy potomnej.
Główne koncepcje polimorfizmu:
Jednolity interfejs dla różnych obiektów:
Polimorfizm pozwala używać jednolitego interfejsu dla obiektów różnych klas, które mogą mieć różne implementacje metod.
Dynamiczne typowanie:
W Pythonie typ zmiennej określa się w trakcie wykonywania, co pozwala funkcjom pracować z obiektami dowolnej klasy, o ile wspierają wymagany interfejs.
Dziedziczenie:
Pozwala tworzyć hierarchie klas, gdzie podklasy dziedziczą właściwości i metody klasy bazowej, ale mogą je nadpisywać dla swojej specyficznej funkcjonalności.
Przykłady polimorfizmu:
Prosty przykład polimorfizmu: wszystkie trzy klasy mają metodę o nazwie move()
. To oznacza, że możemy napisać kod, który będzie jednocześnie działał z tymi obiektami.
class Car:
def move(self):
pass
class Human:
def move(self):
pass
class Bird:
def move(self):
print("Kaa!")
car = Car()
human = Human()
bird = Bird()
for it in [car, human, bird]:
it.move()
W tym przypadku „wspólnym interfejsem” jest nazwa metody lub jej sygnatura, jeśli metoda ma parametry.
8.2 Nadpisywanie metod
Polimorfizm jest często używany razem z dziedziczeniem do tworzenia hierarchii klas, gdzie klasa bazowa definiuje wspólny interfejs, a podklasy realizują specyficzne szczegóły.
class Employee:
def get_salary(self):
return 1000
class FullTimeEmployee(Employee):
def get_salary(self):
return 5000
class PartTimeEmployee(Employee):
def get_salary(self):
return 3000
class Intern(Employee):
pass
def print_salary(employee):
print(employee.get_salary())
employees = [FullTimeEmployee(), PartTimeEmployee(), Intern()]
for employee in employees:
print_salary(employee)
Ten przykład jest podobny do poprzedniego, ale ma ważną różnicę — nasze klasy nie muszą mieć metody get_salary
, bo zawsze jest ona w klasie bazowej. Teraz „wspólnym interfejsem” wszystkich klas jest nie tylko metoda get_salary()
, ale właśnie klasa Employee
ze wszystkimi jej metodami i atrybutami.
8.3 Wywołanie metod klasy potomnej
Spójrz uważnie na poniższy kod:
Wywołujemy metodę print_salary()
, która występuje tylko w klasie bazowej Employee, a ona z klasy bazowej wywołuje inną metodę klasy bazowej — get_salary()
. Jakie więc pensje zostaną wypisane na ekran?
class Employee:
def print_salary(self):
salary = self.get_salary()
print(salary)
def get_salary(self):
return 1000
class FullTimeEmployee(Employee):
def get_salary(self):
return 5000
class PartTimeEmployee(Employee):
def get_salary(self):
return 3000
class Intern(Employee):
pass
employees = [FullTimeEmployee(), PartTimeEmployee(), Intern()]
for employee in employees:
employee.print_salary()
Ważne!
Przeczytaj, co jest napisane poniżej — to ważne.
Jeśli wywołamy metodę print_salary()
, na przykład w klasie FullTimeEmployee
, to wywoła się metoda klasy bazowej, ponieważ taka metoda nie jest zadeklarowana w samej klasie.
Ale ta metoda print_salary()
będzie wywoływać metodę get_salary()
obiektu self
, który ma typ FullTimeEmployee
, a nie Employee
. Dlatego wywoła się właśnie metoda get_salary()
z klasy FullTimeEmployee
!
Co więcej, klasa FullTimeEmployee
może wywołać metodę get_salary()
klasy bazowej dla swoich potrzeb. Na przykład, chcemy obliczać pensję w procentach od stawki bazowej:
class FullTimeEmployee(Employee):
def get_salary(self):
base_salary = super().get_salary()
return base_salary * 5
# 500%
class PartTimeEmployee(Employee):
def get_salary(self):
base_salary = super().get_salary()
return base_salary * 3 # 300%
8.4 Przeciążanie metod
Przeciążanie metod (method overloading)
w Pythonie — to zdolność do tworzenia kilku metod o takiej samej nazwie, ale różnych parametrach. Jednak w czystej postaci przeciążanie metod nie jest wspierane w Pythonie, jak to jest w innych językach (na przykład C++ czy Java).
W Pythonie można imitować przeciążanie metod przez użycie argumentów domyślnych, *args
i **kwargs
.
Przykłady przeciążania metod
Możemy zrealizować funkcję, która będzie mieć różne zachowanie w zależności od liczby przekazanych argumentów. Można to zrobić na różne sposoby, na przykład tak:
class Example:
def display(self, a=None, b=None):
if a is not None and b is not None: print(a, b)
elif a is not None: print(a)
else: print("Brak argumentów")
obj = Example()
obj.display(1, 2) # Output: 1 2
obj.display(1) # Output: 1
obj.display() # Output: Brak argumentów
Można również uwzględnić typ przekazanych danych — po prostu zrobić sprawdzenie typu:
class Example:
def mod(self, a, b):
if type(a) == int and type(b) == int: print(a % b)
elif type(a) == float or type(b) == float: print(round(a / b))
else: print("Instrukcja pomocy: a i b powinny być typu int lub float")
obj = Example()
obj.mod(5, 2) # Output: 1
obj.mod(5.0, 2) # Output: 2
obj.mod("5", 2) # Output: Instrukcja pomocy: a i b powinny być typu int lub float
Jeśli przekazane zostaną niewłaściwe typy danych, można wyświetlić instrukcję lub nawet link do dokumentacji w internecie — to również bardzo popularne rozwiązanie.
GO TO FULL VERSION