Trong bài viết hướng dẫn này, chúng ta sẽ tìm hiểu về Lập trình hướng đối tượng (OOP) bằng Python và khái niệm cơ bản thông qua các ví dụ hướng dẫn

Lập trình hướng đối tượng

Python là ngôn ngữ lập trình linh động, vì vậy nó hỗ trợ nhiều cách tiếp cận lập trình khác nhau.

Một trong những cách tiếp cận phổ biến để giải quyết các vấn đề bằng lập trình là tạo ra các đối tượng. Cách tiếp cận này được gọi là lập trình hướng đối tượng (OOP).

Một đối tượng trong thực tế có hai đặc trưng sau:

  • - Thuộc tính (attributes)
  • - Hành vi (methods)

Ta có ví dụ như sau:

Một con vẹt có thể là một đối tượng, vì nó có các đặc tính sau:

  • - tên, tuổi, màu sắc dưới dạng thuộc tính
  • - ca hát, nhảy múa như hành vi 

 Khái niệm OOP trong python hay trong bất kỳ ngôn ngữ lập trình nào đều tập trình vào việc tái sử dụng code. Khái niệm này còn được gọi là DRY (Don't Repeat Yourself - Đừng lặp lại chính mình)

Trong Python, khái niệm OOP tuân theo một số nguyên tắc cơ bản:

Class (lớp)

Class hay còn gọi là lớp, là một bản thiết kế của đối tượng. 

Ví dụ: Chúng ta có thể coi class như một bản phác thảo của một con vẹt với các nhãn. Nó bao gồm các chi tiết về tên, màu sắc, kích thước, v..v. Dựa trên những mô tả của phác thảo này, chúng ta có thể biết được về con vẹt. Ở đây, một con vẹt là một đối tượng.

Hoặc trong thực tế, Class giống như một bản thiết kế nhà, thiết kế này sẽ chỉ rõ ngôi nhà có diện tích thế nào, kết cấu ra sao, sử dụng vật liệu gì...Còn các ngôi nhà được tạo nên từ thiết kế được gọi là các đối tượng, hay ngôi nhà là thể hiện của thiết kế.

Một ví dụ  class con vẹt (parrot) như sau:

class Parrot:
    pass

Ở đây chúng ta sử dụng từ khóa class để định nghĩa ra một class trống là Parrot. Từ class này, chúng ta sẽ tạo ra các thể hiện của class. Một thể hiện là một đối tượng cụ thể được tạo ra từ class.

Object (đối tượng)

Một object (đối tượng) là một thể hiện của một class. Khi định nghĩa ra class, ta chỉ mô tả định nghĩa các thuộc tính và phương thức của class đó. Vì vậy nên class chưa được cấp phát bộ nhớ.

Ví dụ sau để tạo ra một đối tượng (object) của một class:

obj = Parrot()

Ở đây, obj là một đối tượng của class Parrot.

Giả sử chúng ta có chi tiết về một con vẹt,, bây giờ chúng ta sẽ xây dựng một class và các đối tượng của con vẹt.

Ví dụ 1: Tạo Class và đối tượng trong Python

class Parrot:

    # thuộc tính của class
    species = "bird"

    # Khởi tạo thuộc tính của đối tượng
    def __init__(self, name, age):
        self.name = name
        self.age = age

# tạo ra đối tượng blu và woo
blu = Parrot("Blu", 10)
woo = Parrot("Woo", 15)

# truy cập vào các thuộc tính 
print("Blu is a {}".format(blu.__class__.species))
print("Woo is a {}".format(woo.__class__.species))

# access the instance attributes
print("{} is {} years old".format( blu.name, blu.age))
print("{} is {} years old".format( woo.name, woo.age))

Kết quả sẽ là

Blu is a bird
Woo is also a bird
Blu is 10 years old
Woo is 15 years old

Trong đoạn code trên, chúng ta tạo ra một class có tên là Parrot, sau đó chúng ta xác định các thuộc tính bằng cách khởi tạo các thuộc tính. Các thuộc tính là một đặc tính của đối tượng

Các thuộc tính này đượ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 được chạy đầu tiên ngay sau khi đối tượng được tạo.

Sau đó, chúng ta tạo ra  các thể hiện của class Parrot. Ở đây, blu và woo là các tham chiếu (giá trị) đến các đối tượng mới.

Chúng ta có thể truy cập thuộc tính lớp bằng cách sử dụng __class __. species. Các thuộc tính của lớp sẽ có giá trị giống nhau trong tất cả các đối tượng. Tương tự, chúng ta truy cập các thuộc tính của đối tượng bằng cách sử dụng blu.name và blu.age. Tuy nhiên, các thuộc tính của mỗi đối tượng là khác nhau.

Methods (phương thứ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 class. Chúng được sử dụng để xác định các hành vi của một đối tượng.

Ví dụ 2 : Tạo ra các Phương thức trong Python

class Parrot:
    
    # 
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    # Phương thức
    def sing(self, song):
        return "{} sings {}".format(self.name, song)

    def dance(self):
        return "{} is now dancing".format(self.name)

# Khởi tạo đối tượng
blu = Parrot("Blu", 10)

# Gọi phương thức của đối tượng
print(blu.sing("'Happy'"))
print(blu.dance())

Kết quả in ra sẽ là:

Blu sings 'Happy'
Blu is now dancing

Trong đoạn code trên chúng ta định nghĩa ra 2 phương thức là sing() và dance(). Hai phương thức này được gọi từ thể hiện của lớp là blu.sing() và blu.dance().

Inheritance (kế thừa)

Kế thừa là một cách tạo một class mới để sử dụng các thuộc tính và phương thức của một class trước đó mà không cần sửa đổi nó. Class mới được hình thành là một class dẫn xuất (hoặc class con). Tương tự, class trước đó là một class cơ sở (hoặc class cha).

Ví dụ 3: Sử dụng kế thừa trong python

# class cha
class Bird:
    
    def __init__(self):
        print("Bird is ready")

    def whoisThis(self):
        print("Bird")

    def swim(self):
        print("Swim faster")

# class con
class Penguin(Bird):

    def __init__(self):
        # gọi hàm super()
        super().__init__()
        print("Penguin is ready")

    def whoisThis(self):
        print("Penguin")

    def run(self):
        print("Run faster")

peggy = Penguin()
peggy.whoisThis()
peggy.swim()
peggy.run()

Tạo ra một class Penguin (Chim cánh cụt) từ class cha là Bird

Kết quả sẽ là

Bird is ready
Penguin is ready
Penguin
Swim faster
Run faster

Trong chương trình trên, chúng ta đã tạo hai lớp, tức là Bird (lớp cha) và Penguin (lớp con). Lớp con kế thừa các chức năng của lớp cha. Chúng ta có thể thấy điều này từ phương thức swim().

Một lần nữa, lớp con đã sửa đổi hành vi của lớp cha. Chúng ta có thể thấy điều này từ phương thức whoisThis(). Hơn nữa, chúng tôi mở rộng các chức năng của lớp cha bằng cách tạo một phương thức run() mới.

Ngoài ra, chúng tôi sử dụng hàm super () bên trong phương thức __init __(). Điều này cho phép chúng ta chạy phương thức __init __() của lớp cha bên trong lớp con.

Encapsulation (tính đóng gói)

Sử dụng OOP trong Python, chúng ta có thể hạn chế quyền truy cập vào các phương thức và biến. Điều này giúp ngăn cho dữ liệu không bị sửa đổi trực tiếp. Đây được gọi là tính đóng gói. Trong Python, chúng tôi biểu thị các thuộc tính private bằng cách sử dụng dấu gạch dưới làm tiền tố, như __ hoặc __.

Ví dụ 4: Đóng gói dữ liệu trong python

class Computer:

    def __init__(self):
        self.__maxprice = 900

    def sell(self):
        print("Selling Price: {}".format(self.__maxprice))

    def setMaxPrice(self, price):
        self.__maxprice = price

c = Computer()
c.sell()

# thay đổi giá
c.__maxprice = 1000
c.sell()

# sử dụng hàm setter
c.setMaxPrice(1000)
c.sell()

Kết quả in ra sẽ là

Selling Price: 900
Selling Price: 900
Selling Price: 1000

Trong đoạn code trên, chúng ta đã định nghĩa một lớp Computer.

Chúng ta đã sử dụng phương thức __init __ () để lưu giá bán __maxprice. Chúng ta đã thay đổi giá bằng cách gọi c.__maxprice = 1000. Tuy nhiên, chúng ta không thể thay đổi  vì trong Python coi __maxprice là thuộc tính riêng tư (private).

Giống như đoạn code trên, để thay đổi giá trị của thuộc tính privte, chúng ta phải sử dụng một hàm setter để gián tiếp thay đổi giá trị thuộc tính, tức là setMaxPrice() và truyền vào tham số giá. 

Polymorphism (đa hình)

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

Các bạn có thể xem code sau đây.

Ví dụ 5: Sử dụng đa hình trong python

class Parrot:

    def fly(self):
        print("Parrot can fly")
    
    def swim(self):
        print("Parrot can't swim")

class Penguin:

    def fly(self):
        print("Penguin can't fly")
    
    def swim(self):
        print("Penguin can swim")

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

#instantiate objects
blu = Parrot()
peggy = Penguin()

# passing the object
flying_test(blu)
flying_test(peggy)

Kết quả sẽ là:

Parrot can fly
Penguin can't fly

Trong chương trình trên, chúng ta đã định nghĩa hai lớp Parrot và Penguin. Mỗi đối tượng trong đó có một phương thức fly() chung. Tuy nhiên, chức năng của chúng khác nhau.

Để sử dụng tính đa hình, mình đã tạo một interface chung, tức là hàm fly_test() lấy bất kỳ đối tượng nào và gọi phương thức fly() của đối tượng.

Do đó, khi chúng ta truyền các đối tượng blu và peggy vào trong hàm fly_test(), nó đã gọi hàm fly tương ứng.

Tổng kết

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

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

Dữ liệu an toàn và bảo mật hơn.

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

Lập trình python