Tìm hiểu OOP trong Python: Cách khai báo class, tạo object, kế thừa và best practices

Giới thiệu lập trình hướng đối tượng trong Python

Bạn đã từng nghe về OOP (Object-Oriented Programming) nhưng vẫn thấy mơ hồ về khái niệm này? Hay bạn đang tự hỏi tại sao nhiều lập trình viên lại ưa chuộng phong cách lập trình này đến vậy?

Lập trình hướng đối tượng không chỉ là một khái niệm mà còn là một triết lý tổ chức code giúp dự án của bạn trở nên rõ ràng, dễ bảo trì và có thể mở rộng một cách hiệu quả. Thay vì viết code theo kiểu tuần tự truyền thống, OOP giúp bạn tạo ra những “khối xây dựng” có thể tái sử dụng và kết hợp linh hoạt.

Hình minh họa

Trong bài viết này, tôi sẽ đưa bạn đi từ những khái niệm cơ bản nhất của OOP trong Python đến các kỹ thuật nâng cao, kèm theo những ví dụ thực tế mà bạn có thể áp dụng ngay vào dự án của mình. Chúng ta sẽ khám phá cách khai báo class, tạo object, hiểu về tính kế thừa, đa hình, các hàm đặc biệt và những best practices mà mọi lập trình viên Python nên nắm vững.

Hãy cùng bắt đầu hành trình khám phá thế giới OOP trong Python – một công cụ mạnh mẽ sẽ thay đổi cách bạn nghĩ và viết code!

Khái niệm cơ bản về OOP trong Python

Class và Object là gì?

Để hiểu OOP, bạn cần nắm vững hai khái niệm cốt lõi: Class và Object. Hãy tưởng tượng Class như một bản thiết kế, còn Object là những sản phẩm được tạo ra từ bản thiết kế đó.

Ví dụ, bạn có thể tạo một class XeHoi như một mẫu thiết kế chung. Từ class này, bạn có thể tạo ra nhiều object khác nhau như xe_toyota, xe_honda, mỗi object sẽ có những đặc điểm riêng nhưng vẫn tuân theo cấu trúc chung của class.

Hình minh họa

class XeHoi:
    def __init__(self, hang, mau):
        self.hang = hang
        self.mau = mau
    
    def chay(self):
        return f"Xe {self.hang} màu {self.mau} đang chạy"

# Tạo object từ class
xe_toyota = XeHoi("Toyota", "đỏ")
xe_honda = XeHoi("Honda", "xanh")

Kiểu dữ liệu trong Python đóng vai trò quan trọng trong việc xác định các thuộc tính như hangmau của object trong class.

Thuộc tính và phương thức

Trong OOP, thuộc tính (attributes) là những biến lưu trữ dữ liệu của object, còn phương thức (methods) là những hàm xử lý logic bên trong class. Điều này giúp bạn đóng gói dữ liệu và hành vi liên quan vào cùng một nơi.

Thuộc tính giống như những đặc điểm của object – màu sắc, kích thước, tên gọi. Phương thức giống như những hành động mà object có thể thực hiện – di chuyển, tính toán, hiển thị thông tin.

class NguoiDung:
    def __init__(self, ten, tuoi):
        self.ten = ten  # Thuộc tính
        self.tuoi = tuoi  # Thuộc tính
    
    def chao_hoi(self):  # Phương thức
        return f"Xin chào, tôi là {self.ten}"
    
    def tang_tuoi(self):  # Phương thức
        self.tuoi += 1
        return f"{self.ten} bây giờ {self.tuoi} tuổi"

Cách tiếp cận này giúp code của bạn trở nên logic và dễ hiểu hơn. Thay vì có nhiều hàm rời rạc, mọi thứ liên quan đều được tập trung vào class tương ứng.

Hướng dẫn tạo và sử dụng Class & Object trong Python

Viết class cơ bản và khởi tạo object

Việc tạo class trong Python rất đơn giản với từ khóa class. Hàm __init__ đóng vai trò như constructor, giúp khởi tạo các thuộc tính ban đầu cho object.

Hình minh họa

class HocSinh:
    def __init__(self, ten, lop, diem=[]):
        self.ten = ten
        self.lop = lop
        self.diem = diem if diem else []
    
    def them_diem(self, diem_moi):
        self.diem.append(diem_moi)
        print(f"Đã thêm điểm {diem_moi} cho {self.ten}")
    
    def tinh_diem_trung_binh(self):
        if len(self.diem) == 0:
            return 0
        return sum(self.diem) / len(self.diem)

# Khởi tạo object
hoc_sinh1 = HocSinh("Nguyễn Văn An", "10A1")
hoc_sinh2 = HocSinh("Trần Thị Bình", "10A2", [8, 7, 9])

