8.1 Đa hình
Đa hình là một trong những khái niệm cơ bản của lập trình hướng đối tượng (OOP), cho phép các object thuộc các class khác nhau sử dụng cùng một interface.
Trong Python, đa hình được thực hiện thông qua dynamic typing và kế thừa. Một khía cạnh quan trọng của đa hình là overriding methods và thay thế các phương thức của class cơ sở bằng phương thức của class con.
Những khái niệm chính của đa hình:
Cùng một interface cho các object khác nhau:
Đa hình cho phép sử dụng cùng một interface cho các object của các class khác nhau, dù chúng có thể có cách triển khai khác biệt.
Dynamic typing:
Trong Python, kiểu của biến được xác định trong runtime, điều này cho phép function hoạt động với object của bất kỳ class nào, miễn là chúng hỗ trợ interface yêu cầu.
Kế thừa:
Cho phép tạo ra các hierarchy class, nơi subclass có thể kế thừa thuộc tính và phương thức từ class cơ sở nhưng vẫn có thể override chúng cho mục đích riêng.
Ví dụ về đa hình:
Một ví dụ đơn giản về đa hình: cả ba class đều có một method tên là move()
. Điều này có nghĩa là chúng ta có thể viết code để xử lý đồng thời các object này.
class Car:
def move(self):
pass
class Human:
def move(self):
pass
class Bird:
def move(self):
print("Cạc cạc!")
car = Car()
human = Human()
bird = Bird()
for it in [car, human, bird]:
it.move()
Trong trường hợp này, "interface chung" chính là tên method, hoặc cũng có thể là signature của nó, nếu method có tham số.
8.2 Overriding methods
Đa hình thường được sử dụng cùng với kế thừa để tạo ra các hierarchy class, nơi class cơ sở xác định interface chung, còn subclass triển khai các chi tiết cụ thể.
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)
Ví dụ này khá giống với ví dụ trước, nhưng có một sự khác biệt quan trọng — các class của chúng ta không cần thiết phải có method get_salary
, vì nó luôn tồn tại trong class cơ sở. Khi đó, "interface chung" của tất cả các class không chỉ là method get_salary()
, mà còn chính là class Employee
với tất cả các method và thuộc tính của nó.
8.3 Gọi phương thức của class con
Hãy nhìn kỹ đoạn code dưới đây:
Chúng ta gọi method print_salary()
, chỉ tồn tại trong class cơ sở Employee, và method này lại gọi một method khác từ class cơ sở — get_salary()
. Vậy màn hình sẽ in ra mức lương nào?
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()
Quan trọng!
Đọc kỹ nội dung dưới đây — rất quan trọng.
Nếu chúng ta gọi method print_salary()
, ví dụ, trên class FullTimeEmployee
, thì method từ class cơ sở sẽ được gọi, vì class đó không chứa method này.
Tuy nhiên, chính method print_salary()
sẽ gọi method get_salary()
trên object self
, object này thuộc kiểu FullTimeEmployee
, chứ không phải Employee
. Vì vậy, method get_salary()
của class FullTimeEmployee
sẽ được gọi!
Hơn nữa, class FullTimeEmployee
có thể gọi method get_salary()
từ class cơ sở cho mục đích riêng. Ví dụ, chúng ta muốn tính lương theo tỷ lệ phần trăm từ mức lương cơ bản:
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 Method overloading
Overloading methods trong Python là khả năng tạo ra nhiều method cùng tên nhưng với các tham số khác nhau. Tuy nhiên, trong Python, method overloading không được hỗ trợ một cách thuần túy như các ngôn ngữ khác (ví dụ: C++ hay Java).
Trong Python, chúng ta có thể giả lập method overloading bằng cách sử dụng default arguments, *args
và **kwargs
.
Ví dụ về method overloading
Chúng ta có thể viết một function với hành vi khác nhau dựa trên số lượng argument được truyền vào. Ví dụ:
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("Không có argument nào!")
obj = Example()
obj.display(1, 2) # Output: 1 2
obj.display(1) # Output: 1
obj.display() # Output: Không có argument nào!
Cũng có thể kiểm tra kiểu dữ liệu đã truyền vào:
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("Hướng dẫn: a và b nên là int hoặc float")
obj = Example()
obj.mod(5, 2) # Output: 1
obj.mod(5.0, 2) # Output: 2
obj.mod("5", 2) # Output: Hướng dẫn: a và b nên là int hoặc float
Nếu kiểu dữ liệu không chính xác, bạn có thể in ra hướng dẫn hoặc thậm chí cung cấp đường dẫn tới tài liệu trên mạng — cách này cũng rất phổ biến.
GO TO FULL VERSION