Skip to content

Classes & Objects OOP

Python OOP = Đơn giản hơn là Phức tạp

Cơ bản về Class

Cấu trúc của một Class

python
class User:
    """Một class user đơn giản."""
    
    # Class attribute (chia sẻ bởi tất cả instances)
    platform: str = "PENALGO"
    
    def __init__(self, ten: str, email: str) -> None:
        """Constructor - được gọi khi tạo instance."""
        # Instance attributes (riêng biệt cho mỗi instance)
        self.ten = ten
        self.email = email
        self.hoat_dong = True
    
    def chao(self) -> str:
        """Instance method."""
        return f"Xin chào, tôi là {self.ten}"

# Tạo instances
user1 = User("HPN", "HPN@penalgo.io")
user2 = User("Tom", "tom@penalgo.io")

print(user1.chao())  # "Xin chào, tôi là HPN"
print(User.platform)  # "PENALGO" (class attribute)

Phương thức __init__

__init__constructor của Python - được gọi tự động khi tạo object.

python
class SanPham:
    def __init__(
        self, 
        ten: str, 
        gia: float, 
        so_luong: int = 0  # Giá trị mặc định
    ) -> None:
        self.ten = ten
        self.gia = gia
        self.so_luong = so_luong
        
        # Thuộc tính tính toán
        self.tong_gia_tri = gia * so_luong

# Sử dụng
laptop = SanPham("MacBook", 2000, so_luong=5)
print(laptop.tong_gia_tri)  # 10000

Các Pattern Phổ biến

python
class Config:
    def __init__(self, **kwargs) -> None:
        """Nhận bất kỳ keyword arguments nào."""
        self.debug = kwargs.get("debug", False)
        self.log_level = kwargs.get("log_level", "INFO")
        
        # Lưu tất cả config thêm
        self._extra = kwargs

config = Config(debug=True, custom_setting="value")

Tại sao self?

Python dùng explicit self thay vì implicit this như Java/C#.

python
class Counter:
    def __init__(self) -> None:
        self.dem = 0  # self = instance hiện tại
    
    def tang(self) -> None:
        self.dem += 1  # Phải dùng self để truy cập instance attribute
    
    def lay_gia_tri(self) -> int:
        return self.dem

# Khi gọi method:
counter = Counter()
counter.tang()  # Python tự động truyền `counter` như `self`

# Thực chất là:
Counter.tang(counter)  # Tương đương!

Tại sao Explicit tốt hơn

python
# ✅ TỐT: Rõ ràng biến đến từ đâu
class User:
    def cap_nhat_ten(self, ten_moi: str) -> None:
        self.ten = ten_moi  # Rõ ràng là instance attribute
        bien_tam = "temp"    # Rõ ràng là biến local

# ❌ Kiểu Java: `ten` đến từ đâu?
# public void updateName(String newName) {
#     name = newName;  // Instance? Local? Parameter?
# }

💡 TRIẾT LÝ PYTHON

"Explicit is better than implicit." - Python yêu cầu self để code rõ ràng hơn.


Class Attribute vs Instance Attribute

python
class NhanVien:
    # Class attribute - chia sẻ bởi TẤT CẢ instances
    cong_ty = "PENALGO"
    so_nhan_vien = 0
    
    def __init__(self, ten: str) -> None:
        # Instance attribute - riêng biệt cho MỖI instance
        self.ten = ten
        NhanVien.so_nhan_vien += 1

# Class attributes
print(NhanVien.cong_ty)  # "PENALGO"

# Instance attributes
nv1 = NhanVien("Alice")
nv2 = NhanVien("Bob")

print(nv1.ten)  # "Alice"
print(nv2.ten)  # "Bob"
print(NhanVien.so_nhan_vien)  # 2

Bẫy Mutable Class Attribute ⚠️

python
# ❌ NGUY HIỂM: Mutable class attribute
class TeamXau:
    thanh_vien = []  # CHIA SẺ bởi tất cả instances!
    
    def them_thanh_vien(self, ten: str) -> None:
        self.thanh_vien.append(ten)

team1 = TeamXau()
team2 = TeamXau()
team1.them_thanh_vien("Alice")
print(team2.thanh_vien)  # ["Alice"] - Sao lại thế?!