Khi tạo object, Python sẽ tự động gọi hàm __init__ và truyền các tham số bạn cung cấp. Mỗi object được tạo sẽ có vùng nhớ riêng, do đó việc thay đổi thuộc tính của object này không ảnh hưởng đến object khác.

Sử dụng thuộc tính và phương thức trong thực tế

Sau khi tạo object, bạn có thể truy cập thuộc tính bằng dấu chấm và gọi phương thức để thực hiện các thao tác cần thiết. Đây là lúc sức mạnh của OOP thực sự thể hiện.

# Thao tác với object hoc_sinh1
print(f"Tên: {hoc_sinh1.ten}")
print(f"Lớp: {hoc_sinh1.lop}")

hoc_sinh1.them_diem(8.5)
hoc_sinh1.them_diem(7.0)
hoc_sinh1.them_diem(9.0)

diem_tb = hoc_sinh1.tinh_diem_trung_binh()
print(f"Điểm trung bình của {hoc_sinh1.ten}: {diem_tb:.2f}")

# Thao tác với object hoc_sinh2
diem_tb2 = hoc_sinh2.tinh_diem_trung_binh()
print(f"Điểm trung bình của {hoc_sinh2.ten}: {diem_tb2:.2f}")

Hình minh họa

Bạn có thể thấy mỗi object hoạt động độc lập, có dữ liệu riêng nhưng vẫn sử dụng chung các phương thức được định nghĩa trong class. Điều này giúp tiết kiệm bộ nhớ và tạo ra code có tính nhất quán cao.

Các đặc tính nâng cao trong OOP Python

Kế thừa – Tái sử dụng và mở rộng class

Kế thừa (inheritance) là một trong những tính năng mạnh mẽ nhất của OOP, cho phép bạn tạo class mới dựa trên class đã có. Class con sẽ thừa hưởng tất cả thuộc tính và phương thức của class cha, đồng thời có thể thêm tính năng riêng hoặc override các phương thức có sẵn.

Hình minh họa

class ConNguoi:
    def __init__(self, ten, tuoi):
        self.ten = ten
        self.tuoi = tuoi
    
    def gioi_thieu(self):
        return f"Tôi là {self.ten}, {self.tuoi} tuổi"
    
    def di_chuyen(self):
        return f"{self.ten} đang đi bộ"

class HocSinh(ConNguoi):  # Kế thừa từ ConNguoi
    def __init__(self, ten, tuoi, truong):
        super().__init__(ten, tuoi)  # Gọi constructor của class cha
        self.truong = truong
    
    def gioi_thieu(self):  # Override phương thức của class cha
        return f"Tôi là {self.ten}, {self.tuoi} tuổi, học tại {self.truong}"
    
    def hoc_bai(self):  # Phương thức riêng của class con
        return f"{self.ten} đang học bài"

class GiaoVien(ConNguoi):
    def __init__(self, ten, tuoi, mon_day):
        super().__init__(ten, tuoi)
        self.mon_day = mon_day
    
    def day_hoc(self):
        return f"{self.ten} đang dạy môn {self.mon_day}"

Kế thừa giúp bạn tránh lặp lại code và tạo ra cấu trúc phân cấp logic trong dự án. Khi cần thay đổi logic chung, bạn chỉ cần sửa ở class cha, tất cả class con sẽ được cập nhật tự động.

Vòng lặp trong Python cũng có thể kết hợp linh hoạt với các cấu trúc OOP để duyệt và xử lý dữ liệu trong object.

Đa hình và đóng gói

Đa hình (polymorphism) cho phép nhiều class khác nhau có thể có phương thức cùng tên nhưng hoạt động khác nhau. Điều này tạo ra tính linh hoạt cao trong thiết kế chương trình.

class DongVat:
    def phat_ra_tieng_keu(self):
        pass

class Cho(DongVat):
    def phat_ra_tieng_keu(self):
        return "Gâu gâu"

class Meo(DongVat):
    def phat_ra_tieng_keu(self):
        return "Meo meo"

class Bo(DongVat):
    def phat_ra_tieng_keu(self):
        return "Ò ò"

# Sử dụng đa hình
dong_vat_list = [Cho(), Meo(), Bo()]
for dong_vat in dong_vat_list:
    print(dong_vat.phat_ra_tieng_keu())

Đóng gói (encapsulation) giúp bạn bảo vệ dữ liệu bằng cách sử dụng các biến private (bắt đầu bằng dấu gạch dưới). Mặc dù Python không có từ khóa private thực sự, quy ước này giúp báo hiệu rằng thuộc tính không nên được truy cập trực tiếp từ bên ngoài.

Hình minh họa

Các hàm đặc biệt và cách sử dụng trong Python

