Menu
CoddyTech

Decorator lớp

Thuộc phần Lập trình hướng đối tượng trong hành trình Python của Coddy. Bài học 40 trên 64.

Decorator lớp cho phép bạn sửa đổi hoặc bổ sung chức năng cho lớp bằng cách bọc lớp bằng một hàm khác. Chúng hoạt động giống decorator hàm nhưng áp dụng cho toàn bộ lớp.

Đây là một lớp đơn giản không có trang trí:

class Person:
    def __init__(self, name):
        self.name = name
        
    def greet(self):
        return f"Hello, my name is {self.name}"

Tạo một decorator cho lớp để thêm một phương thức mới:

def add_farewell(cls):
    def farewell(self):
        return f"Goodbye from {self.name}"
    
    cls.farewell = farewell  # Thêm phương thức vào lớp
    return cls

Áp dụng decorator cho một lớp bằng @:

@add_farewell
class EnhancedPerson:
    def __init__(self, name):
        self.name = name
        
    def greet(self):
        return f"Hello, my name is {self.name}"

Bây giờ, lớp có cả phương thức ban đầu lẫn phương thức được thêm vào:

person = EnhancedPerson("Alice")
print(person.greet())     # Xin chào, mình tên là Alice
print(person.farewell())  # Alice chào tạm biệt

Bạn cũng có thể bọc một phương thức có sẵn: lưu phương thức gốc để bạn vẫn có thể gọi nó bên trong hàm bọc. Cách này hữu ích khi bạn muốn thêm hành vi (như theo dõi hoặc ghi nhật ký) xung quanh một phương thức mà lớp đã có:

def add_tracking(cls):
    original_greet = cls.greet  # Lưu phương thức ban đầu

    def tracked_greet(self):
        print(f"greet was called")   # Thực hiện thêm thao tác trước đó
        return original_greet(self)  # Gọi phương thức ban đầu

    cls.greet = tracked_greet  # Thay thế phương thức trong lớp
    return cls

@add_tracking
class TrackedPerson:
    def __init__(self, name):
        self.name = name

    def greet(self):
        return f"Hello, my name is {self.name}"

Khi bạn gọi greet lúc này, hàm bao chạy trước, rồi chuyển tiếp đến hàm gốc:

person = TrackedPerson("Alice")
print(person.greet())
# Đã gọi greet
# Xin chào, mình tên là Alice

Các bước chính để bọc một phương thức hiện có là:
1. Lưu phương thức gốc: original = cls.method
2. Định nghĩa một hàm mới gọi original(self) cùng với mọi logic bổ sung.
3. Gán hàm mới trở lại lớp: cls.method = new_function
4. Trả về lớp đã sửa đổi.

Điểm chính: Decorator lớp nhận một lớp làm đầu vào, sửa đổi hoặc bổ sung chức năng cho lớp đó, rồi trả về lớp đã sửa đổi. Chúng có thể thêm phương thức, sửa đổi thuộc tính hoặc bọc chức năng hiện có. Đặt @decorator_name phía trên phần định nghĩa lớp để áp dụng chúng. Đây là cách gọn gàng để mở rộng chức năng của lớp mà không cần kế thừa.

challenge icon

Thử thách

Trung bình

Trong thử thách này, bạn sẽ triển khai một trình trang trí lớp để theo dõi số lần gọi phương thức.

  • decorator.py - Triển khai trình trang trí lớp add_counter
  • calculator.py - Chứa các lớp sẽ sử dụng trình trang trí của bạn

Tệp driver.py chứa nhiều tình huống kiểm thử để xác thực phần triển khai của bạn với nhiều trường hợp sử dụng, kiểu kế thừa và trường hợp biên. Bạn không cần sửa tệp này, nhưng xem qua tệp sẽ giúp bạn hiểu hành vi mong đợi.

Tự làm thử

from calculator import Calculator

# Xử lý toàn diện các test case
test_case = input()

