Lập Trình Hướng Đối Tượng (OOP) Trong Python

Nội dung bài viết bao gồm các khái niệm cốt lõi và các đặc tính quan trọng của Lập trình hướng đối tượng (OOP) trong Python, với nhiều ví dụ thực tế minh họa.

1. Khái Niệm Cơ Bản Về OOP

Lập trình hướng đối tượng (Object Oriented Programming - OOP) là một phương pháp lập trình tổ chức code xoay quanh các "đối tượng" thay vì các "hàm" và "thủ tục" như trong lập trình hướng thủ tục.

Sự Khác Biệt Giữa Hướng Thủ Tục và Hướng Đối Tượng

  • Lập trình hướng thủ tục: Tập trung vào các bướcquy trình để giải quyết một vấn đề. Code được tổ chức thành các hàm và được gọi tuần tự. Cách này trở nên phức tạp khi dự án lớn.
  • Lập trình hướng đối tượng: Tập trung vào đối tượngtrách nhiệm. Các thuộc tính (dữ liệu) và phương thức (hành vi) liên quan được đóng gói trong một đối tượng. Phương pháp này giúp quản lý các dự án phức tạp một cách có cấu trúc hơn.

2. Lớp (Class) và Đối Tượng (Object)

Lớp và Đối Tượng là gì?

  • Lớp (Class): Là một khuôn mẫu hoặc bản thiết kế trừu tượng dùng để tạo ra các đối tượng. Nó định nghĩa các thuộc tính và phương thức mà các đối tượng sẽ có.
  • Đối tượng (Object): Là một thể hiện cụ thể của một lớp. Nó có trạng thái (giá trị thuộc tính) và hành vi (phương thức) riêng.

Mối Quan Hệ

  • Một lớp có thể có nhiều đối tượng. Mỗi đối tượng là độc lập, có các giá trị thuộc tính riêng.
  • Đối tượng có tất cả các thuộc tính và phương thức được định nghĩa trong lớp của nó.

Ví dụ Thiết kế Lớp "Nguoi" (Person)

Mô tả: Một người có tên, tuổi, chiều cao và các hành động như chạy, ăn.

  • Tên lớp: Nguoi (tuân theo quy tắc PascalCase)
  • Thuộc tính: ten, tuoi, chieu_cao
  • Phương thức: chay(), an()

3. Cú Pháp Cơ Bản về Lớp

Sử dụng Hàm dir()

Hàm dir() cho phép bạn xem tất cả các thuộc tính và phương thức có sẵn của một đối tượng.

Định Nghĩa Lớp Đơn Giản (Chỉ chứa Phương thức)

Cú pháp định nghĩa một lớp chỉ gồm các phương thức:

class TenLop:
    def method1(self, tham_so1):
        pass
    def method2(self, tham_so2):
        pass

Lưu ý: Tham số đầu tiên của phương thức luôn là self, đại diện cho đối tượng đang gọi phương thức.

Ví dụ: Lớp "Meo" (Cat)

class Meo:
    def an(self):
        print("Mèo thích ăn cá!")
    def uong(self):
        print("Mèo đang uống nước...")

meo_cua_toi = Meo()
meo_cua_toi.an()
meo_cua_toi.uong()

Tham số self

Tham số self cho phép phương thức truy cập vào các thuộc tính và phương thức của chính đối tượng. Khi gọi phương thức, Python tự động truyền đối tượng vào tham số này.

Ví dụ về self:

class ConCho:
    def __init__(self, ten):
        self.ten = ten
    def sua(self):
        print(f"{self.ten} đang sủa: Gâu gâu!")

cho_cua_toi = ConCho("Lucky")
cho_cua_toi.sua()

Phương thức Khởi tạo __init__

Phương thức __init__ được gọi tự động khi một đối tượng được tạo từ lớp. Nó thường được dùng để khởi tạo các thuộc tính cho đối tượng.

class SinhVien:
    def __init__(self, ho_ten, ma_so):
        self.hoten = ho_ten
        self.maso = ma_so

sv = SinhVien("Nguyen Van A", "SV001")
print(f"Sinh viên: {sv.hoten}, Mã số: {sv.maso}")

Các Phương thức Đặc biệt Khác

  • __del__(self): Được gọi khi đối tượng bị hủy (ví dụ, khi dùng từ khóa del).
  • __str__(self): Trả về một chuỗi mô tả thân thiện với người dùng khi đối tượng được in ra.