Các hàm khởi tạo và đại diện (__init__, __str__)

Python cung cấp nhiều hàm đặc biệt (magic methods) giúp bạn tùy chỉnh hành vi của class. Hai hàm quan trọng nhất mà bạn cần biết là __init____str__.

Hàm __init__ được gọi tự động khi tạo object, giúp khởi tạo các thuộc tính ban đầu. Hàm __str__ định nghĩa cách hiển thị object dưới dạng chuỗi khi sử dụng hàm print().

class SanPham:
    def __init__(self, ten, gia, so_luong):
        self.ten = ten
        self.gia = gia
        self.so_luong = so_luong
    
    def __str__(self):
        return f"Sản phẩm: {self.ten} - Giá: {self.gia}đ - Số lượng: {self.so_luong}"
    
    def tinh_tong_gia_tri(self):
        return self.gia * self.so_luong

# Sử dụng
san_pham = SanPham("Laptop Dell", 15000000, 3)
print(san_pham)  # Tự động gọi __str__
print(f"Tổng giá trị: {san_pham.tinh_tong_gia_tri():,}đ")

Các hàm magic phổ biến khác cần biết

Bên cạnh __init____str__, còn có nhiều hàm magic khác giúp bạn tùy chỉnh hành vi của class một cách chi tiết. Hàm __repr__ tương tự __str__ nhưng thường được sử dụng cho mục đích debug. Hàm __del__ được gọi khi object bị hủy. Hàm __eq__ định nghĩa cách so sánh hai object.

Hình minh họa

class TheTinDung:
    def __init__(self, chu_the, so_tai_khoan, so_du=0):
        self.chu_the = chu_the
        self.so_tai_khoan = so_tai_khoan
        self.so_du = so_du
    
    def __str__(self):
        return f"Thẻ của {self.chu_the} - Số dư: {self.so_du:,}đ"
    
    def __repr__(self):
        return f"TheTinDung('{self.chu_the}', '{self.so_tai_khoan}', {self.so_du})"
    
    def __eq__(self, other):
        if isinstance(other, TheTinDung):
            return self.so_tai_khoan == other.so_tai_khoan
        return False
    
    def __del__(self):
        print(f"Thẻ tín dụng của {self.chu_the} đã được hủy")

# Sử dụng các magic methods
the1 = TheTinDung("Nguyễn Văn A", "123456789", 1000000)
the2 = TheTinDung("Trần Thị B", "123456789", 500000)

print(the1)  # Gọi __str__
print(repr(the1))  # Gọi __repr__
print(the1 == the2)  # Gọi __eq__

Các vấn đề thường gặp khi học OOP Python

Lỗi hay gặp khi khai báo class và tạo object

Một trong những lỗi phổ biến nhất mà các lập trình viên mới gặp phải là quên sử dụng self trong các phương thức của class. Tham số self là cách Python xác định object nào đang được thao tác trong phương thức.

# SAI - Quên self
class HinhChuNhat:
    def __init__(dai, rong):  # Thiếu self
        dai = dai  # Không gán được vào thuộc tính
        rong = rong

# ĐÚNG - Có self
class HinhChuNhat:
    def __init__(self, self, dai, rong):
        self.dai = dai
        self.rong = rong
    
    def tinh_dien_tich(self):  # Phải có self
        return self.dai * self.rong

Hình minh họa

Một lỗi khác là nhầm lẫn giữa thuộc tính class và thuộc tính instance. Thuộc tính class được chia sẻ giữa tất cả object, còn thuộc tính instance thì mỗi object có một bản riêng.

class DemSoLuong:
    so_luong_chung = 0  # Thuộc tính class
    
    def __init__(self, ten):
        self.ten = ten  # Thuộc tính instance
        DemSoLuong.so_luong_chung += 1

# Mỗi object có tên riêng nhưng cùng chia sẻ số lượng chung
obj1 = DemSoLuong("Object 1")
obj2 = DemSoLuong("Object 2")
print(f"Số lượng object: {DemSoLuong.so_luong_chung}")  # Kết quả: 2

Khó hiểu tính kế thừa và đa hình

Nhiều người gặp khó khăn khi hiểu cách thức hoạt động của kế thừa, đặc biệt là việc sử dụng super() và method resolution order (MRO). Hãy nhớ rằng super() giúp bạn gọi phương thức của class cha mà không cần chỉ định tên cụ thể.

class Xe:
    def __init__(self, hang):
        self.hang = hang
        print(f"Khởi tạo xe {hang}")

class XeOto(Xe):
    def __init__(self, hang, so_cho_ngoi):
        super().__init__(hang)  # Gọi constructor của class Xe
        self.so_cho_ngoi = so_cho_ngoi
        print(f"Xe oto {hang} có {so_cho_ngoi} chỗ ngồi")

