Giao diện
Kế thừa Dùng Cẩn thận
"Ưu tiên composition hơn kế thừa" — Gang of Four
Learning Outcomes
Sau khi hoàn thành trang này, bạn sẽ:
- ✅ Hiểu kế thừa đơn và đa kế thừa trong Python
- ✅ Nắm vững thuật toán C3 Linearization và MRO
- ✅ Sử dụng
super()đúng cách và tránh các pitfalls phổ biến - ✅ Phân biệt khi nào dùng Inheritance vs Composition
- ✅ Implement ABC và Protocol cho dependency injection
- ✅ Áp dụng decision tree để chọn pattern phù hợp
Kế thừa Cơ bản
python
class DongVat:
"""Lớp cơ sở (Cha)."""
def __init__(self, ten: str):
self.ten = ten
def keu(self) -> str:
return "Một tiếng nào đó"
def thong_tin(self) -> str:
return f"Tôi là {self.ten}"
class Cho(DongVat):
"""Lớp dẫn xuất (Con)."""
def __init__(self, ten: str, giong: str):
super().__init__(ten) # Gọi __init__ của lớp cha
self.giong = giong
def keu(self) -> str: # Ghi đè phương thức cha
return "Gâu gâu!"
def bat_bong(self) -> str: # Phương thức mới
return f"{self.ten} đang bắt bóng!"
cho = Cho("Buddy", "Golden Retriever")
cho.keu() # "Gâu gâu!" (đã ghi đè)
cho.thong_tin() # "Tôi là Buddy" (kế thừa)
cho.bat_bong() # "Buddy đang bắt bóng!" (mới)Hàm super() Deep Dive
super() cho phép gọi phương thức của lớp cha theo MRO.
python
class Cha:
def chao(self) -> str:
return "Xin chào từ Cha"
class Con(Cha):
def chao(self) -> str:
loi_chao_cha = super().chao() # Gọi chao của cha
return f"{loi_chao_cha} và Con"
con = Con()
con.chao() # "Xin chào từ Cha và Con"super() với __init__
python
class PhuongTien:
def __init__(self, thuong_hieu: str, nam: int):
self.thuong_hieu = thuong_hieu
self.nam = nam
class Oto(PhuongTien):
def __init__(self, thuong_hieu: str, nam: int, so_cua: int):
super().__init__(thuong_hieu, nam) # Khởi tạo thuộc tính cha
self.so_cua = so_cua # Thêm thuộc tính riêng của ô tô
class OtoDien(Oto):
def __init__(self, thuong_hieu: str, nam: int, so_cua: int, pin_kwh: float):
super().__init__(thuong_hieu, nam, so_cua)
self.pin_kwh = pin_kwh
tesla = OtoDien("Tesla", 2024, 4, 100.0)
print(tesla.thuong_hieu) # "Tesla" (từ PhuongTien)
print(tesla.pin_kwh) # 100.0 (từ OtoDien)super() Pitfalls & Best Practices
Pitfall 1: Quên gọi super().__init__()
python
# ❌ BUG: Quên gọi super().__init__()
class Base:
def __init__(self):
self.base_attr = "initialized"
class Child(Base):
def __init__(self):
# Quên super().__init__()!
self.child_attr = "child"
child = Child()
child.base_attr # AttributeError: 'Child' object has no attribute 'base_attr'
# ✅ FIX: Luôn gọi super().__init__()
class Child(Base):
def __init__(self):
super().__init__()
self.child_attr = "child"Pitfall 2: super() với Multiple Inheritance và **kwargs
python
# ❌ BUG: Không forward **kwargs
class A:
def __init__(self, a_param):
self.a = a_param
class B:
def __init__(self, b_param):
self.b = b_param
class C(A, B):
def __init__(self, a_param, b_param):
super().__init__(a_param) # Chỉ gọi A.__init__!
# B.__init__ không bao giờ được gọi!
# ✅ FIX: Dùng **kwargs pattern
class A:
def __init__(self, a_param, **kwargs):
super().__init__(**kwargs) # Forward kwargs
self.a = a_param
class B:
def __init__(self, b_param, **kwargs):
super().__init__(**kwargs)
self.b = b_param
class C(A, B):
def __init__(self, a_param, b_param, **kwargs):
super().__init__(a_param=a_param, b_param=b_param, **kwargs)
c = C(a_param=1, b_param=2)
print(c.a, c.b) # 1 2Pitfall 3: super() trong Class Methods
python
class Base:
@classmethod
def create(cls):
return cls()
class Child(Base):
@classmethod
def create(cls):
# ❌ KHÔNG ĐÚNG: Base.create() - hardcode class
# ✅ ĐÚNG: super().create() - theo MRO
instance = super().create()
instance.extra = "added"
return instancePitfall 4: super() với __new__
python
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
# ✅ ĐÚNG: Gọi super().__new__(cls)
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, value):
self.value = value
# Cả hai đều là cùng một instance
s1 = Singleton(1)
s2 = Singleton(2)
print(s1 is s2) # True
print(s1.value) # 2 (bị ghi đè bởi __init__ lần 2)Best Practice: Cooperative Multiple Inheritance
python
class Loggable:
"""Mixin thêm logging capability."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._log = []
def log(self, message: str):
self._log.append(message)
class Serializable:
"""Mixin thêm serialization capability."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
def to_dict(self) -> dict:
return {k: v for k, v in self.__dict__.items() if not k.startswith('_')}
class User(Loggable, Serializable):
def __init__(self, name: str, email: str, **kwargs):
super().__init__(**kwargs)
self.name = name
self.email = email
user = User(name="HPN", email="hpn@example.com")
user.log("User created")
print(user.to_dict()) # {'name': 'HPN', 'email': 'hpn@example.com'}Diamond Problem 💎
Khi một class kế thừa từ nhiều lớp cha có chung tổ tiên:
python
class DongVat:
def __init__(self):
print("DongVat init")
class BayDuoc(DongVat):
def __init__(self):
super().__init__()
print("BayDuoc init")
class BoiDuoc(DongVat):
def __init__(self):
super().__init__()
print("BoiDuoc init")
class Vit(BayDuoc, BoiDuoc):
def __init__(self):
super().__init__()
print("Vit init")
vit = Vit()
# Output:
# DongVat init ← Chỉ một lần! (MRO xử lý điều này)
# BoiDuoc init
# BayDuoc init
# Vit initMethod Resolution Order (MRO)
Python dùng C3 Linearization để xác định thứ tự tìm kiếm phương thức:
python
print(Vit.__mro__)
# (<class 'Vit'>, <class 'BayDuoc'>, <class 'BoiDuoc'>,
# <class 'DongVat'>, <class 'object'>)C3 Linearization Algorithm
C3 đảm bảo 3 tính chất quan trọng:
- Monotonicity: Nếu A đứng trước B trong MRO của C, thì A cũng đứng trước B trong MRO của bất kỳ subclass nào của C
- Local Precedence: Thứ tự khai báo trong class definition được tôn trọng
- Extended Precedence: Subclass luôn đứng trước superclass
python
# Công thức C3:
# L[C] = C + merge(L[B1], L[B2], ..., [B1, B2, ...])
#
# merge: Lấy head của list đầu tiên nếu nó không xuất hiện
# trong tail của bất kỳ list nào khác
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
# Tính MRO của D:
# L[A] = [A, object]
# L[B] = [B] + merge([A, object], [A]) = [B, A, object]
# L[C] = [C] + merge([A, object], [A]) = [C, A, object]
# L[D] = [D] + merge([B, A, object], [C, A, object], [B, C])
# = [D, B] + merge([A, object], [C, A, object], [C])
# = [D, B, C] + merge([A, object], [A, object])
# = [D, B, C, A, object]
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)MRO Conflict - Khi C3 Thất bại
python
class X: pass
class Y: pass
class A(X, Y): pass
class B(Y, X): pass
# ❌ TypeError: Cannot create a consistent MRO
class C(A, B): pass # X trước Y trong A, nhưng Y trước X trong B!Visualize MRO
python
def print_mro(cls):
"""In MRO dễ đọc."""
print(f"MRO của {cls.__name__}:")
for i, c in enumerate(cls.__mro__):
print(f" {i}. {c.__name__}")
print_mro(Vit)
# MRO của Vit:
# 0. Vit
# 1. BayDuoc
# 2. BoiDuoc
# 3. DongVat
# 4. object🚨 QUY TẮC VÀNG CỦA HPN
Tránh đa kế thừa phức tạp! Nếu bạn phải giải thích MRO cho team, code của bạn đã quá phức tạp.
Composition Over Inheritance
Kế thừa: quan hệ "là một" (is-a)
Composition: quan hệ "có một" (has-a)
❌ Xấu: Cây Kế thừa Sâu
python
# ❌ ANTI-PATTERN: Cây kế thừa sâu
class ThucThe:
pass
class ThucTheSong(ThucThe):
pass
class DongVat(ThucTheSong):
pass
class DongVatCoVu(DongVat):
pass
class Cho(DongVatCoVu):
pass
class GoldenRetriever(Cho):
pass # 6 cấp - ác mộng bảo trì!✅ Tốt: Composition
python
# ✅ TỐT: Composition với trách nhiệm rõ ràng
from dataclasses import dataclass
from typing import Protocol
class CoBayDuoc(Protocol):
def bay(self) -> str: ...
class CoBoiDuoc(Protocol):
def boi(self) -> str: ...
@dataclass
class DoiCanh:
"""Khả năng bay."""
sai_canh: float
def bay(self) -> str:
return f"Đang bay với sải cánh {self.sai_canh}m"
@dataclass
class Chan:
"""Khả năng bơi."""
kich_thuoc: str
def boi(self) -> str:
return f"Đang bơi với chân màng {self.kich_thuoc}"
@dataclass
class Vit:
"""Vịt CÓ cánh và chân (composition)."""
ten: str
canh: DoiCanh
chan: Chan
def bay(self) -> str:
return self.canh.bay()
def boi(self) -> str:
return self.chan.boi()
# Sử dụng
vit = Vit(
ten="Donald",
canh=DoiCanh(sai_canh=0.5),
chan=Chan(kich_thuoc="vừa")
)
vit.bay() # "Đang bay với sải cánh 0.5m"
vit.boi() # "Đang bơi với chân màng vừa"Abstract Base Classes (ABC)
Python không có interface như Java, nhưng có ABC để bắt buộc implement phương thức.
python
from abc import ABC, abstractmethod
class BoXuLyThanhToan(ABC):
"""Lớp cơ sở trừu tượng - không thể khởi tạo."""
@abstractmethod
def xu_ly_thanh_toan(self, so_tien: float) -> bool:
"""Phải được implement bởi lớp con."""
pass
@abstractmethod
def hoan_tien(self, ma_giao_dich: str) -> bool:
"""Phải được implement bởi lớp con."""
pass
def kiem_tra_so_tien(self, so_tien: float) -> bool:
"""Phương thức cụ thể - tùy chọn ghi đè."""
return so_tien > 0
# ❌ Không thể khởi tạo ABC
# bo_xu_ly = BoXuLyThanhToan() # TypeError!
class BoXuLyStripe(BoXuLyThanhToan):
"""Implementation cụ thể."""
def xu_ly_thanh_toan(self, so_tien: float) -> bool:
print(f"Đang xử lý {so_tien:,.0f} VND qua Stripe")
return True
def hoan_tien(self, ma_giao_dich: str) -> bool:
print(f"Đang hoàn tiền {ma_giao_dich}")
return True
# ✅ Có thể khởi tạo lớp cụ thể
stripe = BoXuLyStripe()
stripe.xu_ly_thanh_toan(99999)ABC với Properties
python
from abc import ABC, abstractmethod
class Repository(ABC):
@property
@abstractmethod
def connection_string(self) -> str:
"""Lớp con phải cung cấp connection string."""
pass
@abstractmethod
def luu(self, du_lieu: dict) -> str:
pass
class PostgresRepository(Repository):
@property
def connection_string(self) -> str:
return "postgresql://localhost:5432/db"
def luu(self, du_lieu: dict) -> str:
# Lưu vào Postgres
return "da_luu"Pattern Thực tế: Interfaces
💡 KIẾN TRÚC PENRIFT
Trong source code Penrift, chúng tôi sử dụng Interfaces rất nhiều để dễ dàng chuyển đổi giữa giao thức TCP và UDP. Pattern này cho phép:
- Test với mock implementations
- Chuyển đổi giao thức mà không thay đổi business logic
- Mở rộng với các giao thức mới (QUIC, WebSocket) dễ dàng
python
from abc import ABC, abstractmethod
from typing import Protocol
# Protocol (interface có thể kiểm tra runtime)
class Transport(Protocol):
def gui(self, du_lieu: bytes) -> None: ...
def nhan(self) -> bytes: ...
def dong(self) -> None: ...
class TCPTransport:
def gui(self, du_lieu: bytes) -> None:
print("Đang gửi qua TCP")
def nhan(self) -> bytes:
return b"Phản hồi TCP"
def dong(self) -> None:
print("Đã đóng kết nối TCP")
class UDPTransport:
def gui(self, du_lieu: bytes) -> None:
print("Đang gửi qua UDP")
def nhan(self) -> bytes:
return b"Phản hồi UDP"
def dong(self) -> None:
print("Đã đóng socket UDP")
# Client không quan tâm implementation
class TunnelClient:
def __init__(self, transport: Transport):
self.transport = transport
def chay(self, du_lieu: bytes) -> bytes:
self.transport.gui(du_lieu)
return self.transport.nhan()
# Dễ dàng chuyển đổi!
tcp_client = TunnelClient(TCPTransport())
udp_client = TunnelClient(UDPTransport())Composition vs Inheritance Decision Tree
Decision Checklist
| Câu hỏi | Nếu CÓ | Nếu KHÔNG |
|---|---|---|
| Subclass có thể thay thế hoàn toàn parent? | Inheritance OK | Dùng Composition |
| Cần override > 50% methods của parent? | Composition | Inheritance OK |
| Parent class có thể thay đổi trong tương lai? | Composition (ít coupling) | Inheritance OK |
| Cần kết hợp behaviors từ nhiều nguồn? | Composition + Protocol | Single Inheritance |
| Chỉ cần thêm behaviors, không có state? | Mixins | Composition |
Ví dụ Thực tế
python
# ❌ ANTI-PATTERN: Kế thừa sai
class Stack(list):
"""Stack KẾ THỪA từ list - SAI!"""
pass
stack = Stack()
stack.push = stack.append
stack.push(1)
stack.insert(0, 999) # Oops! Stack không nên có insert!
# ✅ ĐÚNG: Composition
class Stack:
"""Stack CÓ một list - ĐÚNG!"""
def __init__(self):
self._items: list = []
def push(self, item):
self._items.append(item)
def pop(self):
return self._items.pop()
def peek(self):
return self._items[-1] if self._items else None
def __len__(self):
return len(self._items)
# Không thể gọi insert, remove, etc. - đúng behavior của Stack!Khi nào Dùng gì?
| Tình huống | Sử dụng | Ví dụ |
|---|---|---|
| Quan hệ "là một", ít cấp | Kế thừa | Dog(Animal) |
| Quan hệ "có một" | Composition | Car có Engine |
| Cần hoán đổi implementations | ABC / Protocol | PaymentProcessor interface |
| Hành vi chia sẻ, không có state | Mixin classes | LoggableMixin |
| Cây type phức tạp | Tránh! | Refactor sang Composition |
| Cần runtime flexibility | Composition | Strategy pattern |
| Framework yêu cầu | Kế thừa | Django models, Flask views |
Production Pitfalls 🚨
Pitfall 1: Fragile Base Class Problem
python
# ❌ BUG: Base class thay đổi phá vỡ subclass
class Counter:
def __init__(self):
self.count = 0
def add(self, items: list):
for item in items:
self.add_one(item)
def add_one(self, item):
self.count += 1
class LoggingCounter(Counter):
def add_one(self, item):
print(f"Adding: {item}")
super().add_one(item)
# Hoạt động tốt...
lc = LoggingCounter()
lc.add([1, 2, 3]) # Logs 3 times, count = 3
# Nhưng nếu Base class thay đổi implementation:
class Counter: # Version 2
def add(self, items: list):
self.count += len(items) # Không gọi add_one nữa!
def add_one(self, item):
self.count += 1
# LoggingCounter bị phá vỡ - không log gì cả!
# ✅ FIX: Dùng Composition
class LoggingCounter:
def __init__(self, counter: Counter):
self._counter = counter
def add(self, items: list):
for item in items:
print(f"Adding: {item}")
self._counter.add(items)Pitfall 2: Mixin với State Conflicts
python
# ❌ BUG: Hai mixins cùng dùng attribute name
class TimestampMixin:
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.created = datetime.now()
class AuditMixin:
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.created = "unknown" # Conflict!
class User(TimestampMixin, AuditMixin):
def __init__(self, name: str, **kwargs):
super().__init__(**kwargs)
self.name = name
user = User(name="HPN")
print(user.created) # "unknown" - TimestampMixin bị ghi đè!
# ✅ FIX: Prefix attributes hoặc dùng __slots__
class TimestampMixin:
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._timestamp_created = datetime.now()Pitfall 3: ABC không enforce đủ
python
from abc import ABC, abstractmethod
# ❌ KHÔNG ĐỦ: Chỉ check method existence
class Repository(ABC):
@abstractmethod
def save(self, entity): ...
class BadRepo(Repository):
def save(self, entity):
pass # Không làm gì - vẫn valid!
# ✅ BETTER: Dùng Protocol với runtime checks
from typing import Protocol, runtime_checkable
@runtime_checkable
class Repository(Protocol):
def save(self, entity) -> str: ...
def find(self, id: str) -> dict | None: ...
def use_repo(repo: Repository):
if not isinstance(repo, Repository):
raise TypeError("Invalid repository")
return repo.save({"data": "test"})Pitfall 4: Diamond Problem với State
python
# ❌ BUG: State bị khởi tạo nhiều lần
class A:
def __init__(self):
print("A init")
self.value = 1
class B(A):
def __init__(self):
super().__init__()
print("B init")
self.value += 10
class C(A):
def __init__(self):
super().__init__()
print("C init")
self.value += 100
class D(B, C):
def __init__(self):
super().__init__()
print("D init")
d = D()
# Output: A init, C init, B init, D init
# d.value = 111 (1 + 100 + 10) - có thể không như mong đợi!
# ✅ FIX: Explicit initialization hoặc Composition
class D:
def __init__(self):
self.b = B()
self.c = C()Bảng Tóm tắt
python
# === KẾ THỪA CƠ BẢN ===
class Cha:
def phuong_thuc(self): ...
class Con(Cha):
def phuong_thuc(self):
super().phuong_thuc() # Gọi cha
# Logic riêng của con
# === ABSTRACT BASE CLASS ===
from abc import ABC, abstractmethod
class Interface(ABC):
@abstractmethod
def phai_implement(self): ...
def tuy_chon_ghi_de(self):
return "mac_dinh"
# === PROTOCOL (Python 3.8+) ===
from typing import Protocol
class CoTheVe(Protocol):
def ve(self) -> None: ...
# Bất kỳ class nào có phương thức ve() đều khớp!
# === PATTERN COMPOSITION ===
class DongCo:
def khoi_dong(self): ...
class Oto:
def __init__(self):
self.dong_co = DongCo() # Quan hệ has-a
def khoi_dong(self):
self.dong_co.khoi_dong()
# === KIỂM TRA MRO ===
print(TenClass.__mro__)