Chuyển đến nội dung chính

Lập trình hàm so với lập trình hướng đối tượng trong phân tích dữ liệu

Khám phá hai mô hình lập trình được sử dụng phổ biến nhất trong khoa học dữ liệu: lập trình hướng đối tượng và lập trình hàm.
· 15 phút đọc

Khám phá cùng AI

Cấu trúc tốt là dấu hiệu của mã nguồn được viết tốt. Bản chất của cấu trúc này được quyết định bởi mô hình lập trình sử dụng. Mô hình lập trình là một tập hợp nhất quán các nguyên tắc và kỹ thuật xác định cách tiếp cận khi thiết kế và viết chương trình máy tính.

Có nhiều mô hình lập trình, như thủ tục, mệnh lệnh, khai báo, hướng đối tượng và hàm. Một số ngôn ngữ chỉ giới hạn ở vài mô hình. Ví dụ, SQL là một ngôn ngữ theo kiểu khai báo. Tuy nhiên, nhiều ngôn ngữ hiện đại, bao gồm Python, hỗ trợ nhiều mô hình lập trình, mang lại sự linh hoạt.

Luyện viết hàm thuần với bài tập thực hành từ khóa học Introduction to Programming Paradigms của chúng tôi. 

Nhiều lập trình viên mặc định chọn một mô hình lập trình ưa thích, thường là mô hình họ học đầu tiên. Những lập trình viên nâng cao hiểu rõ ưu và nhược điểm của các mô hình này và có thể tận dụng điểm mạnh của từng mô hình trong những tình huống khác nhau. Hãy cùng điểm qua hai mô hình lập trình được dùng phổ biến nhất trong khoa học dữ liệu: lập trình hàm và lập trình hướng đối tượng.

image1.png

Lập trình hàm (FP) là gì?

Trong lập trình hàm, mã được xây dựng bằng cách định nghĩa và sử dụng các hàm quyết định chương trình sẽ vận hành như thế nào. Đây là một phân nhánh của mô hình lập trình khai báo.

Hàm là những dòng mã nhận tham số vào và trả về kết quả. Một hàm đơn giản có thể nhận một danh sách số làm tham số và trả về các giá trị chẵn, chẳng hạn.

def get_even_numbers(list):
    return [num for num in list if num % 2 == 0]

Xem các khóa học này để học thực hành viết hàm trong Python và R.

Lập trình hàm nổi bật nhờ khả năng dễ truy vết và dễ dự đoán. Lý do là các hàm được dùng là bất biến, nghĩa là chúng không thể bị thay đổi. Hàm được định nghĩa, thường ở một phần riêng của mã (hoặc đôi khi ở một tệp khác), rồi được sử dụng xuyên suốt khi cần. Thuộc tính này giúp bạn dễ xác định đoạn mã đang làm gì, vì hàm luôn hoạt động và được gọi theo cùng một cách ở mọi nơi.

Khái niệm bất biến cũng được mở rộng sang các cấu trúc dữ liệu trong lập trình hàm. Trong FP, ưu tiên tạo một bản sao dữ liệu khi thay đổi trạng thái của nó. Cách này thiết lập một “vết giấy tờ” ảo về mọi phép biến đổi áp dụng lên dữ liệu, tăng khả năng truy vết và đơn giản hóa việc xác định vấn đề. Tuy nhiên, cần lưu ý rằng cách tiếp cận này thường làm tăng mức tiêu thụ bộ nhớ, vì mỗi lần thay đổi trạng thái đều liên quan đến việc sao chép toàn bộ tập dữ liệu.

Một tập con các hàm dùng trong lập trình hàm là hàm bậc cao. Đây là những hàm nhận một hàm khác làm tham số hoặc trả về một hàm. Ví dụ, bạn có thể có một hàm bậc cao chạy một hàm nhiều lần, như ví dụ dưới đây, hoặc một hàm chọn đúng hàm cần dùng trong một kịch bản cụ thể.

def run_function_multiple_times(function, num_times):
	for i in range(num_times):
        function()

Ưu điểm của lập trình hàm trong phân tích dữ liệu

Lập trình hàm cung cấp cấu trúc để giảm trùng lặp trong mã của bạn. Mục tiêu là giúp biểu đạt các hành động lặp lại bằng những “động từ” cấp cao một cách dễ dàng.

