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ước và quy 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ượng và trá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óadel).__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
.pylà 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_hochoặcfrom 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__.pycó thể để trống hoặc chứa code khởi tạo package.
13. Xử lý File
Các Bước Cơ Bản
- Mở file: Dùng hàm
open(), xác định tên file và chế độ (mode) như'r'(đọc),'w'(ghi),'a'(thêm). - Đọc/Ghi: Dùng các method như
read(),readline(),write(). - Đó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.