Lập trình hướng đối tượng (OOP) với Python

Python là ngôn ngữ lập trình cấp cao, được sử dụng rộng rãi để phát triển web, phân tích dữ liệu và tính toán khoa học. Nó có cú pháp đơn giản, dễ học, giảm chi phí bảo trì chương trình. Và đặc biệt là OOP trong Python.

OOP trong Python là gì?

OOP là viết tắt của Object Oriented Programming, là một mô hình hoặc phong cách của phần mềm lập trình với các khái niệm như Object và Class. Các đối tượng là các thực thể trong thế giới thực. Nó tập trung vào việc mô hình hóa các hệ thống phần mềm như một tập hợp các đối tượng tương tác.

Ngôn ngữ lập trình Python

Ưu điểm của lập trình hướng đối tượng là:

  • Khả năng sử dụng lại mã (OOP thúc đẩy khả năng sử dụng lại mã thông qua các tính năng như kế thừa và đa hình.)
  • Cấu trúc mã rõ ràng (OOP khuyến khích cấu trúc mã rõ ràng và có tổ chức bằng cách chia nhỏ hệ thống thành các đối tượng nhỏ hơn, khép kín. Mỗi đối tượng đóng gói dữ liệu và hành vi của nó, dẫn đến mã dễ quản lý và dễ hiểu hơn.)
  • Các ứng dụng có thể mở rộng (Bạn có thể thêm hoặc xóa các tính năng hoặc sửa đổi các tính năng hiện có mà không ảnh hưởng đến toàn bộ cơ sở mã. Tính linh hoạt này đạt được thông qua các khái niệm như trừu tượng hóa và đóng gói.)
  • Tính mô đun (OOP thúc đẩy tính mô đun hóa bằng cách chia các hệ thống phức tạp thành các mô đun hoặc đối tượng nhỏ hơn, có thể tái sử dụng)

Khái niệm về Class:

Class là các bản thiết kế hoặc mẫu để tạo các đối tượng. Chúng xác định cấu trúc và hành vi của các đối tượng bằng cách chỉ định các thuộc tính (dữ liệu) và phương thức (hàm)

  • Để định nghĩa một lớp trong Python, bạn sử dụng classtừ khóa theo sau là tên lớp.
class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def display_info(self):
        return f"{self.year} {self.make} {self.model}"

Lớp “Car” ở đây có thể được sử dụng để tạo các thể hiện hoặc đối tượng

Hàm tạo ( __init__Phương thức):

  • Phương __init__thức (hàm tạo) là một phương thức đặc biệt trong các lớp Python có chức năng khởi tạo các thuộc tính đối tượng khi một đối tượng được tạo.
  • Nó được gọi tự động khi một đối tượng được khởi tạo.

Khái niệm về Objects:

Các đối tượng trong OOP là các thể hiện của các lớp đóng gói dữ liệu và hành vi. Chúng cho phép các nhà phát triển lập mô hình các thực thể trong thế giới thực, thúc đẩy khả năng sử dụng lại mã và tạo điều kiện thuận lợi cho việc triển khai các nguyên tắc OOP để tạo ra các hệ thống phần mềm mô-đun và có thể bảo trì.

car1 = Car( "Toyota" , "Camry" , 2022) 
car2 = Car( "Honda" , "Accord" , 2021)

print (car1.display_info()) # Đầu ra: Toyota Camry 2022
print (car2.display_info()) # Đầu ra: Honda Accord 2021

Chúng ta đã tạo hai đối tượng ở đây bằng cách sử dụng lớp “Car”

Bốn khía cạnh cốt lõi của OOP:

  1. Trừu tượng hóa (abstraction)
  2. Kế thừa (inheritance)
  3. Đóng gói (encapsulation)
  4. Đa hình (polymorphism)

Trừu tượng hóa:

Tính trừu tượng liên quan đến việc ẩn các chi tiết triển khai phức tạp và chỉ hiển thị các tính năng hoặc chức năng thiết yếu của một đối tượng. Nó tập trung vào những gì một đối tượng làm hơn là cách nó thực hiện, cho phép các nhà phát triển làm việc ở mức độ hiểu biết cao hơn.

Các lớp và phương thức trừu tượng: Lớp bao gồm phương thức trừu tượng được gọi là lớp trừu tượng và các phương thức không có phần thân hoặc không thực hiện bất kỳ nhiệm vụ nào hoặc không sở hữu bất kỳ logic nào được gọi là các phương thức trừu tượng.