Ví dụ, giả sử bạn cần chỉnh sửa chuỗi để phù hợp một định dạng cụ thể tại nhiều vị trí khác nhau trong mã. Định dạng này yêu cầu bạn loại bỏ khoảng trắng, dấu câu và chữ số, đồng thời chuyển tất cả về chữ thường. Bạn có thể sao chép và dán vài dòng mã ở nhiều phần khác nhau khi cần. Nhưng việc trùng lặp đó sẽ làm mã cồng kềnh. Thay vào đó, bạn chỉ cần định nghĩa một hàm để xử lý chuỗi và gọi hàm đó ở những nơi phù hợp. Cách này làm gọn mã, giúp dễ đọc hơn và gõ nhanh hơn.

def clean_string(string):
	exclude = set(string.punctuation + string.digits + " ")
    return ''.join(ch.lower() for ch in string if ch not in exclude)
 
my_string = "Hello, World! 123"
new_string = clean_string(my_string)
print(new_string)

Phối hợp các hàm để xây dựng pipeline dữ liệu phức tạp

Lập trình hàm hữu ích khi làm việc với các tập dữ liệu lớn hoặc pipeline dữ liệu vì nó cho phép chia nhỏ các tác vụ xử lý dữ liệu phức tạp thành các bước nhỏ hơn, dễ quản lý hơn. Thường thì sẽ có ít nhất một hàm cho mỗi bước trong pipeline. Nếu bạn đã vẽ sơ đồ luồng cho các bước cần có trong pipeline, bạn sẽ có ý tưởng sơ bộ về các hàm cần dùng. Nói chung, bạn sẽ cần ít nhất một hàm cho mỗi bước.

Sơ đồ kế hoạch đường ống dữ liệu

Hầu hết các ngôn ngữ cũng có nhiều hàm dựng sẵn và thư viện có thể tải thêm. Gói purr trong R là một ví dụ điển hình. Điều này giúp bạn nhanh chóng xây dựng pipeline đồng thời giảm số lượng hàm tùy chỉnh cần tạo.

Tạo mã diễn đạt và dễ đọc bằng các nguyên tắc FP

Lập trình hàm hướng đến việc tạo ra mã rất dễ đọc. Dưới đây là một vài hướng dẫn để xây dựng mã diễn đạt và dễ đọc khi dùng lập trình hàm:

  • Giữ hàm nhỏ gọn và tập trung: Mỗi hàm nên có một trách nhiệm rõ ràng và duy nhất, giúp dễ hiểu và dễ bảo trì. Đây còn được gọi là các hàm thuần.
  • Tránh trạng thái có thể thay đổi: Tránh thay đổi trạng thái dữ liệu để loại bỏ tác dụng phụ và giúp luồng dữ liệu dễ dự đoán hơn. Cấu trúc dữ liệu bất biến giúp mã rõ ràng và dễ suy luận. Thay vì sửa đổi trạng thái của một cấu trúc, hãy tạo cấu trúc dữ liệu mới với thay đổi mong muốn.
  • Tận dụng hàm bậc cao: Những hàm nhận hàm khác làm tham số hoặc trả về hàm được gọi là hàm bậc cao. Chúng cho phép bạn viết mã diễn đạt hơn và gọn hơn.
  • Sử dụng hợp thành hàm: Hợp thành hàm là quá trình kết hợp hai hoặc nhiều hàm để tạo ra một hàm mới. Điều này cho phép bạn chia nhỏ vấn đề phức tạp thành các phần nhỏ hơn, dễ quản lý.

Lập trình hướng đối tượng (OOP) là gì?

Lập trình hướng đối tượng là một mô hình tập trung vào các đối tượng. Đối tượng là các thực thể độc lập, bao gồm cả dữ liệu và các phương thức thao tác lên dữ liệu đó. Đây là cách giữ dữ liệu và các thao tác của chúng ở gần nhau, khác với lập trình hàm, nơi chúng thường tách rời. Điều này có thể giúp bạn dễ theo dõi dữ liệu của mình đang được xử lý ra sao.