class SanPham:
    def __init__(self, ten, gia):
        self.ten = ten
        self.gia = gia
    def __str__(self):
        return f"Sản phẩm: {self.ten}, Giá: {self.gia} VND"

sp = SanPham("Bút bi", 5000)
print(sp) # Output: Sản phẩm: Bút bi, Giá: 5000 VND

4. Đóng Gói (Encapsulation)

Đóng gói là cơ chế che giấu thông tin, cho phép bạn bảo vệ dữ liệu bên trong đối tượng khỏi sự truy cập từ bên ngoài. Điều này giúp tăng tính toàn vẹn và bảo mật của dữ liệu.

Ví dụ: Lớp "MayTinh" (Computer)

class MayTinh:
    def __init__(self, hang, gia):
        self.hang = hang
        self.gia = gia  # Thuộc tính công khai

my_pc = MayTinh("Dell", 15000000)
print(my_pc.gia)

Ví dụ: Lớp "Nguoi" với thuộc tính "can_nang" (cân nặng)

class Nguoi:
    def __init__(self, ten, can_nang):
        self.ten = ten
        self.can_nang = can_nang
    def chay(self):
        self.can_nang -= 0.5
        print(f"{self.ten} chạy xong, giảm còn {self.can_nang} kg")
    def an(self):
        self.can_nang += 1
        print(f"{self.ten} ăn xong, tăng lên {self.can_nang} kg")

xiaoming = Nguoi("Xiao Ming", 75.0)
xiaoming.chay()
xiaoming.an()

Ví dụ Sắp xếp Đồ đạc trong Nhà

Chúng ta sẽ xây dựng hai lớp: DoDac (Furniture) và Nha (House).

class DoDac:
    def __init__(self, ten, dien_tich):
        self.ten = ten
        self.dien_tich = dien_tich
    def __str__(self):
        return f"{self.ten} (diện tích: {self.dien_tich} m2)"

class Nha:
    def __init__(self, loai, tong_dien_tich):
        self.loai = loai
        self.tong_dien_tich = tong_dien_tich
        self.dien_tich_con_lai = tong_dien_tich
        self.danh_sach_do = []
    def them_do(self, do_dac):
        if do_dac.dien_tich > self.dien_tich_con_lai:
            print(f"Không thể thêm {do_dac.ten} vì quá lớn!")
            return
        self.danh_sach_do.append(do_dac.ten)
        self.dien_tich_con_lai -= do_dac.dien_tich
        print(f"Đã thêm {do_dac.ten} vào nhà. Diện tích còn lại: {self.dien_tich_con_lai}")
    def __str__(self):
        return f"Nhà loại {self.loai} - Tổng: {self.tong_dien_tich}m2, Còn: {self.dien_tich_con_lai}m2, Đồ: {self.danh_sach_do}"

g = DoDac("Giường", 4)
t = DoDac("Tủ", 2)
b = DoDac("Bàn", 1.5)
nha_toi = Nha("Chung cư", 50)
nha_toi.them_do(g)
nha_toi.them_do(t)
nha_toi.them_do(b)
print(nha_toi)

5. Đóng Gói (Phần 2) - Ví dụ "Lính và Súng"

class Sung:
    def __init__(self, model):
        self.model = model
        self.so_dan = 0
    def nap_dan(self, so_luong):
        self.so_dan += so_luong
    def ban(self):
        if self.so_dan == 0:
            print("Hết đạn!")
            return
        self.so_dan -= 1
        print(f"Súng {self.model} bắn! Còn {self.so_dan} viên đạn")

class Linh:
    def __init__(self, ten):
        self.ten = ten
        self.sung = None  # Lính chưa có súng
    def xung_phong(self):
        if self.sung is None:
            print(f"{self.ten} chưa có súng, không thể chiến đấu!")
            return
        print(f"{self.ten} xung phong!")
        self.sung.nap_dan(5)
        self.sung.ban()

ak47 = Sung("AK47")
xu_san_duo = Linh("Xu San Duo")
xu_san_duo.sung = ak47  # Gán súng cho lính
xu_san_duo.xung_phong()

Toán tử Xác định Danh tính (is)

Toán tử is dùng để so sánh xem hai biến có trỏ đến cùng một đối tượng trong bộ nhớ hay không, khác với toán tử == so sánh giá trị.