if test_case == "basic_functionality":
    calc = Calculator()
    print(calc.add(5, 3))  # Cần in ra 8
    print(calc.add(2, 7))  # Cần in ra 9
    print(calc.subtract(10, 4))  # Cần in ra 6
    print(calc.call_counts)  # Cần in ra {'add': 2, 'subtract': 1}

elif test_case == "multiple_instances":
    calc1 = Calculator()
    calc2 = Calculator()
    
    print(calc1.add(5, 3))  # Cần in ra 8
    print(calc2.add(2, 7))  # Cần in ra 9
    print(calc1.subtract(10, 4))  # Cần in ra 6
    
    # Cả hai instance cần dùng chung call_counts
    print(calc1.call_counts)  # Cần in ra {'add': 2, 'subtract': 1}
    print(calc2.call_counts)  # Cần in ra {'add': 2, 'subtract': 1}
    print(calc1.call_counts is calc2.call_counts)  # Cần in ra True

elif test_case == "method_call_order":
    calc = Calculator()
    
    print(calc.add(1, 2))  # Cần in ra 3
    print(calc.subtract(5, 2))  # Cần in ra 3
    print(calc.add(10, 20))  # Cần in ra 30
    print(calc.add(7, 3))  # Cần in ra 10
    print(calc.call_counts)  # Cần in ra {'add': 3, 'subtract': 1}

elif test_case == "large_values":
    calc = Calculator()
    
    print(calc.add(1000000, 2000000))  # Cần in ra 3000000
    print(calc.subtract(5000000, 2000000))  # Cần in ra 3000000
    print(calc.call_counts)  # Cần in ra {'add': 1, 'subtract': 1}

elif test_case == "negative_values":
    calc = Calculator()
    
    print(calc.add(-5, -3))  # Cần in ra -8
    print(calc.subtract(-10, -4))  # Sẽ in ra -6
    print(calc.call_counts)  # Cần in ra {'add': 1, 'subtract': 1}

elif test_case == "float_values":
    calc = Calculator()
    
    print(calc.add(5.5, 3.2))  # Sẽ in ra 8.7
    print(calc.subtract(10.5, 4.2))  # Sẽ in ra 6.3
    print(calc.call_counts)  # Cần in ra {'add': 1, 'subtract': 1}

elif test_case == "zero_values":
    calc = Calculator()
    
    print(calc.add(0, 0))  # Sẽ in ra 0
    print(calc.subtract(0, 0))  # Sẽ in ra 0
    print(calc.call_counts)  # Cần in ra {'add': 1, 'subtract': 1}

elif test_case == "counter_reset":
    calc = Calculator()
    
    print(calc.add(5, 3))  # Cần in ra 8
    print(calc.call_counts)  # Cần in ra {'add': 1, 'subtract': 0}
    
    # Đặt lại bộ đếm
    calc.call_counts = {"add": 0, "subtract": 0}
    
    print(calc.add(2, 7))  # Cần in ra 9
    print(calc.call_counts)  # Cần in ra {'add': 0, 'subtract': 0}

elif test_case == "counter_manipulation":
    calc = Calculator()
    
    print(calc.add(5, 3))  # Cần in ra 8
    print(calc.call_counts)  # Cần in ra {'add': 1, 'subtract': 0}
    
    # Thay đổi trực tiếp bộ đếm
    calc.call_counts["add"] = 100
    
    print(calc.add(2, 7))  # Cần in ra 9
    print(calc.call_counts)  # Cần in ra {'add': 101, 'subtract': 0}

elif test_case == "performance_test":
    calc = Calculator()
    
    # Thực hiện 1000 phép cộng
    for i in range(1000):
        calc.add(i, i+1)
    
    # Thực hiện 500 phép trừ
    for i in range(500):
        calc.subtract(i+10, i)
    
    print(calc.call_counts)  # Cần in ra {'add': 1000, 'subtract': 500}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Python online