Trong lập trình hướng đối tượng, dữ liệu và phương thức được tổ chức thành các đối tượng được xác định bởi lớp của chúng. Lớp được thiết kế để quy định cách mỗi đối tượng nên hoạt động, và sau đó các đối tượng được tạo trong khuôn mẫu lớp đó. Xem hướng dẫn của DataCamp để biết thêm chi tiết về cách lớp hoạt động trong Python.

Ví dụ về OOP

Ví dụ, bạn có thể có một lớp tên là BankAccount mô tả cách mỗi tài khoản nên hoạt động. Lớp này có thể có các thuộc tính như số dư, số ID và tên khách hàng. Nó cũng có thể có các phương thức mô tả cách thông tin trong lớp có thể bị tác động, như rút tiền, nạp tiền và kiểm tra số dư.

Sau khi lớp này được định nghĩa, bạn có thể tạo một đối tượng, trong trường hợp này là một tài khoản cho ai đó. Khi đối tượng đã được tạo, bạn có thể điền dữ liệu cho từng thuộc tính và sử dụng các phương thức để thay đổi tài khoản. Xem ví dụ dưới đây.

# Define a class representing a bank account
class BankAccount:
    def __init__(self, account_id, customer_name, initial_balance=0.0):
        # Initialize account properties
        self.account_id = account_id
        self.customer_name = customer_name
        self.balance = initial_balance

    # Method to deposit money into the account
    def deposit(self, amount):
        """Deposit money into the account."""
        if amount > 0:
            # Update balance and print deposit information
            self.balance += amount
            print(f"Deposited ${amount}. New balance: ${self.balance}")
        else:
            # Print message for invalid deposit amount
            print("Invalid deposit amount. Please deposit a positive amount.")

    # Method to withdraw money from the account
    def withdraw(self, amount):
        """Withdraw money from the account."""
        if 0 < amount <= self.balance:
            # Update balance and print withdrawal information
            self.balance -= amount
            print(f"Withdrew ${amount}. New balance: ${self.balance}")
        elif amount > self.balance:
            # Print message for insufficient funds
            print("Insufficient funds. Withdrawal canceled.")
        else:
            # Print message for invalid withdrawal amount
            print("Invalid withdrawal amount. Please withdraw a positive amount.")

    # Method to check the current balance of the account
    def check_balance(self):
        """Check the current balance of the account."""
        print(f"Current balance for {self.customer_name}'s account (ID: {self.account_id}): ${self.balance}")

Đóng gói (Encapsulation)

Quá trình gom dữ liệu và hành vi của bạn vào một đối tượng được định nghĩa bởi lớp gọi là đóng gói. Nó cho phép bạn tạo một cấu trúc rõ ràng để phần còn lại của mã dễ dàng tương tác với đối tượng. Các biến được lưu trong đối tượng được gọi là thuộc tính, và các hàm quyết định hành vi của đối tượng được gọi là phương thức.

Kế thừa

Một cách để giảm trùng lặp mã là sử dụng kế thừa khi tạo lớp mới. Đây là cách tạo lớp mới giữ lại chức năng của lớp cha. Trong ví dụ tài khoản ngân hàng, bạn có thể có một lớp cha tổng quát cho mọi tài khoản và các lớp con chuyên biệt cho tài khoản tiết kiệm và tài khoản thanh toán.

Đa hình

Đa hình là khái niệm cho phép bạn dùng cùng một tên cho các phương thức khác nhau có hành vi khác nhau tùy theo đầu vào. Điều này thường được dùng cùng với kế thừa.

Ví dụ, giả sử chúng ta có một lớp cha tên Shape có phương thức tính diện tích của hình. Bạn có thể có hai lớp con, Circle và Square. Dù cả hai đều có phương thức tên là Area, định nghĩa của phương thức đó sẽ khác nhau cho từng hình. So sánh các phương thức tính diện tích cho các hình khác nhau trong khối mã dưới đây.

# Define the parent class Shape
class Shape:
	# Initialize the attributes for the shape
	def __init__(self, name):
    	    self.name = name
	
	# Define a generic method for calculating the area
	def area(self):
    	    print(f"The area of {self.name} is unknown.")
 