6. Thuộc tính và Phương thức Riêng tư (Private)

Trong Python, để biểu thị một thuộc tính/phương thức là riêng tư, thêm hai dấu gạch dưới __ (dunder) ở đầu tên. Mục đích là để tránh truy cập từ bên ngoài lớp.

class NguoiLon:
    def __init__(self, ten):
        self.ten = ten
        self.__tuoi = 30  # Thuộc tính riêng tư
    def __noi_chuyen(self): # Phương thức riêng tư
        print(f"{self.ten} đang nói chuyện...")

anh_a = NguoiLon("Anh A")
# print(anh_a.__tuoi) # Lỗi!
# anh_a.__noi_chuyen() # Lỗi!

Lưu ý: Cơ chế này chỉ là "name mangling". Về mặt kỹ thuật, bạn vẫn có thể truy cập vào _NguoiLon__tuoi từ bên ngoài, nhưng điều này không được khuyến khích.

7. Kế thừa (Inheritance)

Kế thừa cho phép một lớp (lớp con) kế thừa các thuộc tính và phương thức của một lớp khác (lớp cha).

Kế thừa Đơn

class DongVat:
    def __init__(self, ten):
        self.ten = ten
    def keu(self):
        print("Âm thanh động vật...")

class ConMeo(DongVat): # ConMeo kế thừa từ DongVat
    def __init__(self, ten, mau_sac):
        super().__init__(ten)  # Gọi phương thức khởi tạo của lớp cha
        self.mau_sac = mau_sac
    def keu(self):  # Ghi đè (override) phương thức keu
        print(f"{self.ten} kêu: Meo meo!")

meo = ConMeo("Muop", "Vang")
meo.keu()

Kế thừa Bội (Multiple Inheritance)

class Cha:
    def chay(self):
        print("Cha chạy nhanh!")

class Me:
    def hat(self):
        print("Mẹ hát hay!")

class Con(Cha, Me): # Con kế thừa từ cả Cha và Me
    pass

con_toi = Con()
con_toi.chay() # Kế thừa từ Cha
con_toi.hat()  # Kế thừa từ Me

Ghi đè (Override) và Mở rộng Phương thức

  • Ghi đè: Lớp con định nghĩa lại phương thức cùng tên với lớp cha.
  • Mở rộng: Lớp con gọi phương thức của lớp cha (dùng super()) và bổ sung thêm logic.

Thuộc tính và Phương thức Riêng tư trong Kế thừa

Lớp con không thể truy cập trực tiếp vào các thuộc tính/phương thức riêng tư của lớp cha. Tuy nhiên, nó có thể truy cập gián tiếp thông qua các phương thức công khai của lớp cha.

8. Đa hình (Polymorphism)

Đa hình cho phép các đối tượng của các lớp khác nhau phản hồi cùng một phương thức theo những cách khác nhau. Điều này giúp code linh hoạt và dễ mở rộng.

class Cho:
    def keu(self):
        print("Gâu gâu!")

class Meo:
    def keu(self):
        print("Meo meo!")

def cho_vat_keu(dong_vat):
    dong_vat.keu()

cho_cho = Cho()
con_meo = Meo()

cho_vat_keu(cho_cho)
cho_vat_keu(con_meo)

9. Thuộc tính và Phương thức của Lớp

Thuộc tính của Lớp (Class Attribute)

Thuộc tính của lớp được chia sẻ bởi tất cả các đối tượng. Nó thường dùng để lưu trữ thông tin chung của lớp.

class MayTinh:
    so_luong_may = 0  # Thuộc tính của lớp
    def __init__(self, ten):
        self.ten = ten
        MayTinh.so_luong_may += 1

may1 = MayTinh("PC A")
may2 = MayTinh("PC B")
print(MayTinh.so_luong_may) # Output: 2

Phương thức của Lớp (Class Method) và Phương thức Tĩnh (Static Method)

  • Phương thức Lớp (@classmethod): Hoạt động trên chính lớp, tham số đầu tiên là cls. Dùng để truy cập hoặc thay đổi thuộc tính của lớp.
  • Phương thức Tĩnh (@staticmethod): Không phụ thuộc vào đối tượng hay lớp. Nó giống như một hàm thông thường nhưng được đặt bên trong lớp để tổ chức code.
