Skip to content

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 2

Pitfall 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 instance

Pitfall 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 init

Method 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:

  1. 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
  2. Local Precedence: Thứ tự khai báo trong class definition được tôn trọng
  3. 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 TCPUDP. 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ỏiNếu CÓNếu KHÔNG
Subclass có thể thay thế hoàn toàn parent?Inheritance OKDùng Composition
Cần override > 50% methods của parent?CompositionInheritance 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 + ProtocolSingle Inheritance
Chỉ cần thêm behaviors, không có state?MixinsComposition

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ốngSử dụngVí dụ
Quan hệ "là một", ít cấpKế thừaDog(Animal)
Quan hệ "có một"CompositionCarEngine
Cần hoán đổi implementationsABC / ProtocolPaymentProcessor interface
Hành vi chia sẻ, không có stateMixin classesLoggableMixin
Cây type phức tạpTránh!Refactor sang Composition
Cần runtime flexibilityCompositionStrategy pattern
Framework yêu cầuKế thừaDjango 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__)