# Define the child class Circle that inherits from Shape
class Circle(Shape):
	# Initialize the attributes for the circle
	def __init__(self, name, radius):
    	# Call the parent class constructor
    	    super().__init__(name)
    	    self.radius = radius
	
	# Override the area method for the circle
	def area(self):
    	# Use the formula pi * r^2
    	    area = 3.14 * self.radius ** 2
    	    print(f"The area of {self.name} is {area}.")
 
# Define the child class Square that inherits from Shape
class Square(Shape):
	# Initialize the attributes for the square
	def __init__(self, name, side):
    	# Call the parent class constructor
    	    super().__init__(name)
    	    self.side = side
	
	# Override the area method for the square
	def area(self):
    	# Use the formula s^2
    	    area = self.side ** 2
    	    print(f"The area of {self.name} is {area}.")

Có nhiều tài nguyên để học cách dùng lập trình hướng đối tượng, bao gồm khóa OOP trong Python của DataCamp, khóa OOP trong R và hướng dẫn OOP trong Python.

Ưu điểm của lập trình hướng đối tượng trong phân tích dữ liệu

Lập trình hướng đối tượng có thể mang lại nhiều lợi ích cho phân tích dữ liệu:

  • Đóng gói dữ liệu: Đóng gói giúp ẩn chi tiết nội bộ của các thuật toán và cấu trúc phân tích dữ liệu, chỉ phơi bày những giao diện cần thiết. Điều này thúc đẩy sự phân tách thành phần rõ ràng và cải thiện khả năng bảo trì mã.
  • Tái sử dụng mã: Lập trình hướng đối tượng khuyến khích tạo các lớp tổng quát có thể được các lớp khác kế thừa, tránh trùng lặp mã và giảm công sức bảo trì. Điều này có thể cải thiện năng suất và hiệu quả của phân tích dữ liệu.
  • Lợi ích về thiết kế: Việc mô hình hóa các vấn đề thực tế bằng các lớp và đối tượng đại diện cho dữ liệu và hành vi giúp tăng khả năng đọc, rõ ràng và tính mô-đun của phân tích dữ liệu.

OOP vs. FP: Chọn mô hình phù hợp

Có nhiều yếu tố cần cân nhắc khi chọn dùng OOP hay FP cho dự án dữ liệu của bạn. Một số nhóm dữ liệu có thể ưu tiên mạnh một mô hình hơn mô hình kia; trong trường hợp đó, nên chọn mô hình trùng với nhóm của bạn. Ở những trường hợp khác, bản chất dữ liệu hoặc ứng dụng của bạn có thể khiến cán cân nghiêng về một phía.

Khi nào nên tận dụng OOP cho các tác vụ dữ liệu

Lập trình hướng đối tượng rất phù hợp cho các ứng dụng cần mô hình hóa thực thể. Ví dụ điển hình gồm thiết kế hệ thống quản lý thư viện hoặc xây dựng hệ thống chấm điểm cho mọi công ty trong danh mục đầu tư. Quá trình tạo các đối tượng được đóng gói giúp dễ theo dõi những thực thể này. OOP cũng được ưa chuộng khi bạn bị hạn chế bộ nhớ, vì FP thường cần nhiều bộ nhớ hơn OOP.

Khi nào nên tận dụng FP cho các tác vụ dữ liệu

Lập trình hàm vượt trội ở các tác vụ phân tích dữ liệu cần xử lý song song. Việc tập trung vào tính bất biến và tránh tác dụng phụ giúp dễ dàng song song hóa các thao tác trên một tập dữ liệu. Các hàm có thể được áp dụng đồng thời trên những phần dữ liệu khác nhau mà không can nhiễu.

Sự tập trung vào tính bất biến của FP cũng khiến nó lý tưởng cho các pipeline biến đổi dữ liệu. Các hàm có thể được hợp thành để tạo chuỗi biến đổi mà không làm thay đổi trạng thái, dẫn đến mã dễ đọc và dễ bảo trì hơn.

FP cũng thường được giới khoa học và học thuật ưa chuộng vì giữ được trọng tâm gần với toán học thuần túy.

Kết hợp kỹ thuật OOP và FP

Trong một số ứng dụng thực tế, OOP và FP có thể được kết hợp trong cùng một chương trình. Điều này cho phép nhà phát triển tận dụng điểm mạnh của mỗi mô hình. Cả tính đóng gói của OOP và tính diễn đạt của FP đều có thể được khai thác để xây dựng pipeline học máy hoặc ứng dụng web.

