Explore all courses in this category.
Explore all courses in this category.
Nguyễn Đình Cường
0 comment
09 Nov, 2026
Khi học OOP, kế thừa thường được giới thiệu như siêu năng lực: viết một lần, dùng lại nhiều nơi. Nhưng kinh nghiệm cho thấy lạm dụng nó sinh ra những cây phả hệ rối rắm. Có một lựa chọn khác gọn gàng hơn trong nhiều tình huống: composition.
Kế thừa dùng khi lớp con thực sự là một dạng đặc biệt của lớp cha: Chó là một Động vật, Hình tròn là một Hình học.
class Hinh:
def dien_tich(self):
raise NotImplementedError
class HinhTron(Hinh):
def __init__(self, r):
self.r = r
def dien_tich(self):
return 3.14159 * self.r ** 2
class HinhChuNhat(Hinh):
def __init__(self, dai, rong):
self.dai, self.rong = dai, rong
def dien_tich(self):
return self.dai * self.rongỞ đây kế thừa hợp lý: các lớp con chia sẻ cùng giao diện, và code khác có thể dùng chúng thay thế cho nhau.
Composition (kết hợp) nghĩa là một lớp chứa object của lớp khác như một thành phần. Xe hơi có một động cơ, không phải là một động cơ.
class DongCo:
def khoi_dong(self):
return "Brừm!"
class XeHoi:
def __init__(self):
self.dong_co = DongCo()
def chay(self):
return self.dong_co.khoi_dong() + " Xe đang chạy"
print(XeHoi().chay())Xe ủy quyền công việc cho động cơ. Muốn đổi sang động cơ điện, bạn chỉ thay thành phần mà không phải sửa cây kế thừa.
Đây là lý do một nguyên tắc thiết kế nổi tiếng khuyên: ưu tiên composition hơn kế thừa (favor composition over inheritance).
class BayDuoc:
def di_chuyen(self):
return "bay"
class BoiDuoc:
def di_chuyen(self):
return "bơi"
class Vit:
def __init__(self):
self.ky_nang = [BayDuoc(), BoiDuoc()]
def mo_ta(self):
return ", ".join(k.di_chuyen() for k in self.ky_nang)
print(Vit().mo_ta()) # bay, bơiKhả năng được tách thành những mảnh riêng và lắp ghép tùy ý. Thêm kỹ năng mới chỉ là thêm một mảnh.
| Câu hỏi tự đặt | Nghiêng về |
|---|---|
| B có thực sự là một dạng của A ở mọi nơi dùng A? | Kế thừa |
| Chỉ muốn dùng lại vài chức năng của A? | Composition |
| Hành vi cần đổi lúc chạy chương trình? | Composition |
| Có nhiều chiều biến thể độc lập? | Composition |
Đừng hiểu nhầm rằng kế thừa là xấu. Khung ứng dụng và thư viện giao diện dùng nó rất nhiều, và nó rất tự nhiên với các họ đối tượng rõ ràng. Nguyên tắc thực dụng: giữ cây kế thừa nông (một hai tầng), lớp cha ổn định, và luôn kiểm tra xem lớp con có thể thay thế lớp cha mà không gây bất ngờ không.
Một game có chiến binh, pháp sư, cung thủ; mỗi loại có thể có kỹ năng khác nhau, thậm chí đổi kỹ năng trong lúc chơi. Nếu dùng kế thừa, bạn sẽ có cây lớp phình to. Với composition, kỹ năng là các mảnh riêng:
class DanhThuong:
def dung(self):
return "chém 10 sát thương"
class PhepLua:
def dung(self):
return "lửa 25 sát thương"
class NhanVat:
def __init__(self, ten, ky_nang):
self.ten = ten
self.ky_nang = ky_nang
def tan_cong(self):
return f"{self.ten} {self.ky_nang.dung()}"
hiep_si = NhanVat("Hiệp sĩ", DanhThuong())
print(hiep_si.tan_cong())
hiep_si.ky_nang = PhepLua() # đổi kỹ năng khi đang chạy
print(hiep_si.tan_cong())Đây là một dạng của mẫu thiết kế Strategy. Chỉ cần đổi thành phần là hành vi đổi theo, không cần tạo lớp con mới. Nhiều game engine, như hệ thống thành phần của Unity, được thiết kế theo tinh thần này.
Hỏi "là một" hay "có một" là cách nhanh để chọn giữa kế thừa và composition. Khi phân vân, hãy chọn composition vì nó linh hoạt và ít ràng buộc hơn. Hãy thử viết lại một lớp kế thừa gượng ép trong dự án của bạn bằng composition, bạn sẽ thấy sự khác biệt.
Muốn học bài bản và có người hướng dẫn? Hãy xem các khóa học trên codebem.com để đi từ nền tảng đến dự án thực tế.
Nguyễn Đình Cường
0 comment