class XeDien(XeOto):  # Kế thừa nhiều cấp
    def __init__(self, hang, so_cho_ngoi, dung_luong_pin):
        super().__init__(hang, so_cho_ngoi)
        self.dung_luong_pin = dung_luong_pin
        print(f"Pin có dung lượng {dung_luong_pin} kWh")

# Tạo object sẽ gọi tất cả constructor theo thứ tự
xe_dien = XeDien("Tesla", 5, 100)

Best practices khi lập trình hướng đối tượng trong Python

Khi làm việc với OOP trong Python, việc tuân thủ các best practices sẽ giúp code của bạn trở nên chuyên nghiệp và dễ bảo trì hơn. Đầu tiên, luôn nhớ sử dụng self một cách rõ ràng trong mọi phương thức instance. Điều này không chỉ là yêu cầu về cú pháp mà còn giúp code dễ đọc hơn.

Hình minh họa

Việc đặt tên class, method và thuộc tính cần tuân thủ chuẩn PEP8. Class nên sử dụng CapitalizedWords (PascalCase), trong khi method và thuộc tính nên dùng lowercase_with_underscore. Điều này giúp code nhất quán và dễ đọc cho mọi lập trình viên Python.

# Đặt tên chuẩn PEP8
class QuanLyNhanVien:  # Class dùng PascalCase
    def __init__(self, ten_nhan_vien, luong_co_ban):  # Method/thuộc tính dùng snake_case
        self.ten_nhan_vien = ten_nhan_vien
        self.luong_co_ban = luong_co_ban
    
    def tinh_luong_thang(self, he_so_thuong=1.0):
        return self.luong_co_ban * he_so_thuong

Tránh lạm dụng kế thừa quá sâu vì điều này có thể làm cho code trở nên khó hiểu và khó bảo trì. Thường thì cấu trúc kế thừa không nên quá 3-4 cấp độ. Thay vào đó, hãy sử dụng composition (tổng hợp) khi có thể.

Sử dụng encapsulation để bảo vệ dữ liệu quan trọng bằng cách đặt tên thuộc tính bắt đầu với dấu gạch dưới. Mặc dù Python không có private thực sự, quy ước này giúp báo hiệu rằng thuộc tính không nên được truy cập trực tiếp từ bên ngoài.

Cuối cùng, luôn viết code theo nguyên tắc modular, tách biệt trách nhiệm rõ ràng. Mỗi class nên có một mục đích cụ thể, mỗi method nên thực hiện một nhiệm vụ duy nhất. Điều này giúp code dễ test, dễ debug và dễ mở rộng.

Hình minh họa

Kết luận

OOP trong Python thực sự là một công cụ mạnh mẽ giúp bạn tổ chức và quản lý code một cách hiệu quả, tạo ra những dự án có thể mở rộng và bảo trì dễ dàng. Qua bài viết này, chúng ta đã cùng khám phá từ những khái niệm cơ bản như class và object, đến những kỹ thuật nâng cao như kế thừa, đa hình và các hàm đặc biệt.

Việc áp dụng đúng các nguyên tắc OOP không chỉ giúp code của bạn trở nên chuyên nghiệp mà còn tạo ra nền tảng vững chắc cho những dự án lớn hơn trong tương lai. Hãy nhớ rằng OOP không phải là mục tiêu cuối cùng mà là phương tiện giúp bạn giải quyết vấn đề một cách tốt hơn.

Hình minh họa

Bây giờ là lúc để bạn áp dụng những kiến thức đã học vào thực tế. Hãy bắt đầu với những dự án nhỏ, luyện tập tạo class và object, thử nghiệm với kế thừa và các tính năng nâng cao khác. Qua mỗi dự án, bạn sẽ hiểu sâu hơn về sức mạnh của OOP và cách áp dụng nó một cách hiệu quả.

Đừng ngần ngại tham gia các cộng đồng lập trình viên Python, đặt câu hỏi và chia sẻ kinh nghiệm. Việc học hỏi từ những lập trình viên khác sẽ giúp bạn nâng cao kỹ năng nhanh chóng và tránh được những sai lầm phổ biến. Chúc bạn thành công trên con đường chinh phục OOP trong Python!

Ứng dụng của Python được phát triển rộng rãi trong các lĩnh vực thực tế, giúp bạn thấy rõ lợi ích khi nắm vững lập trình hướng đối tượng.

Chia sẻ Tài liệu học Python

Đánh giá
Tác giả

Mạnh Đức

Có cao nhân từng nói rằng: "Kiến thức trên thế giới này đầy rẫy trên internet. Tôi chỉ là người lao công cần mẫn đem nó tới cho người cần mà thôi !"

Chia sẻ
Bài viết liên quan