Tuy nhiên, việc kết hợp các mô hình như vậy có thể đem lại thách thức và cần thực hiện cẩn trọng. Sẽ khó hiểu hơn khi mã nhảy qua lại giữa nhiều mô hình. Việc đảm bảo các thành phần tích hợp trơn tru cũng có thể là thách thức.

Hãy xem ví dụ dưới đây. Đoạn mã Python này kết hợp OOP và FP để thực hiện các phép toán trên một danh sách số. Lớp MathOperation đóng vai trò như một “bộ chứa” cho một phép toán được tạo bằng FP. Các hàm filter_odd_numbers và square_operation lần lượt giúp lọc số lẻ và tính bình phương một số. Bằng cách sử dụng các thành phần này cùng nhau, mã lọc hiệu quả các số lẻ từ danh sách cho trước rồi bình phương chúng, minh họa cách OOP và FP có thể phối hợp để đơn giản hóa và tổ chức các tác vụ phân tích dữ liệu.

# Object-Oriented Programming (OOP) - Class Definition
class MathOperation:
	def __init__(self, operation):
    	    self.operation = operation
 
	def apply_operation(self, number):
    	    return self.operation(number)
 
# Functional Programming (FP) - Higher-Order Functions
def filter_odd_numbers(numbers):
    return list(filter(lambda x: x % 2 != 0, numbers))
 
def square_operation(number):
    return number ** 2
 
def process_numbers(numbers, operation_function):
    return list(map(operation_function, numbers))
 
# Example Usage:
# Define an OOP class representing a mathematical operation (square in this case)
square_operation_instance = MathOperation(square_operation)
 
# Sample list of numbers
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
 
# Use FP to filter odd numbers
odd_numbers = filter_odd_numbers(numbers)
 
# Use OOP to apply the square operation to odd numbers
result = process_numbers(odd_numbers, square_operation_instance.apply_operation)
 
# Display the result
print(result)

Để xem thêm một ví dụ kết hợp cả OOP và FP, hãy xem kho lưu trữ GitHub này.

Kết luận

Không có câu trả lời dứt khoát cho việc mô hình lập trình nào là lựa chọn tốt nhất, vì điều đó phụ thuộc vào ngữ cảnh, mục tiêu, ngôn ngữ và sở thích của lập trình viên. Tuy nhiên, vẫn có một số hướng dẫn chung bạn có thể làm theo để xác định mô hình nào sẽ phù hợp nhất với tình huống của mình.

Hãy dùng OOP khi bạn cần mô hình hóa các hệ thống phức tạp với nhiều thực thể và tương tác, và khi bạn cần đóng gói dữ liệu và hành vi thành các thành phần có thể tái sử dụng. Dùng FP khi bạn cần thực hiện các phép tính thuần với đầu vào và đầu ra đơn giản, và khi bạn cần tránh tác dụng phụ hoặc thay đổi trạng thái. Dùng cách tiếp cận kết hợp khi bạn cần tận dụng điểm mạnh của cả hai mô hình, và khi bạn cần thích ứng với các tính năng và hạn chế của ngôn ngữ.

Những lập trình viên giỏi nhất không bị bó buộc vào một mô hình này hay mô hình kia; họ có thể linh hoạt chuyển đổi giữa các mô hình khi cần để đạt mục tiêu. Khám phá nhiều mô hình ở mức độ chuyên sâu với khóa học Introduction to Programming Paradigms. Hãy thử làm một dự án dữ liệu và sử dụng cả OOP lẫn FP để giải quyết. Thực hành mỗi mô hình sẽ giúp bạn có trực giác tốt hơn về mô hình nào phù hợp nhất cho dự án của mình.

Amberle McKee

Tác giả

Amberle McKee

Tôi là Tiến sĩ với 13 năm kinh nghiệm làm việc với dữ liệu trong môi trường nghiên cứu sinh học. Tôi phát triển phần mềm bằng nhiều ngôn ngữ lập trình, bao gồm Python, MATLAB và R. Tôi đam mê chia sẻ tình yêu học hỏi của mình với mọi người.

Chủ đề

Liên quan