1. Mô hình Mixins
Mixins là một kỹ thuật trong lập trình hướng đối tượng giúp tái sử dụng mã nguồn linh hoạt. Thay vì xây dựng cây phân cấp sâu, chúng ta có thể đưa các chức năng cụ thể vào các lớp độc lập và kết hợp chúng khi cần thiết.
Ví dụ, xét hệ thống giao thông. Các phương tiện như máy bay dân dụng, trực thăng và ô tô đều là phương tiện di chuyển. Tuy nhiên, chỉ có máy bay và trực thăng mới bay được. Do đó, chức năng bay nên được tách riêng thành một lớp phụ trợ.
class Transport:
"""Lớp cơ sở cho các phương tiện"""
def move(self):
print("Đang di chuyển trên mặt đất")
class FlightAbility:
"""Mixin cung cấp khả năng bay"""
def fly(self):
print("Đang bay trên bầu trời")
# Máy bay dân dụng kế thừa cả khả năng bay và phương tiện
class JetPlane(FlightAbility, Transport):
pass
# Trực thăng cũng có khả năng bay
class Chopper(FlightAbility, Transport):
pass
# Ô tô chỉ di chuyển trên mặt đất
class Sedan(Transport):
pass
Một số quy tắc khi sử dụng Mixins:
- Lớp chính chứa chức năng cốt lõi của đối tượng.
- Lớp phụ (Mixin) chứa chức năng bổ trợ, không đứng độc lập.
- Tên lớp Mixin thường kết thúc bằng suffix như Mixin, Ability, Capable để dễ nhận biết.
- Thứ tự kế thừa ảnh hưởng đến việc tìm kiếm phương thức (MRO), cần sắp xếp cẩn thận.
2. Metaclass (Siêu lớp)
Metaclass được hiểu là lớp dùng để tạo ra các lớp khác. Nếu ví lớp là bản thiết kế cho đối tượng, thì metaclass là bản thiết kế cho lớp.
2.1. Kiểm tra metaclass mặc định
Mọi lớp trong Python đều được tạo ra bởi một metaclass, mặc định là type.
class Transport:
pass
class Sedan(Transport):
pass
xe = Transport()
print(type(xe)) # <class '__main__.Transport'>
print(type(Transport)) # <class 'type'>
Kết quả cho thấy instance xe thuộc lớp Transport, còn bản thân lớp Transport là một đối tượng của type.
2.2. Hai cách khởi tạo lớp
Cách thông thường sử dụng từ khóa class:
class TenLop:
pass
Cách thứ hai sử dụng trực tiếp hàm dựng type, vì mọi lớp đều được sinh ra từ đây:
# type(tên_lớp, tuple_cha, dict_thuộc_tính)
DynamicClass = type("DynamicClass", (), {"attr": 1})
2.3. Mối quan hệ giữa type và object
Trong Python, type là metaclass mặc định, chịu trách nhiệm tạo ra các lớp. Trong khi đó, object là lớp cơ sở mà mọi lớp đều kế thừa ngầm định. object cung cấp các phương thức nền tảng như __str__, __eq__. Tóm lại, type tạo ra lớp, còn object là gốc của cây kế thừa đối tượng.
2.4. Tùy chỉnh Metaclass
Chúng ta không thể kế thừa trực tiếp từ một lớp bình thường để làm metaclass, mà phải kế thừa từ type. Metaclass được chỉ định qua tham số metaclass khi định nghĩa lớp.
Ví dụ 1: Validate tên lớp
Yêu cầu tên lớp phải bắt đầu bằng chữ in hoa khi định nghĩa.
class NameValidator(type):
def __init__(cls, name, bases, attrs):
if not name[0].isupper():
raise ValueError("Tên lớp phải bắt đầu bằng chữ in hoa")
super().__init__(name, bases, attrs)
class ValidClass(metaclass=NameValidator):
pass
# class invalidClass(metaclass=NameValidator): # Sẽ gây lỗi
# pass
Ví dụ 2: Kiểm soát quá trình khởi tạo
Metaclass có phương thức __call__, được gọi khi lớp được triệu gọi để tạo instance. Ta có thể chèn logic kiểm tra tại đây trước khi đối tượng được sinh ra.
class CallController(type):
def __call__(cls, *args, **kwargs):
print("Metaclass __call__ đang chạy")
# Bắt buộc chỉ dùng keyword arguments
if args:
raise TypeError("Không chấp nhận positional arguments")
return super().__call__(*args, **kwargs)
class User(metaclass=CallController):
def __init__(self, username):
self.username = username
print("Instance __init__ đang chạy")
# Tạo đối tượng
user = User(username="admin")
# User("admin") sẽ gây lỗi do vi phạm quy tắc trong __call__
Khi thực hiện User(username="admin"), phương thức __call__ của metaclass chạy trước, kiểm tra tham số, sau đó mới đến __init__ của lớp. Nếu truyền tham số vị trí, ngoại lệ sẽ được ném ra ngay từ bước đầu tiên mà không cần khởi tạo đối tượng.