Quick Start: Bắt đầu ngay với OOP Python trong 5 phút
Chào anh em, hôm nay mình cùng nhau “mổ xẻ” về Lập trình hướng đối tượng (OOP) trong Python nhé. Nghe có vẻ ‘hàn lâm’ nhưng thực ra nó là một cách tổ chức code rất hiệu quả, giúp project của mình dễ quản lý và mở rộng hơn rất nhiều.
Để bắt đầu nhanh, mình sẽ tạo một class NhanVien (Nhân viên) đơn giản. Class giống như một khuôn mẫu, còn đối tượng (object) là sản phẩm được tạo ra từ khuôn mẫu đó.
class NhanVien:
# Phương thức khởi tạo (constructor)
def __init__(self, ten, ma_so):
self.ten = ten # Thuộc tính tên
self.ma_so = ma_so # Thuộc tính mã số
# Phương thức (method) hiển thị thông tin
def hien_thi_thong_tin(self):
print(f"Tên: {self.ten}, Mã số: {self.ma_so}")
# Tạo đối tượng (object) từ class NhanVien
nhan_vien_1 = NhanVien("Nguyễn Văn A", "NV001")
nhan_vien_2 = NhanVien("Trần Thị B", "NV002")
# Gọi phương thức của đối tượng
nhan_vien_1.hien_thi_thong_tin()
nhan_vien_2.hien_thi_thong_tin()
Chạy đoạn code trên, anh em sẽ thấy output:
Tên: Nguyễn Văn A, Mã số: NV001
Tên: Trần Thị B, Mã số: NV002
Thấy không, chỉ vài dòng code mà mình đã có thể định nghĩa được một “loại” nhân viên và tạo ra các “cá thể” nhân viên khác nhau rồi. Đó chính là ý tưởng cốt lõi của OOP đấy!
Giải thích chi tiết: Các trụ cột của OOP trong Python
Sau màn khởi động nhanh, mình đi sâu hơn một chút nhé. OOP không chỉ là Class và Object. Nó có 4 ‘trụ cột’ chính giúp mình xây dựng code vững chắc:
1. Tính Đóng gói (Encapsulation)
Đóng gói nghĩa là mình gom các thuộc tính (dữ liệu) và phương thức (hành vi) liên quan vào một ‘gói’ (class), và ẩn đi các chi tiết triển khai bên trong. Điều này giúp bảo vệ dữ liệu khỏi bị truy cập hoặc thay đổi từ bên ngoài một cách không mong muốn. Trong Python, mình dùng quy ước đặt tên (leading underscore) để chỉ ra thuộc tính nào nên là ‘private’, dù thực chất Python không có private hoàn toàn như Java hay C++.
class TaiKhoanNganHang:
def __init__(self, ten_chu_tk, so_du_ban_dau):
self.ten_chu_tk = ten_chu_tk
self.__so_du = so_du_ban_dau # Quy ước 'private' bằng hai dấu gạch dưới
def nap_tien(self, so_tien):
if so_tien > 0:
self.__so_du += so_tien
print(f"Nạp thành công {so_tien}. Số dư hiện tại: {self.__so_du}")
else:
print("Số tiền nạp phải lớn hơn 0.")
def rut_tien(self, so_tien):
if 0 < so_tien <= self.__so_du:
self.__so_du -= so_tien
print(f"Rút thành công {so_tien}. Số dư hiện tại: {self.__so_du}")
else:
print("Số tiền rút không hợp lệ hoặc không đủ số dư.")
def xem_so_du(self):
return self.__so_du
tk = TaiKhoanNganHang("Minh Hoang", 1000)
print(f"Số dư ban đầu: {tk.xem_so_du()}")
tk.nap_tien(500)
tk.rut_tien(200)
# Thử truy cập trực tiếp (không khuyến khích)
# print(tk.__so_du) # Sẽ gây lỗi AttributeError
# Thay vào đó, truy cập qua phương thức
print(f"Số dư cuối cùng: {tk.xem_so_du()}")
2. Tính Thừa kế (Inheritance)
Thừa kế cho phép một class (class con hay subclass) kế thừa các thuộc tính và phương thức từ một class khác (class cha hay superclass). Điều này giúp mình tái sử dụng code, tránh viết lại những phần giống nhau.
class HinhDang:
def __init__(self, ten):
self.ten = ten
def mo_ta(self):
return f"Đây là một hình dạng có tên: {self.ten}"
class HinhTron(HinhDang):
def __init__(self, ten, ban_kinh):
super().__init__(ten) # Gọi constructor của class cha
self.ban_kinh = ban_kinh
def dien_tich(self):
return 3.14 * self.ban_kinh ** 2
class HinhVuong(HinhDang):
def __init__(self, ten, canh):
super().__init__(ten)
self.canh = canh
def dien_tich(self):
return self.canh ** 2
hinh_tron = HinhTron("Vòng tròn lớn", 7)
hinh_vuong = HinhVuong("Ô vuông nhỏ", 5)
print(hinh_tron.mo_ta()) # Phương thức từ class cha
print(f"Diện tích hình tròn: {hinh_tron.dien_tich()}")
print(hinh_vuong.mo_ta())
print(f"Diện tích hình vuông: {hinh_vuong.dien_tich()}")
3. Tính Đa hình (Polymorphism)
Đa hình nghĩa là các đối tượng thuộc các class khác nhau có thể phản ứng khác nhau với cùng một thông điệp (cùng tên phương thức). Ví dụ điển hình là việc override (ghi đè) phương thức từ class cha.
class ConVat:
def noi(self):
pass # Phương thức rỗng, sẽ được ghi đè
class Cho(ConVat):
def noi(self):
return "Gâu gâu!"
class Meo(ConVat):
def noi(self):
return "Meo meo!"
class Vit(ConVat):
def noi(self):
return "Cạp cạp!"
# Một hàm có thể xử lý nhiều loại đối tượng khác nhau
def tieng_keu(con_vat):
print(con_vat.noi())
cho = Cho()
meo = Meo()
vit = Vit()
tieng_keu(cho)
tieng_keu(meo)
tieng_keu(vit)
Output sẽ là:
Gâu gâu!
Meo meo!
Cạp cạp!
Mặc dù cùng gọi phương thức noi() nhưng mỗi đối tượng lại có hành vi khác nhau.
4. Tính Trừu tượng (Abstraction)
Trừu tượng hóa tập trung vào việc hiển thị những thông tin cần thiết và ẩn đi các chi tiết phức tạp. Trong Python, mình thường đạt được tính trừu tượng thông qua abstract classes (lớp trừu tượng) và abstract methods (phương thức trừu tượng) bằng cách sử dụng module abc.
from abc import ABC, abstractmethod
class PhuongTien(ABC):
@abstractmethod
def di_chuyen(self):
pass
@abstractmethod
def dung_lai(self):
pass
class XeOto(PhuongTien):
def di_chuyen(self):
print("Xe ô tô đang lăn bánh.")
def dung_lai(self):
print("Xe ô tô đã dừng.")
class XeMay(PhuongTien):
def di_chuyen(self):
print("Xe máy đang phóng bạt mạng.")
def dung_lai(self):
print("Xe máy đã dừng khẩn cấp.")
# p = PhuongTien() # Sẽ báo lỗi vì không thể tạo đối tượng từ Abstract Class
xemay = XeMay()
xemay.di_chuyen()
xemay.dung_lai()
xeto = XeOto()
xeto.di_chuyen()
xeto.dung_lai()
Nâng cao: Những kỹ thuật hữu ích trong OOP Python
Khi đã nắm vững các trụ cột, mình có thể khám phá thêm vài ‘chiêu’ nâng cao để code gọn gàng và mạnh mẽ hơn.
1. Decorator @property
Decorator @property cho phép mình truy cập phương thức như một thuộc tính, giúp kiểm soát việc đọc/ghi dữ liệu của các thuộc tính mà không làm thay đổi cách thức truy cập từ bên ngoài.
class HocSinh:
def __init__(self, ten, diem):
self._ten = ten # Quy ước 'protected'
self._diem = diem
@property
def diem(self):
return self._diem
@diem.setter
def diem(self, new_diem):
if 0 <= new_diem <= 10:
self._diem = new_diem
else:
print("Điểm phải nằm trong khoảng từ 0 đến 10.")
hs = HocSinh("Ngọc Anh", 8)
print(f"Điểm của {hs._ten}: {hs.diem}") # Truy cập như thuộc tính
hs.diem = 9.5 # Gán giá trị như thuộc tính
print(f"Điểm mới của {hs._ten}: {hs.diem}")
hs.diem = 12 # Sẽ in ra cảnh báo
2. Class Method và Static Method
- Class Method (
@classmethod): Phương thức này hoạt động trên class chứ không phải instance của class. Nó nhậncls(thay vìself) làm đối số đầu tiên, trỏ đến chính class đó. Thường dùng để tạo các ‘alternative constructor’. - Static Method (
@staticmethod): Phương thức này không nhậnselfhaycls. Nó giống như một hàm thông thường nhưng được đặt trong class vì nó có liên quan logic đến class đó, không cần truy cập thuộc tính hay phương thức của instance hay class.
class HinhHoc:
PI = 3.14159
def __init__(self, ten):
self.ten = ten
@classmethod
def tao_hinh_vuong_tu_chu_vi(cls, chu_vi):
canh = chu_vi / 4
return cls(f"Hình vuông chu vi {chu_vi}", canh)
@staticmethod
def kiem_tra_so_duong(so):
return so > 0
hv = HinhHoc.tao_hinh_vuong_tu_chu_vi(20)
print(f"Tên hình: {hv.ten}") # hv.canh sẽ bị lỗi vì chỉ có ở HinhVuong nếu mình kế thừa và thêm thuộc tính
print(f"Kiểm tra 5 là số dương: {HinhHoc.kiem_tra_so_duong(5)}")
print(f"Kiểm tra -3 là số dương: {HinhHoc.kiem_tra_so_duong(-3)}")
Để code ví dụ trên rõ ràng hơn cho classmethod, anh em có thể hình dung nó sẽ phát huy tác dụng tốt hơn khi có kế thừa và cần tạo instance của class con từ một ‘constructor’ khác.
Tips thực tế: Áp dụng OOP hiệu quả trong Python
OOP là một công cụ mạnh, nhưng quan trọng là mình phải biết khi nào dùng nó và dùng như thế nào cho hiệu quả.
1. Khi nào thì dùng OOP?
- Khi mình có các thực thể (entities) trong hệ thống có thuộc tính và hành vi rõ ràng (ví dụ: Người dùng, Sản phẩm, Đơn hàng).
- Khi muốn tái sử dụng code và dễ dàng mở rộng chức năng (thừa kế, đa hình).
- Khi cần quản lý độ phức tạp của một dự án lớn, chia nhỏ thành các module logic hơn.
2. Kinh nghiệm cá nhân khi làm việc với Python
Trong quá trình làm việc, mình thấy Python rất linh hoạt. Có những lúc mình cần xử lý chuỗi hay các pattern đặc biệt. Khi cần test nhanh regex pattern trước khi đưa vào code Python, mình hay dùng regex tester tại toolcraft.app/vi/tools/developer/regex-tester — chạy ngay trên trình duyệt, không cần cài gì. Điều này giúp mình tiết kiệm kha khá thời gian debug những lỗi nhỏ nhặt liên quan đến regex mà không cần phải chạy lại cả script Python chỉ để kiểm tra một cái pattern.
3. Đừng lạm dụng OOP!
Không phải cứ project nào cũng cần OOP phức tạp. Với những script nhỏ, đơn giản chỉ thực hiện một vài tác vụ tuần tự, việc áp dụng OOP có thể làm code trở nên rườm rà không cần thiết. Hãy chọn giải pháp phù hợp với quy mô và yêu cầu của bài toán.
4. Code tổ chức rõ ràng
Dù dùng OOP hay functional programming, việc đặt tên class, phương thức, biến sao cho dễ hiểu, dễ đọc là cực kỳ quan trọng. Mình luôn cố gắng tuân thủ PEP 8, không chỉ cho riêng mình mà còn cho cả team khi review code.
Kết luận
Lập trình hướng đối tượng trong Python là một kỹ năng nền tảng giúp anh em xây dựng các ứng dụng lớn, có cấu trúc tốt và dễ bảo trì. Qua bài viết này, mình hy vọng anh em đã có cái nhìn tổng quan và thực hành được những khái niệm cơ bản đến nâng cao của OOP. Cứ thực hành nhiều, đọc code của người khác, và tự tay viết thật nhiều code là sẽ ‘ngấm’ thôi. Chúc anh em code thật vui!