Tuy nhiên, python không hỗ trợ tạo lớp trừu tượng . Để tạo một lớp trừu tượng trong Python, chúng tôi sử dụng thư viện ‘abc’ và nhập ‘ABC’ và ‘abstractmethod’ từ nó. Sau đó, chúng ta phải sử dụng trình trang trí ‘@abstractmethod’ bên trong một lớp để định nghĩa lớp đó là một lớp trừu tượng.

Lưu ý** Chúng ta không thể tạo đối tượng của lớp trừu tượng.

Áp dụng: Ở đây chúng ta tạo các lớp cấp cao hơn với một số phương thức trừu tượng mà chúng ta muốn các lớp cấp thấp hơn tuân theo. Các lớp cấp thấp hơn có thể viết bất kỳ logic nào bên trong các phương thức đó nhưng quy tắc là chúng cần sử dụng các phương thức trừu tượng đó. Ở đây chúng ta đang truyền các phương thức trừu tượng đó trong khi ẩn logic bên trong.

from abc import ABC , abstractmethod 

from abc import ABC, abstractmethod

# Parent class using ABC for being an abstract class
class Parent(ABC):
def __init__(self, name):
self.name = name

# Here we used a decorator called @abstractmethod
@abstractmethod
def security(self):
pass

# Creating Child class and inheriting Parent class
class Child(Parent):

#Note: here we wanted that the child class also implement security method.
def security(self):
print('You are secure in child class')

# Corrected instantiation of Child class object
abhay = Child()
abhay.security()
o/p--> You are secure in child class

Lớp trừu tượng cho Cổng thanh toán:

  • Tạo một lớp trừu tượng được gọi PaymentGateway bằng các phương thức trừu tượng như process_paymentrefund_paymentvà get_payment_status.
  • Các phương thức này xác định các chức năng chung mà tất cả các cổng thanh toán phải hỗ trợ.
from abc import ABC, abstractmethod

class PaymentGateway(ABC):
@abstractmethod
def process_payment(self, amount):
pass

@abstractmethod
def refund_payment(self, transaction_id, amount):
pass

@abstractmethod
def get_payment_status(self, transaction_id):
pass

Kế thừa:

Nghĩa đen của sự kế thừa là nhằm lấy đi các thuộc tính từ cha mẹ. Trong OOP, khái niệm kế thừa cho phép chúng ta sử dụng các thuộc tính của lớp cha ở lớp con rất dễ dàng.

Điều thú vị ở đây là ngoài việc kế thừa các thuộc tính và phương thức từ lớp cha, lớp con cũng có thể có các thuộc tính và phương thức của nó.

Lưu ý ** Nếu chúng ta không có hàm tạo trong lớp con thì nó có thể tự động kế thừa hàm tạo của lớp cha:

class Parent:
def __init__(self, name):
self.name = name

def show_name(self):
print('The name is:', self.name)

class Child(Parent):
pass # Child class inherits the constructor and show_name method from Parent class

# Creating an object of the Child class
child_obj = Child("Abhay") # Passing the name "Abhay" to the constructor

# Calling the show_name method of the Parent class using the Child object
child_obj.show_name() # Output: The name is: Abhay

Nếu lớp con có hàm tạo của nó nhưng bạn muốn gọi hàm tạo của lớp cha thì bạn có thể sử dụng từ khóa “super()”

**Trong Python, super()từ khóa được sử dụng để truy cập và gọi các phương thức hoặc hàm tạo từ lớp cha (còn được gọi là siêu lớp) trong lớp con (còn được gọi là lớp con).

class Parent:
def __init__(self,name):
self.name=name

class Child(Parent):
def __init__(self,name,age):
super().__init__(name):
self.age=age

def print_name(self):
print(f"the name is {self.name} and the age is {self.age}")


Abhay = Child('abhay',22)
Abhay.print_name() # Output: The name is Abhay and the age is 22

Các loại thừa kế:

Trong Lập trình hướng đối tượng Python (OOP), chủ yếu có bốn loại kế thừa:

Kế thừa đơn: Trong kế thừa đơn, một lớp chỉ kế thừa từ một lớp cơ sở. Đây là hình thức kế thừa đơn giản nhất trong đó một lớp con mở rộng chức năng của một lớp cha duy nhất.