class CongCu:
    so_luong = 0
    def __init__(self, ten):
        self.ten = ten
        CongCu.so_luong += 1
    @classmethod
    def hien_thi_so_luong(cls):
        print(f"Tổng số công cụ: {cls.so_luong}")
    @staticmethod
    def tro_giup():
        print("Hướng dẫn sử dụng các công cụ.")

c1 = CongCu("Búa")
c2 = CongCu("Kìm")
CongCu.hien_thi_so_luong()
CongCu.tro_giup()

10. Mẫu Singleton (Một thực thể duy nhất)

Mẫu thiết kế Singleton đảm bảo rằng một lớp chỉ có một phiên bản duy nhất trong suốt vòng đời của chương trình.

class MayNgheNhac:
    _instance = None
    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

player1 = MayNgheNhac()
player2 = MayNgheNhac()
print(player1 is player2) # Output: True (cùng một đối tượng)

11. Xử lý Ngoại lệ (Exception Handling)

Xử lý ngoại lệ giúp chương trình không bị crash khi gặp lỗi.

Cú pháp Cơ bản

try:
    so = int(input("Nhập một số: "))
    print(f"8 / {so} = {8 / so}")
except ValueError:
    print("Lỗi: Vui lòng nhập số nguyên!")
except ZeroDivisionError:
    print("Lỗi: Không thể chia cho 0!")
except Exception as e:
    print(f"Lỗi không xác định: {e}")
else:
    print("Không có lỗi xảy ra.")
finally:
    print("Khối này luôn được thực thi.")

Truyền Ngoại lệ (Raise Exception)

Bạn có thể chủ động tạo và ném ra ngoại lệ bằng từ khóa raise.

def nhap_mat_khau():
    mk = input("Nhập mật khẩu (tối thiểu 8 ký tự): ")
    if len(mk) < 8:
        raise Exception("Mật khẩu quá ngắn!")
    return mk

try:
    mat_khau = nhap_mat_khau()
    print("Mật khẩu hợp lệ.")
except Exception as e:
    print(f"Lỗi: {e}")

12. Module và Package

Module

  • Một file .py là một module. Module dùng để tổ chức code thành các đơn vị riêng biệt.
  • Dùng import để sử dụng module. Ví dụ: import toan_hoc hoặc from toan_hoc import tinh_tong.
  • Để tránh chạy code test khi import, sử dụng if __name__ == "__main__":. Code trong khối này chỉ chạy khi file được thực thi trực tiếp.

Package

  • Package là một thư mục chứa nhiều module và một file đặc biệt __init__.py.
  • File __init__.py có thể để trống hoặc chứa code khởi tạo package.

13. Xử lý File

Các Bước Cơ Bản

  1. Mở file: Dùng hàm open(), xác định tên file và chế độ (mode) như 'r' (đọc), 'w' (ghi), 'a' (thêm).
  2. Đọc/Ghi: Dùng các method như read(), readline(), write().
  3. Đóng file: Dùng method close().

Ví dụ: Đọc và Ghi File

# Ghi file
with open("ghi_chu.txt", "w") as file:
    file.write("Dòng 1: Hello!\n")
    file.write("Dòng 2: Đây là file ghi chú.\n")

# Đọc file
with open("ghi_chu.txt", "r") as file:
    noi_dung = file.read()
    print(noi_dung)

Ví dụ: Sao chép file

with open("nguon.txt", "r") as src, open("dich.txt", "w") as dst:
    for dong in src:
        dst.write(dong)
print("Sao chép thành công!")

14. Hàm eval()

Hàm eval() nhận một chuỗi và thực thi nó như một biểu thức Python.

bieu_thuc = "2 * (3 + 5)"
ket_qua = eval(bieu_thuc)
print(ket_qua) # Output: 16

# Chuyển đổi chuỗi thành list/dict
chuoi_list = "[1, 2, 3]"
chuoi_dict = "{'a': 1, 'b': 2}"
print(eval(chuoi_list))  # [1, 2, 3]
print(eval(chuoi_dict))  # {'a': 1, 'b': 2}

Cảnh báo: Hàm eval() rất nguy hiểm nếu dùng với dữ liệu đầu vào từ người dùng, vì nó có thể thực thi các lệnh độc hại. Tránh sử dụng eval() với dữ liệu không đáng tin cậy.

Thẻ: python oop class object encapsulation

Đăng vào ngày 21 tháng 6 lúc 00:03