# ✅ SỬA: Khởi tạo trong __init__
class TeamTot:
    def __init__(self) -> None:
        self.thanh_vien = []  # Riêng biệt cho mỗi instance

OOP Hiện đại: @dataclass

Python 3.7+ có @dataclass - tự động generate code boilerplate.

Trước: Địa ngục Boilerplate

python
# ❌ CÁCH CŨ: Quá nhiều code...
class User:
    def __init__(self, ten: str, email: str, tuoi: int) -> None:
        self.ten = ten
        self.email = email
        self.tuoi = tuoi
    
    def __repr__(self) -> str:
        return f"User(ten={self.ten!r}, email={self.email!r}, tuoi={self.tuoi!r})"
    
    def __eq__(self, other: object) -> bool:
        if not isinstance(other, User):
            return NotImplemented
        return (self.ten, self.email, self.tuoi) == (other.ten, other.email, other.tuoi)

Sau: Phép màu @dataclass

python
from dataclasses import dataclass

# ✅ HIỆN ĐẠI: Một decorator, có tất cả
@dataclass
class User:
    ten: str
    email: str
    tuoi: int

# Tự động có:
# - __init__
# - __repr__
# - __eq__
# - Và nhiều hơn!

user = User("HPN", "HPN@x.com", 28)
print(user)  # User(ten='HPN', email='HPN@x.com', tuoi=28)

Tính năng của Dataclass

python
from dataclasses import dataclass, field
from typing import ClassVar

@dataclass
class SanPham:
    # Trường bắt buộc (không có default)
    ten: str
    gia: float
    
    # Trường tùy chọn (có default)
    so_luong: int = 0
    tags: list[str] = field(default_factory=list)  # Mutable default
    
    # Trường tính toán (không trong __init__)
    tong: float = field(init=False)
    
    # Class variable (không phải instance attribute)
    tien_te: ClassVar[str] = "VND"
    
    def __post_init__(self) -> None:
        """Được gọi sau __init__."""
        self.tong = self.gia * self.so_luong

san_pham = SanPham("Laptop", 1000, so_luong=5)
print(san_pham.tong)  # 5000

Dataclass Bất biến

python
from dataclasses import dataclass

@dataclass(frozen=True)  # Bất biến!
class Diem:
    x: float
    y: float

diem = Diem(10, 20)
diem.x = 30  # ❌ FrozenInstanceError!

Dataclass với Slots (Python 3.10+)

python
@dataclass(slots=True)  # Tiết kiệm bộ nhớ!
class User:
    ten: str
    email: str
    
# Dùng __slots__ bên trong - ít bộ nhớ, truy cập nhanh hơn

Khi nào Dùng gì?

Trường hợpGiải pháp
Container dữ liệu đơn giản@dataclass
Dữ liệu bất biến@dataclass(frozen=True)
Logic khởi tạo phức tạpClass thường với __init__
Cần kế thừaClass thường (kế thừa dataclass phức tạp)
Objects cấu hình@dataclass với defaults

⚠️ QUY TẮC CỦA HPN

Nếu class của bạn chủ yếu là lưu trữ dữ liệu → Dùng @dataclass.
Nếu class có hành vi phức tạp → Dùng class thường.


Bảng Tóm tắt

python
# === CLASS CƠ BẢN ===
class MyClass:
    class_attr = "chia_se"  # Class attribute
    
    def __init__(self, gia_tri):
        self.gia_tri = gia_tri  # Instance attribute
    
    def phuong_thuc(self):
        return self.gia_tri

# === DATACLASS (Python 3.7+) ===
from dataclasses import dataclass, field

@dataclass
class Data:
    bat_buoc: str                              # Bắt buộc
    tuy_chon: int = 0                          # Có default
    mutable: list = field(default_factory=list)  # Mutable default
    tinh_toan: str = field(init=False)         # Không trong __init__
    
    def __post_init__(self):
        self.tinh_toan = self.bat_buoc.upper()

# === FROZEN DATACLASS ===
@dataclass(frozen=True)
class BatBien:
    x: int
    y: int

# === SLOTS DATACLASS (3.10+) ===
@dataclass(slots=True)
class HieuQua:
    ten: str