class Parent:
def print1('parent class print')

class Child(Parent):
def print2('child class print')

abhay=Child()
abhay.print1()#output==> parent class print

Đa kế thừa : Đa kế thừa cho phép một lớp kế thừa từ nhiều lớp cơ sở. Điều này có nghĩa là một lớp con có thể có nhiều lớp cha và nó kế thừa các thuộc tính và phương thức từ tất cả chúng.

  • Khái niệm về MRO (Thứ tự phân giải phương thức – Method Resolution Order): Thứ tự phân giải phương thức (MRO) xác định thứ tự các phương thức được giải quyết và gọi khi có nhiều kế thừa. Trong Python, thuật toán Tuyến tính hóa C3 được sử dụng để tính toán MRO.
class Parent1:
def print1('parent1'):
print('parent1')

class Parent2:
def print1('parent2'):
print('parent2')

class Child(Parent1,Parent2):
def print2('child'):
print('child')

abhay=Child()
abhay.print2()
abhay.print1()

#output1-->child(as we invoked child class method called print1)
#output2-->parent1(so print1 is the common method in both the parent classes "Parent1 and Parent2" .
#But MRO decides which method is going to be invoked
#Child class inherited Parent1 first so priority will be given to that class.

#here the MRO would be: The MRO for the Child class can be obtained using Child.__mro__ or Child.mro().
#In this case, the MRO will be: Child -> Parent1 -> Parent2 -> object.

Kế thừa đa cấp : Kế thừa đa cấp bao gồm một chuỗi kế thừa trong đó một lớp con kế thừa từ lớp cha và một lớp khác kế thừa từ lớp con này. Nó tạo thành một hệ thống phân cấp của các lớp.

class Grandparent:
def method1(self):
print("Method 1 from Grandparent class")

class Parent(Grandparent):
def method2(self):
print("Method 2 from Parent class")

class Child(Parent):
def method3(self):
print("Method 3 from Child class")

# Child class inherits from Parent class, which in turn inherits from Grandparent class

Kế thừa phân cấp: Kế thừa phân cấp bao gồm nhiều lớp con kế thừa từ một lớp cha duy nhất. Điều này có nghĩa là một lớp cha duy nhất đóng vai trò là cơ sở cho nhiều lớp con.

class Parent:
def method1(self):
print("Method 1 from Parent class")

class Child1(Parent):
def method2(self):
print("Method 2 from Child1 class")

class Child2(Parent):
def method3(self):
print("Method 3 from Child2 class")

# Child1 and Child2 classes inherit from the same Parent class

Đóng gói:

Đóng gói giống như việc đặt mọi thứ vào một chiếc hộp và khóa nó lại, để không ai có thể can thiệp vào nó từ bên ngoài. Đó là một cách để các lập trình viên giữ dữ liệu và hành động (như hàm) cùng nhau trong một lớp, ẩn khỏi sự can thiệp từ bên ngoài. Thay vào đó, nó cung cấp một cách được kiểm soát để các phần khác của mã tương tác với các phần ẩn này bằng cách sử dụng các quy tắc rõ ràng.

Đóng gói bao gồm hai khái niệm phụ:

  1. Công cụ sửa đổi truy cập (Công khai, Riêng tư, Được bảo vệ – Public, Private, Protected)
  2. Kiểm soát truy cập (phương thức getter và setter)

Công cụ sửa đổi truy cập: Công cụ sửa đổi truy cập trong các ngôn ngữ lập trình như Python, Java và C++ là những từ khóa được sử dụng để kiểm soát khả năng truy cập của các thành viên lớp (thuộc tính và phương thức).

Ba công cụ sửa đổi quyền truy cập chính thường được sử dụng là công khai, được bảo vệ và riêng tư.

  1. Công cụ sửa đổi truy cập công cộng ( public):
  • Các thành viên public có thể được truy cập từ bên ngoài lớp học.
  • Theo mặc định, tất cả các thành viên trong lớp Python đều ở chế độ công khai trừ khi có quy định rõ ràng khác.

2. Công cụ sửa đổi quyền truy cập được bảo vệ ( protected):

  • Các thành viên được bảo vệ có thể truy cập được trong cùng một lớp và bởi các lớp con (lớp con) kế thừa từ lớp cha.
  • Để xác định một thành viên là được bảo vệ trong Python, hãy thêm một dấu gạch dưới vào tiền tố tên thành viên_

