Trong lập trình hướng đối tượng (OOP) với Python, việc quản lý mã nguồn hiệu quả phụ thuộc vào cách chúng ta tái sử dụng và mở rộng các lớp. Dưới đây là các khái niệm chuyên sâu về kế thừa (Inheritance), thành phần (Composition), đa hình (Polymorphism) và Duck Typing.
1. Kỹ thuật phái sinh và tái sử dụng phương thức
Lớp con (subclass) có thể kế thừa các đặc tính từ lớp cha và định nghĩa thêm các thuộc tính hoặc phương thức riêng. Để tái sử dụng logic từ lớp cha, chúng ta có hai cách tiếp cận chính:
Sử dụng tên lớp cụ thể (Explicit Call)
Cách này gọi trực tiếp hàm của lớp cha như một hàm thông thường và truyền tham số self thủ công. Phương pháp này không phụ thuộc chặt chẽ vào cấu trúc kế thừa.
class NhanSu:
def __init__(self, ho_ten, tuoi, gioi_tinh):
self.ho_ten = ho_ten
self.tuoi = tuoi
self.gioi_tinh = gioi_tinh
class GiangVien(NhanSu):
def __init__(self, ho_ten, tuoi, gioi_tinh, hoc_vi, luong):
# Gọi trực tiếp thông qua tên lớp cha
NhanSu.__init__(self, ho_ten, tuoi, gioi_tinh)
self.hoc_vi = hoc_vi
self.luong = luong
gv = GiangVien('Nguyen Van A', 35, 'Nam', 'Tien si', 2000)
print(gv.__dict__)
Sử dụng hàm super()
super() trả về một đối tượng tạm thời của lớp cha, cho phép gọi các phương thức đã được định nghĩa. Cách này tuân thủ thứ tự giải quyết phương thức (MRO - Method Resolution Order) và tự động truyền self.
class NhanSu:
def __init__(self, ho_ten, tuoi, gioi_tinh):
self.ho_ten = ho_ten
self.tuoi = tuoi
self.gioi_tinh = gioi_tinh
class GiangVien(NhanSu):
def __init__(self, ho_ten, tuoi, gioi_tinh, hoc_vi, luong):
# Sử dụng super() để gọi hàm khởi tạo của lớp cha
super().__init__(ho_ten, tuoi, gioi_tinh)
self.hoc_vi = hoc_vi
self.luong = luong
gv = GiangVien('Tran Thi B', 28, 'Nu', 'Thac si', 1500)
Lưu ý về MRO trong đa kế thừa: super() sẽ tìm kiếm phương thức dựa trên danh sách MRO của lớp hiện tại, không đơn thuần là lớp cha trực tiếp.
class X:
def hien_thi(self):
print("Tu X")
super().hien_thi()
class Y:
def hien_thi(self):
print("Tu Y")
class Z(X, Y):
pass
obj = Z()
obj.hien_thi()
# Kết quả sẽ in ra 'Tu X' sau đó đến 'Tu Y' dựa trên MRO của Z
2. Quan hệ thành phần (Composition)
Thành phần là kỹ thuật nhúng một đối tượng của lớp này vào làm thuộc tính của lớp khác. Khác với kế thừa thể hiện quan hệ "is-a" (là một), thành phần thể hiện quan hệ "has-a" (có một).
class KhoaHoc:
def __init__(self, ten_mon, thoi_gian):
self.ten_mon = ten_mon
self.thoi_gian = thoi_gian
def thong_tin(self):
print(f"Khoa hoc: {self.ten_mon}, Thoi gian: {self.thoi_gian}")
class NgayThang:
def __init__(self, ngay, thang, nam):
self.ngay = ngay
self.thang = thang
self.nam = nam
def dinh_dang(self):
print(f"{self.ngay}/{self.thang}/{self.nam}")
class SinhVien:
def __init__(self, ten, ngay, thang, nam):
self.ten = ten
# Composition: Sinh vien 'co' mot ngay sinh
self.ngay_sinh = NgayThang(ngay, thang, nam)
self.danh_sach_mon = []
sv = SinhVien("Le Van C", 15, 5, 2001)
mon_python = KhoaHoc("Python Backend", "6 thang")
sv.danh_sach_mon.append(mon_python)
sv.ngay_sinh.dinh_dang()
sv.danh_sach_mon[0].thong_tin()
3. Tính đa hình (Polymorphism) và Lớp trừu tượng
Đa hình cho phép các đối tượng thuộc các lớp khác nhau phản hồi cùng một thông điệp (gọi cùng một phương thức) theo những cách khác nhau.
Lớp trừu tượng (Abstract Base Class)
Để đảm bảo các lớp con thực thi đúng các phương thức cần thiết, chúng ta sử dụng module abc.
import abc
class ThanhToan(metaclass=abc.ABCMeta):
@abc.abstractmethod
def thuc_hien(self, so_tien):
pass
class ChuyenKhoan(ThanhToan):
def thuc_hien(self, so_tien):
print(f"Dang chuyen khoan: {so_tien} VND")
class ViDienTu(ThanhToan):
def thuc_hien(self, so_tien):
print(f"Thanh toan qua Vi dien tu: {so_tien} VND")
def thanh_toan_hoa_don(phuong_thuc, so_tien):
# Khong can biet phuong_thuc la lop nao, chi can biet no co ham thuc_hien
phuong_thuc.thuc_hien(so_tien)
ck = ChuyenKhoan()
vdt = ViDienTu()
thanh_toan_hoa_don(ck, 500000)
thanh_toan_hoa_don(vdt, 200000)
4. Duck Typing
Duck Typing là một khái niệm đặc thù trong các ngôn ngữ dynamic typing như Python: "Nếu nó đi như một con vịt và kêu như một con vịt, thì nó là một con vịt". Chúng ta không quan tâm đến kiểu dữ liệu thực sự của đối tượng, chỉ quan tâm đến việc nó có hỗ trợ các phương thức hoặc thuộc tính mà ta cần hay không.
class FileHeThong:
def read(self):
print("Doc du lieu tu o cung...")
def write(self):
print("Ghi du lieu vao o cung...")
class BoNhoDem:
def read(self):
print("Doc du lieu tu RAM...")
def write(self):
print("Ghi du lieu vao RAM...")
def xu_ly_du_lieu(obj):
# obj co the la bat cu thu gi, mien la co phuong thuc read va write
obj.read()
obj.write()
file = FileHeThong()
ram = BoNhoDem()
xu_ly_du_lieu(file)
xu_ly_du_lieu(ram)
Duck Typing giúp giảm bớt sự phụ thuộc vào cây thừa kế phức tạp, làm cho mã nguồn trở nên linh hoạt và dễ mở rộng hơn.