Bài 32. Lập trình hướng đối tượng

Python Object Oriented Programming

Lập trình hướng đối tượng (Object-oriented programming, viết tắt: OOP) là một kỹ thuật hỗ trợ, cho phép lập trình viên trực tiếp làm việc với các đối tượng mà họ định nghĩa lên. Hiệu quả của kĩ thuật này giúp tăng năng suất, đơn giản hoá độ phức tạp khi bảo trì cũng như mở rộng phần mềm.

Khái niệm về OOP trong Python tập trung vào việc tạo code sử dụng lại. Khái niệm này còn được gọi là DRY (Don't Repeat Yourself). OOP trong Python tuân theo một số nguyên lý cơ bản là tính đóng gói, tính kế thừa và tính đa hình.

  • Tính kế thừa (Inheritance): cho phép một lớp (class) có thể kế thừa các thuộc tính và phương thức từ các lớp khác đã được định nghĩa.

  • Tính đóng gói (Encapsulation): là quy tắc yêu cầu trạng thái bên trong của một đối tượng được bảo vệ và tránh truy cập được từ code bên ngoài (tức là code bên ngoài không thể trực tiếp nhìn thấy và thay đổi trạng thái của đối tượng đó).

  • Tính đa hình (Polymorphism): là khái niệm mà hai hoặc nhiều lớp có những phương thức giống nhau nhưng có thể thực thi theo những cách thức khác nhau.

Lớp (Class) và Đối tượng (Object)

Class và Object là hai khái niệm cơ bản trong lập trình hướng đối tượng.

Đối tượng (Object) là những thực thể tồn tại có hành vi.

Ví dụ đối tượng là một con chim vẹt có tên, màu sắc, trọng lượng, hành vi (tập nói, ăn, bay, nhảy...)...

Lớp (Class) là một kiểu dữ liệu đặc biệt do người dùng định nghĩa, tập hợp nhiều thuộc tính đặc trưng cho mọi đối tượng được tạo ra từ lớp đó.

Thuộc tính là các giá trị của lớp. Sau này khi các đối tượng được tạo ra từ lớp, thì thuộc tính của lớp lúc này sẽ trở thành các đặc điểm của đối tượng đó.

Lớp (Class)

Ta có thể hình dung class giống như là một bản mẫu (template), một khuôn mẫu. Ở đó ta khai báo các thuộc tính (attribute) và phương thức (method) nhằm miêu tả để từ đó ta tạo ra được những object (đối tượng).

Cú pháp để tạo một lớp

class <tên_lớp>:
    code thuộc tính và phương thức

Ví dụ:

class ConVet:
    pass

Thông thường (và theo chuẩn format code Python PEP8) thì tên lớp được viết hoa chữ đầu từ (CapWord).

Đối tượng (Object)

Một đối tượng (còn gọi là instance) là một khởi tạo của một lớp.

Khi lớp được định nghĩa, chỉ có mô tả cho đối tượng được định nghĩa. Do đó, không có bộ nhớ hoặc bộ nhớ nào được cấp phát. Khi khai báo đối tượng bằng việc gán cho một class thì lúc này bộ nhớ được cấp phát cho đối tượng.

Ví dụ

v1 = ConVet()

Ví dụ 1. Tạo class và object trong Python

class ConVet:
    loai = "chim"

    def __init__(self, name, age):
        self.name = name
        self.age = age

em = ConVet("Vẹt Em", 10)
anh = ConVet("Vẹt Anh", 15)

# truy xuất thuộc tính class
print(f'Vẹt thuộc loài {anh.__class__.loai}')

# truy xuất thuộc tính đối tượng
print(f"{anh.name} {anh.age} năm tuổi")
print(f"{em.name} {em.age} năm tuổi")

Output:

Vẹt thuộc loài chim
Vẹt Anh 15 năm tuổi
Vẹt Em 10 năm tuổi

Trong chương trình trên, ta đã tạo một class với tên ConVet và khai báo các thuộc tính (đặc tính của một đối tượng).

Các thuộc tính được định nghĩa bên trong phương thức __init__ của lớp. Đây là phương thức khởi tạo (constructor) được gọi đầu tiên ngay sau khi đối tượng được tạo.

Trong chương trình, ta tạo 2 đối tượng (thể hiện) của lớp ConVet gồm ble và woo.

Ta gọi thuộc tính class bằng cú pháp: __class__.<tên thuộc tính lớp> (vd. __class__.loai); gọi thuộc tính đối tượng với cú pháp: <tên đối tượng>.<tên thuộc tính> (vd: blu.name, blu.age).

Phương thức (Methods)

Các phương thức là các hàm được định nghĩa bên trong phần thân của một lớp. Chúng được sử dụng để xác định các hành vi của một đối tượng.

Ví dụ

class ConVet:
	# thuộc tính
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    # phương thức
    def sing(self, song):
        return f"{self.name} hát bài {song}"

    def dance(self):
        return f"{self.name} đang nhảy múa"

# khởi tạo đối tượng
em = ConVet("Vẹt em", 10)

# gọi phương thức
print(em.sing("AAAAAAAAAAAA"))
print(em.dance())

Output:

Vẹt em hát bài AAAAAAAAAAAA
Vẹt em đang nhảy múa

Kế thừa (Inheritance)

Kế thừa là một cách tạo một lớp mới để sử dụng các chi tiết của một lớp hiện có mà không cần sửa đổi nó. Lớp mới được hình thành là một lớp dẫn xuất (hoặc lớp con). Tương tự, lớp hiện có là một lớp cơ sở (hoặc lớp cha).

Ví dụ

# parent class (lớp cha)
class Chim:
    
    def __init__(self):
        print("Chim được khởi tạo")

    def boi(self):
        print("Bơi nhanh ")

    def bay(self):
        print("Chim hầu hết là biết bay")    

    def noio(self):
    	print('Hoang dã')

# child class (lớp con)
class CanhCut(Chim):

    def __init__(self):
        # gọi hàm super()
        super().__init__()
        print("Cánh cụt được khởi tạo")

    def chay(self):
        print("Chạy nhanh ")

    def bay(self):
        print("Chim cánh cụt thì không biết bay nhé")        

chimA = Chim()
chimA.bay()
chimA.boi()
print()

chimZ = CanhCut()
chimZ.boi()
chimZ.chay()
chimZ.noio()
chimZ.bay()

Output:

Chim được khởi tạo
Chim hầu hết là biết bay
Bơi nhanh 

Chim được khởi tạo
Cánh cụt được khởi tạo
Bơi nhanh 
Chạy nhanh 
Hoang dã
Chim cánh cụt thì không biết bay nhé

Trong ví dụ trên ta dễ thấy lớp CanhCut thừa kế từ lớp Chim. Qua đó, đối tượng chimZ có thể gọi phương tức boi() và noio() trong khi lớp CanhCut không hề khai báo.

Đóng gói (Encapsulation)

Ta có thể hạn chế quyền truy cập vào các phương thức (methods) và biến (variables) trong class. Điều này ngăn dữ liệu khỏi bị sửa đổi trực tiếp và được gọi là 'đóng gói'. Trong Python, ta biểu thị các thuộc tính riêng (private attributes) bằng cách sử dụng dấu 2 dấu gạch dưới làm tiền tố " __ ".

Ví dụ 4. Đóng gói dữ diệu

class Computer:

    def __init__(self):
        self.__giabaogia = 900

    def dinhgia(self, price):
        self.__giabaogia = price        

    def baogia(self):
        print(f"Giá bán: {self.__giabaogia}")


c = Computer()
c.baogia()

# thử thay đổi giá 
c.__giabaogia = 2000
c.baogia()

# đổi giá bằng phương thức
c.dinhgia(1000)
c.baogia()

Output:

Giá bán: 900
Giá bán: 900
Giá bán: 1000

Tính đa hình (Polymorphism)

Đa hình là một khả năng (trong OOP) sử dụng một giao diện chung cho nhiều biểu mẫu (kiểu dữ liệu).

Giả sử, chúng ta cần tô màu cho một hình dạng, có nhiều lựa chọn hình dạng (hình chữ nhật, hình vuông, hình tròn). Tuy nhiên, chúng ta có thể sử dụng cùng một phương pháp để tô màu bất kỳ hình dạng nào. Khái niệm này được gọi là Đa hình.

Ví dụ 5. Sử dụng tính đa hành

class Vet:

    def fly(self):
        print("Vẹt có thể bay")
    
    def swim(self):
        print("Vẹt không biết bơi")

class Canhcut:

    def fly(self):
        print("Chim cánh cụt không thể bay")
    
    def swim(self):
        print("Chim cánh cụt bơi rất nhanh")

# common interface
def kt_bay(bird):
    bird.fly()

def kt_boi(b):
	b.swim()

#instantiate objects
con1 = Vet()
con2 = Canhcut()

# passing the object
kt_bay(con1)
kt_bay(con2)
kt_boi(con1)
kt_boi(con2)

Output:

Vẹt có thể bay
Chim cánh cụt không thể bay
Vẹt không biết bơi
Chim cánh cụt bơi rất nhanh

Để sử dụng tính đa hình, ta đã tạo các 'giao diện chung', đó là hàm kt_bay(), kt_boi() và gọi phương thức cùng có ở các đối tượng.

Đáng chú ý với OOP

Lập trình hướng đối tượng làm cho chương trình dễ hiểu và hiệu quả.

Vì lớp có thể chia sẻ được, mã có thể được sử dụng lại.

Dữ liệu an toàn và bảo mật với dữ liệu trừu tượng.

Tính đa hình cho phép cùng một giao diện cho các đối tượng khác nhau, do đó các lập trình viên có thể viết mã hiệu quả.

Last updated