3. Công cụ sửa đổi quyền truy cập riêng tư ( private):

  • Các thành viên riêng tư chỉ có thể truy cập được trong lớp nơi chúng được xác định chứ không phải từ bên ngoài lớp hoặc các lớp con của nó.
  • Để xác định một thành viên là riêng tư trong Python, hãy đặt tiền tố vào tên thành viên bằng dấu gạch dưới kép__
class MyClass:
def __init__(self):
self.public_var = "Public variable"
self._protected_var = "Protected variable"
self.__private_var = "Private variable"

def public_method(self):
print("This is a public method")

def _protected_method(self):
print("This is a protected method")

def __private_method(self):
print("This is a private method")

# Create an object of MyClass
obj = MyClass()

# Accessing public members
print(obj.public_var) # Output: Public variable
obj.public_method() # Output: This is a public method

# Accessing protected members
print(obj._protected_var) # Output: Protected variable
obj._protected_method() # Output: This is a protected method

# Accessing private members (will raise an AttributeError)
# print(obj.__private_var)
# obj.__private_method()

Kiểm soát truy cập (Access Control): Các phương thức Getter và setter là một mẫu phổ biến được sử dụng trong lập trình hướng đối tượng (OOP) để triển khai việc đóng gói và kiểm soát quyền truy cập vào các thuộc tính riêng tư của một lớp.

  1. Phương thức Getter: Phương thức getter được sử dụng để lấy giá trị của thuộc tính riêng (thành viên dữ liệu) từ một đối tượng. Nó thường được đặt tên với tiền tố “get” theo sau là tên của thuộc tính mà nó truy xuất. Nó cung cấp quyền truy cập chỉ đọc.
  2. Phương thức setter: Phương thức setter được sử dụng để sửa đổi giá trị của thuộc tính riêng (thành viên dữ liệu) trong một đối tượng. Nó thường được đặt tên với tiền tố “set”, theo sau là tên của thuộc tính mà nó đặt. Nó cung cấp quyền truy cập ghi có kiểm soát.
class MyClass:
def __init__(self):
self.__private_var = 10 # Private attribute

def get_private_var(self):
return self.__private_var # Getter method

def set_private_var(self, new_value):
if new_value >= 0: # Validation logic
self.__private_var = new_value # Setter method

obj = MyClass()
print(obj.get_private_var()) # Output: 10
obj.set_private_var(20) # Setter method called
print(obj.get_private_var()) # Output: 20

Tóm tắt về đóng gói : Đóng gói là gói dữ liệu (thuộc tính) và phương thức (hàm) hoạt động trên dữ liệu thành một đơn vị duy nhất gọi là lớp. Nó cho phép các nhà phát triển ẩn trạng thái bên trong của một đối tượng và chỉ hiển thị các chức năng cần thiết thông qua các giao diện được xác định rõ ràng. Đóng gói thúc đẩy bảo vệ dữ liệu, trừu tượng hóa, mô đun hóa và khả năng sử dụng lại mã.

Đa hình:

Tính đa hình giống như có một công cụ kỳ diệu trong lập trình cho phép chúng ta sử dụng  một thứ (như hàm hoặc phương thức) theo nhiều cách khác nhau  tùy thuộc vào thứ chúng ta đang làm việc.

Ví dụ: hãy tưởng tượng bạn có một công cụ có thể “làm điều gì đó” với các đồ vật. Công cụ này có thể thực hiện nhiều việc khác nhau tùy theo loại đối tượng mà nó đang xử lý. Nếu đó là một con chim, công cụ sẽ làm cho nó bay. Nếu đó là một chiếc ô tô, công cụ này sẽ khiến nó lái được. Nó giống như có một công cụ siêu linh hoạt, biết cách thích ứng với các tình huống khác nhau!

Đa hình cho phép chúng ta viết  mã có thể xử lý các loại dữ liệu hoặc đối tượng khác nhau mà không cần hướng dẫn riêng cho từng loại . Điều này làm cho mã của chúng tôi linh hoạt hơn, dễ hiểu hơn và có thể tái sử dụng trên các phần khác nhau của chương trình.

Trong Python,  tính đa hình đạt được thông qua nạp chồng phương thức và ghi đè phương thức , hai kỹ thuật mạnh mẽ hỗ trợ các hành vi khác nhau dựa trên ngữ cảnh mà chúng được sử dụng.

Nạp chồng phương thức : Nạp chồng phương thức là một dạng đa hình trong đó một lớp có thể có nhiều phương thức có cùng tên nhưng tham số hoặc chữ ký khác nhau. Điều này cho phép các phương thức thực hiện các tác vụ khác nhau dựa trên các đối số đầu vào.

Trong Python, khái niệm nạp chồng phương thức không được hỗ trợ trực tiếp. Tuy nhiên, trong Python, bạn có thể đạt được hành vi tương tự bằng cách sử dụng các giá trị tham số mặc định

class MathOperations:
#by default b is zero here
def area(self,a,b=0):
if b==0:
return 3.14*(a**2)
else:
return a*b

Ghi đè phương thức : Ghi đè phương thức là một dạng đa hình khác trong đó một lớp con cung cấp cách triển khai cụ thể của một phương thức đã được xác định trong siêu lớp của nó. Điều này cho phép các lớp con tùy chỉnh hoặc mở rộng hoạt động của các phương thức được kế thừa.

class Vehicle:
def start_engine(self):
print("Starting the engine...")

def move(self):
print("Vehicle is moving...")

class Car(Vehicle):
def start_engine(self):
print("Starting car engine...")

def move(self):
print("Car is cruising...")

class Truck(Vehicle):
def start_engine(self):
print("Starting truck engine...")

def move(self):
print("Truck is hauling cargo...")

# Creating instances of Vehicle, Car, and Truck
vehicle = Vehicle()
vehicle.start_engine() # Output: Starting the engine...
vehicle.move() # Output: Vehicle is moving...

car = Car()
car.start_engine() # Output: Starting car engine...
car.move() # Output: Car is cruising...

truck = Truck()
truck.start_engine() # Output: Starting truck engine...
truck.move() # Output: Truck is hauling cargo...

Trong ví dụ này, chúng ta có một hệ thống phân cấp các lớp đại diện cho các loại phương tiện khác nhau:  Vehicle,  Car, và  Truck. Cả hai  Car và  Truck đều là lớp con của  Vehicle và ghi đè các  phương thức start_engine và  move .

Thêm khái niệm:

Phương thức tĩnh:  Phương thức tĩnh trong Python là phương thức thuộc về lớp chứ không phải là một thể hiện của lớp. Những phương thức này được xác định bằng cách sử dụng  @staticmethod trình trang trí. Chúng không hoạt động trực tiếp trên các biến thể hiện hoặc biến lớp và không yêu cầu quyền truy cập vào các đối tượng thể hiện ( self) hoặc lớp ( cls).

class MathUtils:
@staticmethod
def add(x, y):
return x + y

result = MathUtils.add(5, 10)
print(result) # Output: 15

Phương thức lớp:  Phương thức lớp trong Python là một phương thức thuộc về lớp và hoạt động trên chính lớp đó chứ không phải trên các cá thể riêng lẻ. Những phương thức này được xác định bằng cách sử dụng  @classmethod trình trang trí. Họ nhận chính lớp đó làm đối số đầu tiên ( cls) thay vì thể hiện ( self).

class Employee:
company = "XYZ Corp" # Class variable

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

@classmethod
def get_company_name(cls):
return cls.company

company_name = Employee.get_company_name()
print(company_name) # Output: XYZ Corp

Phương thức ma thuật:  Phương thức ma thuật là các hàm đặc biệt trong các lớp Python giúp bạn tùy chỉnh cách các đối tượng của bạn hoạt động khi bạn sử dụng các thao tác tích hợp sẵn. Chúng được gọi là “ma thuật” vì Python tự động gọi chúng cho các hành động cụ thể, như tạo đối tượng, làm toán, so sánh đối tượng, chuyển đổi đối tượng thành chuỗi, v.v.

Bạn có thể nhận ra các phương thức ma thuật vì chúng có tên bắt đầu và kết thúc bằng dấu gạch dưới kép, như  __init__,  __add__,  __str__, v.v. Những phương thức này cho phép bạn xác định các hành vi đặc biệt cho đối tượng của mình mà không cần phải gọi chúng một cách rõ ràng.